From 07bb9f628d1ddacf2483d16420d8d01f63649789 Mon Sep 17 00:00:00 2001 From: Mehran Date: Fri, 16 Oct 2026 01:20:39 +0300 Subject: [PATCH 001/612] perf(agc): move per-draw thread-locals to TEB slots --- .../Execution/include/Driver/Driver.hpp | 3 +- .../Execution/include/ThreadScratch.hpp | 21 +++++++++++ .../src/Driver/Memory/WriteEvidence.cpp | 20 +++++++--- .../Graphics/src/ShaderResources.cpp | 37 ++++++++++--------- .../libSceAgcDriver/Graphics/src/Texture.cpp | 24 ++++++------ 5 files changed, 70 insertions(+), 35 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/Execution/include/ThreadScratch.hpp diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index a331ac8de8..8a4919eca5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -221,7 +221,8 @@ private: static IndirectSource& indirectSource(); static std::string describeIndirectSource(); std::array readDispatchArguments(std::uint32_t queue, std::uint64_t arguments, std::uint32_t initiator); - static bool& sampledRead(); + static bool sampledRead(); + static void setSampledRead(bool sampled); static bool writeEvidenceEnabled(); static bool writerKeyedEvidence(); static bool foreignWriters(); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/ThreadScratch.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/ThreadScratch.hpp new file mode 100644 index 0000000000..b6268d9489 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/ThreadScratch.hpp @@ -0,0 +1,21 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_THREADSCRATCH_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_THREADSCRATCH_HPP + +#include "prx/libc/include/HostThreadSlot.hpp" +#include "ThreadOwned.hpp" + +namespace AgcDriver { + +template +T& ThreadScratch() { + auto* object = HostThreadSlot::Get(); + if (object == nullptr) { + ShaderRecompiler::ThreadOwned(object); + HostThreadSlot::Set(object); + } + return *object; +} + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp index 596daf0146..65d64a46ac 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp @@ -2,16 +2,17 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Memory/WriteEvidence.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libc/include/HostThreadSlot.hpp" #include #include namespace AgcDriver::DriverDetail { SampledReadScope::SampledReadScope(std::atomic& reads) : previous(Driver::sampledRead()) { - Driver::sampledRead() = Driver::writeEvidenceEnabled() && reads.fetch_add(1, std::memory_order_relaxed) % Driver::writeEvidenceSampleEvery() == 0; + Driver::setSampledRead(Driver::writeEvidenceEnabled() && reads.fetch_add(1, std::memory_order_relaxed) % Driver::writeEvidenceSampleEvery() == 0); } -SampledReadScope::~SampledReadScope() { Driver::sampledRead() = previous; } +SampledReadScope::~SampledReadScope() { Driver::setSampledRead(previous); } void PendingView::Load() { if (Driver::validateLegacy()) return; @@ -26,9 +27,18 @@ bool PendingView::Overlaps(std::uint64_t address, std::size_t bytes) const { return Driver::validateLegacy() ? Graphics::Recorder::SnapshotWriteOverlaps(address, bytes) : Graphics::Recorder::SnapshotOverlaps(snapshot.get(), address, bytes); } -bool& Driver::sampledRead() { - static thread_local bool sampled = false; - return sampled; +namespace { + +struct SampledReadTag; + +} + +bool Driver::sampledRead() { + return HostThreadSlot::Get(); +} + +void Driver::setSampledRead(bool sampled) { + HostThreadSlot::Set(sampled); } bool Driver::writeEvidenceEnabled() { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 3dd8e92e3f..f1c4353b3e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -2,6 +2,7 @@ #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" #include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" +#include "prx/libSceAgcDriver/Execution/include/ThreadScratch.hpp" #include "ThreadOwned.hpp" #include "prx/libc/include/HostThreadLocal.hpp" #include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp" @@ -2955,17 +2956,17 @@ bool ShaderResources::fastRevalidate(std::uint64_t serialBefore, std::span* queriesSlot = nullptr; - auto& queries = ShaderRecompiler::ThreadOwned(queriesSlot); - thread_local std::vector* pendingSlot = nullptr; - auto& pending = ShaderRecompiler::ThreadOwned(pendingSlot); + struct QueriesTag {}; + auto& queries = ThreadScratch, QueriesTag>(); + struct PendingTag {}; + auto& pending = ThreadScratch, PendingTag>(); // The element each pending query stands for, in query order. - thread_local std::vector* ownersSlot = nullptr; - auto& owners = ShaderRecompiler::ThreadOwned(ownersSlot); - thread_local std::vector* imagesSlot = nullptr; - auto& images = ShaderRecompiler::ThreadOwned(imagesSlot); - thread_local std::vector* scannedKeysSlot = nullptr; - auto& scannedKeys = ShaderRecompiler::ThreadOwned(scannedKeysSlot); + struct OwnersTag {}; + auto& owners = ThreadScratch, OwnersTag>(); + struct ImagesTag {}; + auto& images = ThreadScratch, ImagesTag>(); + struct ScannedKeysTag {}; + auto& scannedKeys = ThreadScratch, ScannedKeysTag>(); queries.clear(); pending.clear(); owners.clear(); @@ -3440,10 +3441,10 @@ bool ShaderResources::revalidateFull(std::span shaders, Pr captureValidation(); return true; }; - thread_local std::vector* overlappingSlot = nullptr; - auto& overlapping = ShaderRecompiler::ThreadOwned(overlappingSlot); - thread_local std::vector* refreshedSlot = nullptr; - auto& refreshed = ShaderRecompiler::ThreadOwned(refreshedSlot); + struct OverlappingTag {}; + auto& overlapping = ThreadScratch, OverlappingTag>(); + struct RefreshedTag {}; + auto& refreshed = ThreadScratch, RefreshedTag>(); refreshed.clear(); FastFail reason = FastFail::Count; bool accepted = false; @@ -3494,8 +3495,8 @@ bool ShaderResources::revalidateFull(std::span shaders, Pr // The walk after the refresh, or beside a proof that accepted a foreign overlap, must // find every object in place and current: another object, or an upload (a moved // content version), is a decision the proof got wrong. - thread_local std::vector>* versionsSlot = nullptr; - auto& versions = ShaderRecompiler::ThreadOwned(versionsSlot); + struct VersionsTag {}; + auto& versions = ThreadScratch>, VersionsTag>(); versions.clear(); for (const auto& surface : validatedTextures) { if (surface.source != nullptr) versions.emplace_back(surface.source, surface.source->Version()); @@ -3532,8 +3533,8 @@ bool ShaderResources::revalidateFull(std::span shaders, Pr }; if (EpochRevalidate()) { if ((!directRegions.empty() || !guestMemory.Writes().empty()) && !(pendingSerialSeen != 0 && pendingSerialSeen == StorageTexture::PendingSerial())) { - thread_local std::vector* regionsSlot = nullptr; - auto& regions = ShaderRecompiler::ThreadOwned(regionsSlot); + struct RegionsTag {}; + auto& regions = ThreadScratch, RegionsTag>(); regions.clear(); for (const auto& region : directRegions) regions.push_back({region.begin, region.end, nullptr, nullptr, false}); for (const auto& [begin, end] : guestMemory.Writes()) regions.push_back({begin, end, nullptr, nullptr, false}); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 14e60f3cac..b4b3eb78a9 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -8,6 +8,7 @@ #include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "prx/libc/include/General.hpp" #include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" +#include "prx/libc/include/HostThreadSlot.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" @@ -1678,7 +1679,8 @@ bool FillClearColor(VkFormat format, std::uint32_t elementBytes, std::span; void FlushHook(std::uint64_t address, std::size_t bytes) { StorageTexture::FlushPending(address, bytes); @@ -2086,11 +2088,11 @@ bool StorageTexture::refresh(std::uint32_t firstLayer, std::uint32_t lastLayer) refreshProof = {}; struct Exempt { const StorageTexture* previous; - ~Exempt() { refreshing = previous; } - } exempt{refreshing}; + ~Exempt() { Refreshing::Set(previous); } + } exempt{Refreshing::Get()}; // This image's own pending results stay on the GPU, where the next dispatch wants them (the // flush below and the compare through the hook skip it). - refreshing = this; + Refreshing::Set(this); NoteProved(); // Results of other images pending in this memory must reach it first, except an alias's: its // units are taken on the device below (borrowUnits), so it stays pending. The keys read for @@ -2507,9 +2509,9 @@ bool StorageTexture::compareUntracked(std::uint64_t address, std::size_t bytes, if (!originalHolds(address, bytes)) return false; struct Exempt { const StorageTexture* previous; - ~Exempt() { refreshing = previous; } - } exempt{refreshing}; - refreshing = this; + ~Exempt() { Refreshing::Set(previous); } + } exempt{Refreshing::Get()}; + Refreshing::Set(this); Require(address >= descriptor.baseAddress && bytes <= guestBytes && address - descriptor.baseAddress <= guestBytes - bytes, "texture comparison exceeds its snapshot"); Require(original.size() == guestBytes, "texture comparison has no complete snapshot"); GuestMemory::FlushGpuWrites(address, bytes); @@ -3894,7 +3896,7 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons { auto& pending = Pending(); std::lock_guard lock(pending.mutex); - const auto* exempt = refreshing; + const auto* exempt = Refreshing::Get(); for (auto it = pending.textures.MayOverlap(address, bytes) ? pending.textures.begin() : pending.textures.end(); it != pending.textures.end();) { auto* texture = *it; if (texture != except && texture != exempt && texture->overlaps(address, bytes)) { @@ -3983,7 +3985,7 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons bool StorageTexture::WaitForFlushing(std::uint64_t address, std::size_t bytes, const StorageTexture* except) { auto& pending = Pending(); if (pending.flushingCount.load(std::memory_order_acquire) == 0 || GuestMemory::GpuMutex().HeldByThisThread()) return false; - const auto* exempt = refreshing; + const auto* exempt = Refreshing::Get(); for (bool waited = false;; waited = true) { { std::lock_guard lock(pending.mutex); @@ -4328,7 +4330,7 @@ std::vector> StorageTexture::overlappingPending( if (pending.textures.SurelyClear(address, bytes)) return overlapping; std::lock_guard lock(pending.mutex); if (!pending.textures.MayOverlap(address, bytes)) return overlapping; - const auto* exempt = refreshing; + const auto* exempt = Refreshing::Get(); for (auto* texture : pending.textures) { if (texture == exempt || !texture->overlaps(address, bytes) || !texture->pendingUnitInside(address, bytes)) continue; if (auto alive = texture->weak_from_this().lock()) overlapping.push_back(std::move(alive)); @@ -4509,7 +4511,7 @@ std::size_t StorageTexture::DiscardPendingInside(std::uint64_t address, std::siz std::lock_guard lock(pending.mutex); std::size_t discarded = 0; const auto end = address + bytes; - const auto* exempt = refreshing; + const auto* exempt = Refreshing::Get(); for (auto it = pending.textures.begin(); it != pending.textures.end();) { auto* texture = *it; const auto begin = texture->descriptor.baseAddress; From f03bd5506eef4029a93e0fd352dbdb85855b3011 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:40:47 +0200 Subject: [PATCH 002/612] feat(libSceLibreSsl): implement X509_NAME_get_entry --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 ++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 44 +++++++++++ core/libs/tests/GuestLibreSslX509Name.cpp | 79 +++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 3 +- 5 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/X509Name.cpp create mode 100644 core/libs/tests/GuestLibreSslX509Name.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index bd17c2379e..d65c07e61f 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509Name.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -69,4 +69,15 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_error PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_x509_name_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslX509Name.cpp) + target_include_directories(guest_libressl_x509_name_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_x509_name_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_x509_name_tests) + add_test(NAME guest_libressl_x509_name COMMAND guest_libressl_x509_name_tests) + set_tests_properties(guest_libressl_x509_name PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_x509_name PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 5cebe1b097..58dc8de0e4 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1568,11 +1568,6 @@ int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void) { return 0; } -int APS5_VABI X509_NAME_get_entry_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp new file mode 100644 index 0000000000..85fb8215a6 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct LibreSslX509NameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct LibreSslX509Name { + LibreSslStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + +static_assert(sizeof(LibreSslStack) == 32); +static_assert(offsetof(LibreSslStack, data) == 8); +static_assert(sizeof(LibreSslX509NameEntry) == 24); +static_assert(offsetof(LibreSslX509NameEntry, value) == 8); +static_assert(sizeof(LibreSslX509Name) == 40); +static_assert(offsetof(LibreSslX509Name, entries) == 0); + +extern "C" { + +LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc) { + if (!name || loc < 0 || !name->entries || name->entries->num <= loc) return nullptr; + if (!name->entries->data) throw std::invalid_argument(std::string(__func__) + ": null entry array"); + return reinterpret_cast(name->entries->data[loc]); +} + +} diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp new file mode 100644 index 0000000000..ac574ce133 --- /dev/null +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -0,0 +1,79 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct TestStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct TestNameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct TestName { + TestStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + +static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); +static_assert(sizeof(TestNameEntry) == 24 && offsetof(TestNameEntry, value) == 8); +static_assert(sizeof(TestName) == 40); + +extern "C" { +TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL X509_NAME check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetEntry() { + TestNameEntry first {}; + TestNameEntry second {}; + char* slots[] {reinterpret_cast(&first), reinterpret_cast(&second), nullptr}; + TestStack entries {2, slots, 0, 3, nullptr}; + TestName name {}; + Require(X509_NAME_get_entry_nid_postfix(nullptr, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + name.entries = &entries; + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == &first); + Require(X509_NAME_get_entry_nid_postfix(&name, 1) == &second); + Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, INT_MIN) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, INT_MAX) == nullptr); + entries.num = 0; + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + entries.num = 3; + Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + entries.data = nullptr; + RequireThrows([&] { X509_NAME_get_entry_nid_postfix(&name, 0); }); + Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); +} + +int main() { + TestGetEntry(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index c562a24a2a..f2170f8456 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,10 +2,11 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 353 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 352 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); the module's LibreSSL version is unknown and nothing is checked on the console. Behaviour that differs between LibreSSL versions throws: `SHA256` with a null digest buffer (older versions return a static buffer) and `SHA512_Final` with the SHA-512/224 or SHA-512/256 digest length. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. - [RAND_add, RAND_bytes, RAND_status, RAND_load_file, RAND_file_name, CRYPTO_malloc, CRYPTO_free, CRYPTO_set_mem_functions](../../../core/libs/prx/libSceLibreSsl/src/Runtime.cpp) (libSceLibreSsl) - follow LibreSSL's [rand_lib.c, randfile.c](https://github.com/libressl/openbsd/tree/0f0b16dc977f6ca499f0f4540078d992c96da597/src/lib/libcrypto/rand) and [malloc-wrapper.c](https://github.com/libressl/openbsd/blob/0f0b16dc977f6ca499f0f4540078d992c96da597/src/lib/libcrypto/malloc-wrapper.c), which are the same in 2.6.5 (3.4.3 linked); nothing is checked on the console. `RAND_bytes` reads host entropy and `CRYPTO_malloc` and `CRYPTO_free` use the guest heap. `CRYPTO_malloc` returns null for a size of zero or less, as LibreSSL 2.5.4 to 3.4 do; later versions take a `size_t` size and call `malloc`. A null buffer with a positive length throws - [AES_set_encrypt_key, AES_set_decrypt_key, AES_encrypt, AES_decrypt, AES_ecb_encrypt, AES_cbc_encrypt, AES_cfb128_encrypt, AES_ctr128_encrypt](../../../core/libs/prx/libSceLibreSsl/src/Aes.cpp) (libSceLibreSsl) - follow LibreSSL's [aes.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/aes/aes.h) and [mode code](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/modes). Nothing is checked on the console. Key setup follows [LibreSSL 2.5.4's aes_core.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/aes_core.c), the version the `X509_verify_cert_error_string` entry names: a null pointer returns -1 and a key length other than 128, 192 or 256 bits returns -2 before the key is written, and only the 44, 52 or 60 words the key length needs are written. The `AES_KEY` schedule uses that file's generic C layout (big-endian words, `rounds` 10, 12 or 14). The 2.5.4 amd64 build uses [aes-x86_64](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/asm/aes-x86_64.pl) instead, which copies the key bytes without swapping, so each word is byte-reversed compared with the C layout (OpenSSL 3.0.13's x86-64 code gives the same schedules byte-reversed); the console's build options are unknown, so a schedule the title reads or fills is unverified. CTR leaves the last keystream block in `ecount_buf` even at a block boundary. Null pointers in the cipher and mode functions, other `rounds` values and a CFB or CTR position outside 0-15 throw - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names +- [X509_NAME_get_entry](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names the caller builds reach it. A positive entry count with a null entry array, which LibreSSL dereferences, throws From 03ef4963434ca3a70c5aef32c457a8a895ace668 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:42:33 +0200 Subject: [PATCH 003/612] feat(libSceLibreSsl): implement X509_NAME_ENTRY_get_data --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 5 +++++ core/libs/tests/GuestLibreSslX509Name.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 58dc8de0e4..2582acdca6 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1563,11 +1563,6 @@ int APS5_VABI X509_LOOKUP_file_nid_postfix(void) { return 0; } -int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp index 85fb8215a6..57efc6993b 100644 --- a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -41,4 +41,9 @@ LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX return reinterpret_cast(name->entries->data[loc]); } +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry* entry) { + if (!entry) return nullptr; + return entry->value; +} + } diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp index ac574ce133..1dea64955e 100644 --- a/core/libs/tests/GuestLibreSslX509Name.cpp +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -35,6 +35,7 @@ static_assert(sizeof(TestName) == 40); extern "C" { TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const TestNameEntry*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -73,7 +74,20 @@ static void TestGetEntry() { Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); } +static void TestEntryGetData() { + int value = 0; + TestNameEntry entry {}; + Require(X509_NAME_ENTRY_get_data_nid_postfix(nullptr) == nullptr); + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == nullptr); + entry.value = &value; + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + entry.object = &entry; + entry.set = 7; + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); +} + int main() { TestGetEntry(); + TestEntryGetData(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index f2170f8456..1dd7936933 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,11 +2,11 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 352 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 351 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); the module's LibreSSL version is unknown and nothing is checked on the console. Behaviour that differs between LibreSSL versions throws: `SHA256` with a null digest buffer (older versions return a static buffer) and `SHA512_Final` with the SHA-512/224 or SHA-512/256 digest length. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. - [RAND_add, RAND_bytes, RAND_status, RAND_load_file, RAND_file_name, CRYPTO_malloc, CRYPTO_free, CRYPTO_set_mem_functions](../../../core/libs/prx/libSceLibreSsl/src/Runtime.cpp) (libSceLibreSsl) - follow LibreSSL's [rand_lib.c, randfile.c](https://github.com/libressl/openbsd/tree/0f0b16dc977f6ca499f0f4540078d992c96da597/src/lib/libcrypto/rand) and [malloc-wrapper.c](https://github.com/libressl/openbsd/blob/0f0b16dc977f6ca499f0f4540078d992c96da597/src/lib/libcrypto/malloc-wrapper.c), which are the same in 2.6.5 (3.4.3 linked); nothing is checked on the console. `RAND_bytes` reads host entropy and `CRYPTO_malloc` and `CRYPTO_free` use the guest heap. `CRYPTO_malloc` returns null for a size of zero or less, as LibreSSL 2.5.4 to 3.4 do; later versions take a `size_t` size and call `malloc`. A null buffer with a positive length throws - [AES_set_encrypt_key, AES_set_decrypt_key, AES_encrypt, AES_decrypt, AES_ecb_encrypt, AES_cbc_encrypt, AES_cfb128_encrypt, AES_ctr128_encrypt](../../../core/libs/prx/libSceLibreSsl/src/Aes.cpp) (libSceLibreSsl) - follow LibreSSL's [aes.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/aes/aes.h) and [mode code](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/modes). Nothing is checked on the console. Key setup follows [LibreSSL 2.5.4's aes_core.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/aes_core.c), the version the `X509_verify_cert_error_string` entry names: a null pointer returns -1 and a key length other than 128, 192 or 256 bits returns -2 before the key is written, and only the 44, 52 or 60 words the key length needs are written. The `AES_KEY` schedule uses that file's generic C layout (big-endian words, `rounds` 10, 12 or 14). The 2.5.4 amd64 build uses [aes-x86_64](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/asm/aes-x86_64.pl) instead, which copies the key bytes without swapping, so each word is byte-reversed compared with the C layout (OpenSSL 3.0.13's x86-64 code gives the same schedules byte-reversed); the console's build options are unknown, so a schedule the title reads or fills is unverified. CTR leaves the last keystream block in `ecount_buf` even at a block boundary. Null pointers in the cipher and mode functions, other `rounds` values and a CFB or CTR position outside 0-15 throw - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names -- [X509_NAME_get_entry](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names the caller builds reach it. A positive entry count with a null entry array, which LibreSSL dereferences, throws +- [X509_NAME_get_entry, X509_NAME_ENTRY_get_data](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names and entries the caller builds reach them. A positive entry count with a null entry array, which LibreSSL dereferences, throws From 73b11e79a71c0d34a77817fb27e3e2179f52ea64 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 06:00:22 +0100 Subject: [PATCH 004/612] refactor(libSceLibreSsl): declare the X509_NAME exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 27 ++++++++++++- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 24 +---------- core/libs/tests/GuestLibreSslX509Name.cpp | 40 ++++++++++--------- 3 files changed, 48 insertions(+), 43 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index b610257cb7..650b94024e 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -44,6 +44,29 @@ struct Sha512Context { std::uint32_t num, mdLength; }; +struct LibreSslStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct LibreSslX509NameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct LibreSslX509Name { + LibreSslStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + extern "C" { int APS5_VABI ASN1_INTEGER_get_nid_postfix(void); @@ -357,8 +380,8 @@ int APS5_VABI X509_EXTENSION_get_data_nid_postfix(void); int APS5_VABI X509_EXTENSION_get_object_nid_postfix(void); int APS5_VABI X509_INFO_free_nid_postfix(void); int APS5_VABI X509_LOOKUP_file_nid_postfix(void); -int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void); -int APS5_VABI X509_NAME_get_entry_nid_postfix(void); +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry* entry); +LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc); int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void); int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(void); int APS5_VABI X509_NAME_oneline_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp index 57efc6993b..f68342f636 100644 --- a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -1,31 +1,9 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslStack { - int num; - char** data; - int sorted; - int numAlloc; - void* comp; -}; - -struct LibreSslX509NameEntry { - void* object; - void* value; - int set; - int size; -}; - -struct LibreSslX509Name { - LibreSslStack* entries; - int modified; - void* bytes; - unsigned char* canonEnc; - int canonEnclen; -}; - static_assert(sizeof(LibreSslStack) == 32); static_assert(offsetof(LibreSslStack, data) == 8); static_assert(sizeof(LibreSslX509NameEntry) == 24); diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp index 1dea64955e..365b16002e 100644 --- a/core/libs/tests/GuestLibreSslX509Name.cpp +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -33,9 +34,12 @@ static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); static_assert(sizeof(TestNameEntry) == 24 && offsetof(TestNameEntry, value) == 8); static_assert(sizeof(TestName) == 40); -extern "C" { -TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); -void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const TestNameEntry*); +static const LibreSslX509Name* Name(const TestName* name) { + return reinterpret_cast(name); +} + +static const LibreSslX509NameEntry* Entry(const TestNameEntry* entry) { + return reinterpret_cast(entry); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -56,34 +60,34 @@ static void TestGetEntry() { TestStack entries {2, slots, 0, 3, nullptr}; TestName name {}; Require(X509_NAME_get_entry_nid_postfix(nullptr, 0) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), -1) == nullptr); name.entries = &entries; - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == &first); - Require(X509_NAME_get_entry_nid_postfix(&name, 1) == &second); - Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, INT_MIN) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, INT_MAX) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == Entry(&first)); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 1) == Entry(&second)); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), INT_MIN) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), INT_MAX) == nullptr); entries.num = 0; - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == nullptr); entries.num = 3; - Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 2) == nullptr); entries.data = nullptr; - RequireThrows([&] { X509_NAME_get_entry_nid_postfix(&name, 0); }); - Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); + RequireThrows([&] { X509_NAME_get_entry_nid_postfix(Name(&name), 0); }); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 3) == nullptr); } static void TestEntryGetData() { int value = 0; TestNameEntry entry {}; Require(X509_NAME_ENTRY_get_data_nid_postfix(nullptr) == nullptr); - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == nullptr); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == nullptr); entry.value = &value; - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == &value); entry.object = &entry; entry.set = 7; - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == &value); } int main() { From 8b0e336d2c8028a3b92dc0fbf53294ae254eeec5 Mon Sep 17 00:00:00 2001 From: lopdan <62883984+lopdan@users.noreply.github.com> Date: Sun, 25 Oct 2026 14:59:00 +0100 Subject: [PATCH 005/612] feat(libSceLibreSsl): implement OCSP_resp_count and OCSP_resp_get0 Read the guest OCSP_BASICRESP responses stack as LibreSSL 2.5.4's ocsp_cl.c and stack.c do. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 15 +++ core/libs/prx/libSceLibreSsl/Export.cpp | 10 -- core/libs/prx/libSceLibreSsl/include/Api.hpp | 4 +- .../libSceLibreSsl/src/OcspBasicResponse.cpp | 61 +++++++++ .../tests/GuestLibreSslOcspBasicResponse.cpp | 123 ++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 +- 6 files changed, 204 insertions(+), 13 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/OcspBasicResponse.cpp create mode 100644 core/libs/tests/GuestLibreSslOcspBasicResponse.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 20e7b85ba9..47746a85a6 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -93,3 +93,18 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/OcspBasicResponse.cpp) + +if(BUILD_TESTING) + add_test_executable(guest_libressl_ocsp_basic_response_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslOcspBasicResponse.cpp) + target_include_directories(guest_libressl_ocsp_basic_response_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ocsp_basic_response_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_ocsp_basic_response_tests) + add_test(NAME guest_libressl_ocsp_basic_response COMMAND guest_libressl_ocsp_basic_response_tests) + set_tests_properties(guest_libressl_ocsp_basic_response PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ocsp_basic_response PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index dfbd9c146d..8581bdc960 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -828,16 +828,6 @@ int APS5_VABI OCSP_crl_reason_str_nid_postfix(void) { return 0; } -int APS5_VABI OCSP_resp_count_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI OCSP_resp_get0_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI OCSP_response_get1_basic_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index a9bfae70a0..12ca98c454 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -222,8 +222,8 @@ int APS5_VABI OCSP_basic_verify_nid_postfix(void); int APS5_VABI OCSP_cert_status_str_nid_postfix(void); int APS5_VABI OCSP_check_validity_nid_postfix(void); int APS5_VABI OCSP_crl_reason_str_nid_postfix(void); -int APS5_VABI OCSP_resp_count_nid_postfix(void); -int APS5_VABI OCSP_resp_get0_nid_postfix(void); +int APS5_VABI OCSP_resp_count_nid_postfix(const void* basicResponse); +void* APS5_VABI OCSP_resp_get0_nid_postfix(const void* basicResponse, std::int32_t index); int APS5_VABI OCSP_response_get1_basic_nid_postfix(void); int APS5_VABI OCSP_response_status_nid_postfix(void); int APS5_VABI OCSP_response_status_str_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/OcspBasicResponse.cpp b/core/libs/prx/libSceLibreSsl/src/OcspBasicResponse.cpp new file mode 100644 index 0000000000..bf9c246ae9 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/OcspBasicResponse.cpp @@ -0,0 +1,61 @@ +#include +#include +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libc/include/General.hpp" + +namespace { + +struct Stack { + std::int32_t num; + void** data; + std::int32_t sorted; + std::int32_t numAlloc; + void* comp; +}; + +struct ResponseData { + void* version; + void* responderId; + void* producedAt; + const Stack* responses; + void* responseExtensions; +}; + +struct BasicResponse { + const ResponseData* tbsResponseData; + void* signatureAlgorithm; + void* signature; + void* certs; +}; + +static_assert(sizeof(Stack) == 32); +static_assert(offsetof(Stack, data) == 8); +static_assert(sizeof(ResponseData) == 40); +static_assert(offsetof(ResponseData, responses) == 24); +static_assert(sizeof(BasicResponse) == 32); + +const Stack* Responses(const void* basicResponse, const char* function) { + const ResponseData* data = static_cast(basicResponse)->tbsResponseData; + if (!data) NotImplemented_nid_no_patch(function); + return data->responses; +} + +} // namespace + +extern "C" { + +int APS5_VABI OCSP_resp_count_nid_postfix(const void* basicResponse) { + if (!basicResponse) return -1; + const Stack* responses = Responses(basicResponse, __func__); + return responses ? responses->num : -1; +} + +void* APS5_VABI OCSP_resp_get0_nid_postfix(const void* basicResponse, std::int32_t index) { + if (!basicResponse) return nullptr; + const Stack* responses = Responses(basicResponse, __func__); + if (!responses || index < 0 || index >= responses->num) return nullptr; + if (!responses->data) NotImplemented_nid_no_patch(__func__); + return responses->data[index]; +} + +} diff --git a/core/libs/tests/GuestLibreSslOcspBasicResponse.cpp b/core/libs/tests/GuestLibreSslOcspBasicResponse.cpp new file mode 100644 index 0000000000..308af1f329 --- /dev/null +++ b/core/libs/tests/GuestLibreSslOcspBasicResponse.cpp @@ -0,0 +1,123 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +struct alignas(8) Guest { + std::array basic; + std::array data; + std::array stack; + std::array decoyData; + std::array decoyStack; + std::array entries; + std::array decoyEntries; +}; + +static constexpr std::int32_t MinIndex = std::numeric_limits::min(); +static constexpr std::int32_t MaxIndex = std::numeric_limits::max(); +static constexpr std::int32_t Indices[] {0, 1, 2, 3, -1, MinIndex, MaxIndex}; + +static int first; +static int third; +static int beyond; + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "failed: %s\n", message); + std::abort(); +} + +template static void RequireThrows(TAction action, const char* message) { + try { action(); } catch (const std::exception&) { return; } + Require(false, message); +} + +template static void Store(std::array& object, std::size_t offset, TValue value) { + Require(offset + sizeof(value) <= TSize, "store stays inside the object"); + std::memcpy(object.data() + offset, &value, sizeof(value)); +} + +static void Build(Guest& guest, std::int32_t count) { + std::memset(&guest, 0xa5, sizeof(guest)); + guest.entries = {&first, nullptr, &third, &beyond}; + for (auto& entry : guest.decoyEntries) entry = &entry; + Store(guest.decoyStack, 0, std::int32_t{7}); + Store(guest.decoyStack, 8, guest.decoyEntries.data()); + for (std::size_t offset = 0; offset < 40; offset += 8) Store(guest.decoyData, offset, guest.decoyStack.data()); + for (std::size_t offset = 8; offset < 32; offset += 8) Store(guest.basic, offset, guest.decoyData.data()); + Store(guest.basic, 0, guest.data.data()); + for (std::size_t offset = 0; offset < 40; offset += 8) Store(guest.data, offset, guest.decoyStack.data()); + Store(guest.data, 24, guest.stack.data()); + Store(guest.stack, 0, count); + Store(guest.stack, 8, guest.entries.data()); + Store(guest.stack, 16, std::int32_t{5}); + Store(guest.stack, 20, std::int32_t{2}); + Store(guest.stack, 24, guest.decoyEntries.data()); +} + +static int Count(Guest& guest) { + return OCSP_resp_count_nid_postfix(guest.basic.data()); +} + +static void* Get(Guest& guest, std::int32_t index) { + return OCSP_resp_get0_nid_postfix(guest.basic.data(), index); +} + +int main() { + Require(OCSP_resp_count_nid_postfix(nullptr) == -1, "a null basic response counts -1"); + for (const auto index : Indices) + Require(OCSP_resp_get0_nid_postfix(nullptr, index) == nullptr, "a null basic response has no entries"); + + Guest guest; + Build(guest, 3); + const Guest snapshot = guest; + Require(Count(guest) == 3, "the count is the 32-bit size of the stack at offset 24 of the response data"); + Require(Get(guest, 0) == &first, "index 0 returns the first entry"); + Require(Get(guest, 1) == nullptr, "a null entry is returned as stored"); + Require(Get(guest, 2) == &third, "the last index returns the last entry"); + Require(Get(guest, 3) == nullptr, "an index equal to the count returns null"); + Require(Get(guest, -1) == nullptr, "a negative index returns null"); + Require(Get(guest, MinIndex) == nullptr, "the minimum index returns null"); + Require(Get(guest, MaxIndex) == nullptr, "the maximum index returns null"); + Require(std::memcmp(&guest, &snapshot, sizeof(guest)) == 0, "queries do not write the objects"); + + Store(guest.data, 24, static_cast(nullptr)); + Require(Count(guest) == -1, "a null responses stack counts -1"); + for (const auto index : Indices) Require(Get(guest, index) == nullptr, "a null responses stack has no entries"); + + Build(guest, 0); + Require(Count(guest) == 0, "an empty stack counts 0"); + for (const auto index : Indices) Require(Get(guest, index) == nullptr, "an empty stack has no entries"); + + Build(guest, -2); + Require(Count(guest) == -2, "a negative stack size is returned unchanged"); + for (const auto index : Indices) Require(Get(guest, index) == nullptr, "a negative stack size has no entries"); + + Build(guest, MaxIndex); + Require(Count(guest) == MaxIndex, "the maximum stack size is returned unchanged"); + Require(Get(guest, 0) == &first, "index 0 is below the maximum stack size"); + Require(Get(guest, 3) == &beyond, "the stack size alone bounds the index"); + Require(Get(guest, MaxIndex) == nullptr, "an index equal to the maximum stack size returns null"); + Require(Get(guest, -1) == nullptr, "a negative index returns null for the maximum stack size"); + Require(Get(guest, MinIndex) == nullptr, "the minimum index returns null for the maximum stack size"); + + Build(guest, 3); + Store(guest.stack, 8, static_cast(nullptr)); + Require(Count(guest) == 3, "the count does not read the entry array"); + Require(Get(guest, 3) == nullptr, "an index equal to the count returns null before the entry array is read"); + Require(Get(guest, -1) == nullptr, "a negative index returns null before the entry array is read"); + RequireThrows([&] { Get(guest, 0); }, "a null entry array with index 0 throws"); + RequireThrows([&] { Get(guest, 2); }, "a null entry array with the last index throws"); + + Build(guest, 3); + Store(guest.basic, 0, static_cast(nullptr)); + RequireThrows([&] { Count(guest); }, "a null response data pointer throws in the count"); + for (const auto index : Indices) + RequireThrows([&] { Get(guest, index); }, "a null response data pointer throws for every index"); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index a9e5271b10..9a3548ab56 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 345 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 343 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); the module's LibreSSL version is unknown and nothing is checked on the console. Behaviour that differs between LibreSSL versions throws: `SHA256` with a null digest buffer (older versions return a static buffer) and `SHA512_Final` with the SHA-512/224 or SHA-512/256 digest length. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,6 +12,8 @@ - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. +- [OCSP_resp_count and OCSP_resp_get0](../../../core/libs/prx/libSceLibreSsl/src/OcspBasicResponse.cpp) - not verified on the console. The signatures, the guest `OCSP_BASICRESP`, `OCSP_RESPDATA` and stack layouts and the results follow LibreSSL 2.5.4's [ocsp.h and ocsp_cl.c](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp) and [stack.h and stack.c](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/stack), the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. A null response data pointer, and a null entry array with an index in range, throw where that release dereferences them. + ### Functional - [ASN1_INTEGER_get](../../../core/libs/prx/libSceLibreSsl/src/Asn1.cpp) has no console verification for nonpositive lengths. It follows [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/a_int.c#L363-L389): valid integer types return zero without reading data when length is zero or negative; invalid types return -1 first. Later LibreSSL versions reject negative lengths through `ASN1_INTEGER_get_int64`; that later contract is not used for this library. From 01b8161fb90fb242e46ec059f7309aa9b81fbe1f Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:40:47 +0200 Subject: [PATCH 006/612] feat(libSceLibreSsl): implement X509_NAME_get_entry --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 ++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 44 +++++++++++ core/libs/tests/GuestLibreSslX509Name.cpp | 79 +++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 3 +- 5 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/X509Name.cpp create mode 100644 core/libs/tests/GuestLibreSslX509Name.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 20e7b85ba9..640e45e6a4 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509Name.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -77,6 +77,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_error PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_x509_name_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslX509Name.cpp) + target_include_directories(guest_libressl_x509_name_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_x509_name_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_x509_name_tests) + add_test(NAME guest_libressl_x509_name COMMAND guest_libressl_x509_name_tests) + set_tests_properties(guest_libressl_x509_name PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_x509_name PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index dfbd9c146d..9535f86743 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1528,11 +1528,6 @@ int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void) { return 0; } -int APS5_VABI X509_NAME_get_entry_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp new file mode 100644 index 0000000000..85fb8215a6 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct LibreSslX509NameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct LibreSslX509Name { + LibreSslStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + +static_assert(sizeof(LibreSslStack) == 32); +static_assert(offsetof(LibreSslStack, data) == 8); +static_assert(sizeof(LibreSslX509NameEntry) == 24); +static_assert(offsetof(LibreSslX509NameEntry, value) == 8); +static_assert(sizeof(LibreSslX509Name) == 40); +static_assert(offsetof(LibreSslX509Name, entries) == 0); + +extern "C" { + +LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc) { + if (!name || loc < 0 || !name->entries || name->entries->num <= loc) return nullptr; + if (!name->entries->data) throw std::invalid_argument(std::string(__func__) + ": null entry array"); + return reinterpret_cast(name->entries->data[loc]); +} + +} diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp new file mode 100644 index 0000000000..ac574ce133 --- /dev/null +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -0,0 +1,79 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct TestStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct TestNameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct TestName { + TestStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + +static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); +static_assert(sizeof(TestNameEntry) == 24 && offsetof(TestNameEntry, value) == 8); +static_assert(sizeof(TestName) == 40); + +extern "C" { +TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL X509_NAME check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetEntry() { + TestNameEntry first {}; + TestNameEntry second {}; + char* slots[] {reinterpret_cast(&first), reinterpret_cast(&second), nullptr}; + TestStack entries {2, slots, 0, 3, nullptr}; + TestName name {}; + Require(X509_NAME_get_entry_nid_postfix(nullptr, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + name.entries = &entries; + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == &first); + Require(X509_NAME_get_entry_nid_postfix(&name, 1) == &second); + Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, INT_MIN) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, INT_MAX) == nullptr); + entries.num = 0; + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + entries.num = 3; + Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + entries.data = nullptr; + RequireThrows([&] { X509_NAME_get_entry_nid_postfix(&name, 0); }); + Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); +} + +int main() { + TestGetEntry(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index e9885b8324..36be714e5c 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 345 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 344 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -11,6 +11,7 @@ - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. +- [X509_NAME_get_entry](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names the caller builds reach it. A positive entry count with a null entry array, which LibreSSL dereferences, throws ### Functional From 3d69aff16d53e795e34400710a54c45ee0da1e36 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:42:33 +0200 Subject: [PATCH 007/612] feat(libSceLibreSsl): implement X509_NAME_ENTRY_get_data --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 5 +++++ core/libs/tests/GuestLibreSslX509Name.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 9535f86743..288b70634d 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1523,11 +1523,6 @@ int APS5_VABI X509_LOOKUP_file_nid_postfix(void) { return 0; } -int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp index 85fb8215a6..57efc6993b 100644 --- a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -41,4 +41,9 @@ LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX return reinterpret_cast(name->entries->data[loc]); } +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry* entry) { + if (!entry) return nullptr; + return entry->value; +} + } diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp index ac574ce133..1dea64955e 100644 --- a/core/libs/tests/GuestLibreSslX509Name.cpp +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -35,6 +35,7 @@ static_assert(sizeof(TestName) == 40); extern "C" { TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const TestNameEntry*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -73,7 +74,20 @@ static void TestGetEntry() { Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); } +static void TestEntryGetData() { + int value = 0; + TestNameEntry entry {}; + Require(X509_NAME_ENTRY_get_data_nid_postfix(nullptr) == nullptr); + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == nullptr); + entry.value = &value; + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + entry.object = &entry; + entry.set = 7; + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); +} + int main() { TestGetEntry(); + TestEntryGetData(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 36be714e5c..c10ecd8584 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 344 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 343 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -11,7 +11,7 @@ - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [X509_NAME_get_entry](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names the caller builds reach it. A positive entry count with a null entry array, which LibreSSL dereferences, throws +- [X509_NAME_get_entry, X509_NAME_ENTRY_get_data](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names and entries the caller builds reach them. A positive entry count with a null entry array, which LibreSSL dereferences, throws ### Functional From b976726f00abcb90ff3be2d105580a35c5693490 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 06:00:22 +0100 Subject: [PATCH 008/612] refactor(libSceLibreSsl): declare the X509_NAME exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 27 ++++++++++++- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 24 +---------- core/libs/tests/GuestLibreSslX509Name.cpp | 40 ++++++++++--------- 3 files changed, 48 insertions(+), 43 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index a9bfae70a0..667c40b44a 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -51,6 +51,29 @@ struct Sha512Context { std::uint32_t num, mdLength; }; +struct LibreSslStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct LibreSslX509NameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct LibreSslX509Name { + LibreSslStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -364,8 +387,8 @@ int APS5_VABI X509_EXTENSION_get_data_nid_postfix(void); int APS5_VABI X509_EXTENSION_get_object_nid_postfix(void); int APS5_VABI X509_INFO_free_nid_postfix(void); int APS5_VABI X509_LOOKUP_file_nid_postfix(void); -int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void); -int APS5_VABI X509_NAME_get_entry_nid_postfix(void); +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry* entry); +LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc); int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void); int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(void); int APS5_VABI X509_NAME_oneline_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp index 57efc6993b..f68342f636 100644 --- a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -1,31 +1,9 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslStack { - int num; - char** data; - int sorted; - int numAlloc; - void* comp; -}; - -struct LibreSslX509NameEntry { - void* object; - void* value; - int set; - int size; -}; - -struct LibreSslX509Name { - LibreSslStack* entries; - int modified; - void* bytes; - unsigned char* canonEnc; - int canonEnclen; -}; - static_assert(sizeof(LibreSslStack) == 32); static_assert(offsetof(LibreSslStack, data) == 8); static_assert(sizeof(LibreSslX509NameEntry) == 24); diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp index 1dea64955e..365b16002e 100644 --- a/core/libs/tests/GuestLibreSslX509Name.cpp +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -33,9 +34,12 @@ static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); static_assert(sizeof(TestNameEntry) == 24 && offsetof(TestNameEntry, value) == 8); static_assert(sizeof(TestName) == 40); -extern "C" { -TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); -void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const TestNameEntry*); +static const LibreSslX509Name* Name(const TestName* name) { + return reinterpret_cast(name); +} + +static const LibreSslX509NameEntry* Entry(const TestNameEntry* entry) { + return reinterpret_cast(entry); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -56,34 +60,34 @@ static void TestGetEntry() { TestStack entries {2, slots, 0, 3, nullptr}; TestName name {}; Require(X509_NAME_get_entry_nid_postfix(nullptr, 0) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), -1) == nullptr); name.entries = &entries; - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == &first); - Require(X509_NAME_get_entry_nid_postfix(&name, 1) == &second); - Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, INT_MIN) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, INT_MAX) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == Entry(&first)); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 1) == Entry(&second)); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), INT_MIN) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), INT_MAX) == nullptr); entries.num = 0; - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == nullptr); entries.num = 3; - Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 2) == nullptr); entries.data = nullptr; - RequireThrows([&] { X509_NAME_get_entry_nid_postfix(&name, 0); }); - Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); + RequireThrows([&] { X509_NAME_get_entry_nid_postfix(Name(&name), 0); }); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 3) == nullptr); } static void TestEntryGetData() { int value = 0; TestNameEntry entry {}; Require(X509_NAME_ENTRY_get_data_nid_postfix(nullptr) == nullptr); - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == nullptr); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == nullptr); entry.value = &value; - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == &value); entry.object = &entry; entry.set = 7; - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == &value); } int main() { From 28f406378fa008ced697b720fd4d399418806e09 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:53:15 +0200 Subject: [PATCH 009/612] feat(libSceLibreSsl): implement SSL_get_SSL_CTX --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 +++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 78 +++++++++++++++++++++ core/libs/tests/GuestLibreSslSsl.cpp | 65 +++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 3 +- 5 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/Ssl.cpp create mode 100644 core/libs/tests/GuestLibreSslSsl.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 20e7b85ba9..1573a883cf 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ssl.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -77,6 +77,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_error PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_ssl_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSsl.cpp) + target_include_directories(guest_libressl_ssl_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_tests) + add_test(NAME guest_libressl_ssl COMMAND guest_libressl_ssl_tests) + set_tests_properties(guest_libressl_ssl PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index dfbd9c146d..67d2512614 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1213,11 +1213,6 @@ int APS5_VABI SSL_get1_session_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_SSL_CTX_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_get_certificate_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp new file mode 100644 index 0000000000..c817055b09 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct LibreSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + int server; + void* s3; + void* d1; + void* param; + void* cipherList; + void* cert; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* session; + int verifyMode; + int error; + int errorCode; + void* ctx; + std::int64_t verifyResult; + int references; + int clientVersion; + unsigned int maxSendFragment; + char* tlsextHostname; + int tlsextStatusType; + void* initialCtx; + void* encReadCtx; + void* readHash; + LibreSslSslInternal* internal; +}; + +static_assert(sizeof(LibreSslSslInternal) == 432); +static_assert(offsetof(LibreSslSslInternal, rwstate) == 156); +static_assert(offsetof(LibreSslSslInternal, shutdown) == 168); +static_assert(sizeof(LibreSsl) == 232); +static_assert(offsetof(LibreSsl, rbio) == 16); +static_assert(offsetof(LibreSsl, wbio) == 24); +static_assert(offsetof(LibreSsl, session) == 128); +static_assert(offsetof(LibreSsl, ctx) == 152); +static_assert(offsetof(LibreSsl, verifyResult) == 160); +static_assert(offsetof(LibreSsl, internal) == 224); + +namespace { + +const LibreSsl& Connection(const LibreSsl* ssl, const char* function) { + if (!ssl) throw std::invalid_argument(std::string(function) + ": null connection"); + return *ssl; +} + +const LibreSslSslInternal& Internal(const LibreSsl* ssl, const char* function) { + const LibreSsl& connection = Connection(ssl, function); + if (!connection.internal) throw std::invalid_argument(std::string(function) + ": null internal state"); + return *connection.internal; +} + +} + +extern "C" { + +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).ctx; +} + +} diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp new file mode 100644 index 0000000000..db130edf56 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -0,0 +1,65 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct TestSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct TestSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + unsigned char beforeSession[88]; + void* session; + unsigned char beforeCtx[16]; + void* ctx; + std::int64_t verifyResult; + unsigned char beforeInternal[56]; + TestSslInternal* internal; +}; + +static_assert(sizeof(TestSslInternal) == 432 && offsetof(TestSslInternal, rwstate) == 156 && offsetof(TestSslInternal, shutdown) == 168); +static_assert(sizeof(TestSsl) == 232 && offsetof(TestSsl, session) == 128 && offsetof(TestSsl, ctx) == 152); +static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, internal) == 224); + +extern "C" { +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetSslCtx() { + int context = 0; + int session = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_SSL_CTX_nid_postfix(nullptr); }); + Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == nullptr); + ssl.ctx = &context; + ssl.session = &session; + Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); +} + +int main() { + TestGetSslCtx(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index e9885b8324..cdcc326d69 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 345 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 344 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -11,6 +11,7 @@ - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. +- [SSL_get_SSL_CTX](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach it. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From ccef4ef4af5e3f0535c735b0ace29525d12d787e Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:17 +0200 Subject: [PATCH 010/612] feat(libSceLibreSsl): implement SSL_get_rbio --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 13 +++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 67d2512614..3a5a56347e 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1263,11 +1263,6 @@ int APS5_VABI SSL_get_privatekey_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_rbio_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_get_servername_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index c817055b09..3069c3cc5d 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -75,4 +75,8 @@ void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).ctx; } +void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).rbio; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index db130edf56..95fa53ff94 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -35,6 +35,7 @@ static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, intern extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); +void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -59,7 +60,19 @@ static void TestGetSslCtx() { Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); } +static void TestGetRbio() { + int read = 0; + int write = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_rbio_nid_postfix(nullptr); }); + Require(SSL_get_rbio_nid_postfix(&ssl) == nullptr); + ssl.rbio = &read; + ssl.wbio = &write; + Require(SSL_get_rbio_nid_postfix(&ssl) == &read); +} + int main() { TestGetSslCtx(); + TestGetRbio(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index cdcc326d69..9aacc421c7 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 344 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 343 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -11,7 +11,7 @@ - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach it. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 71ba80eba9b4e54ae0faf2fc706d8bb68aace348 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:27 +0200 Subject: [PATCH 011/612] feat(libSceLibreSsl): implement SSL_get_wbio --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 15 +++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 3a5a56347e..f282b6b703 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1283,11 +1283,6 @@ int APS5_VABI SSL_get_verify_result_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_wbio_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 3069c3cc5d..2c544b2aa2 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -79,4 +79,8 @@ void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).rbio; } +void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).wbio; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 95fa53ff94..6547b2aac1 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -36,6 +36,7 @@ static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, intern extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); +void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -71,8 +72,22 @@ static void TestGetRbio() { Require(SSL_get_rbio_nid_postfix(&ssl) == &read); } +static void TestGetWbio() { + int read = 0; + int write = 0; + int buffer = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_wbio_nid_postfix(nullptr); }); + Require(SSL_get_wbio_nid_postfix(&ssl) == nullptr); + ssl.rbio = &read; + ssl.wbio = &write; + ssl.bbio = &buffer; + Require(SSL_get_wbio_nid_postfix(&ssl) == &write); +} + int main() { TestGetSslCtx(); TestGetRbio(); + TestGetWbio(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 9aacc421c7..8518da17ee 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 343 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 342 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -11,7 +11,7 @@ - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX, SSL_get_rbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 20ac7404656ec8babb8e3d77c94d882db9c77612 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:37 +0200 Subject: [PATCH 012/612] feat(libSceLibreSsl): implement SSL_version --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index f282b6b703..e2511907cf 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1368,11 +1368,6 @@ int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void) { return 0; } -int APS5_VABI SSL_version_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_want_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 2c544b2aa2..a980c12199 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -83,4 +83,8 @@ void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).wbio; } +int APS5_VABI SSL_version_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).version; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 6547b2aac1..10945d54ba 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -37,6 +37,7 @@ extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); +int APS5_VABI SSL_version_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -85,9 +86,20 @@ static void TestGetWbio() { Require(SSL_get_wbio_nid_postfix(&ssl) == &write); } +static void TestVersion() { + TestSsl ssl {}; + RequireThrows([] { SSL_version_nid_postfix(nullptr); }); + Require(SSL_version_nid_postfix(&ssl) == 0); + ssl.version = 0x0303; + Require(SSL_version_nid_postfix(&ssl) == 0x0303); + ssl.version = 0xFEFF; + Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); + TestVersion(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 8518da17ee..d8ab0856bc 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 342 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 341 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -11,7 +11,7 @@ - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 461a07feca3c659585fd516bb2aad2c8b31fd6df Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:47 +0200 Subject: [PATCH 013/612] feat(libSceLibreSsl): implement SSL_get_verify_result --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index e2511907cf..e4704f7fb5 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1278,11 +1278,6 @@ int APS5_VABI SSL_get_shutdown_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_verify_result_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index a980c12199..89c984e2d6 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -87,4 +87,8 @@ int APS5_VABI SSL_version_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).version; } +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).verifyResult; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 10945d54ba..2d1a43528c 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -38,6 +38,7 @@ void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -96,10 +97,23 @@ static void TestVersion() { Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); } +static void TestVerifyResult() { + TestSsl ssl {}; + RequireThrows([] { SSL_get_verify_result_nid_postfix(nullptr); }); + Require(SSL_get_verify_result_nid_postfix(&ssl) == 0); + ssl.verifyResult = 20; + Require(SSL_get_verify_result_nid_postfix(&ssl) == 20); + ssl.verifyResult = INT64_C(0x100000002); + Require(SSL_get_verify_result_nid_postfix(&ssl) == INT64_C(0x100000002)); + ssl.verifyResult = -1; + Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); TestVersion(); + TestVerifyResult(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index d8ab0856bc..cf6c45ba17 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 341 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 340 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -11,7 +11,7 @@ - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 6c7c53cf7b85ff5af215c1d2f0436ab7a0be12b1 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:57 +0200 Subject: [PATCH 014/612] feat(libSceLibreSsl): implement SSL_get_shutdown --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 16 ++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index e4704f7fb5..0e37122be4 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1273,11 +1273,6 @@ int APS5_VABI SSL_get_session_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_shutdown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 89c984e2d6..cb226a9590 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -91,4 +91,8 @@ std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).verifyResult; } +int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSsl* ssl) { + return Internal(ssl, __func__).shutdown; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 2d1a43528c..2bca9bdbdb 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -39,6 +39,7 @@ void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); +int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -109,11 +110,26 @@ static void TestVerifyResult() { Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); } +static void TestGetShutdown() { + TestSslInternal internal {}; + TestSsl ssl {}; + RequireThrows([] { SSL_get_shutdown_nid_postfix(nullptr); }); + RequireThrows([&] { SSL_get_shutdown_nid_postfix(&ssl); }); + ssl.internal = &internal; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 0); + internal.rwstate = 3; + internal.shutdown = 1; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 1); + internal.shutdown = 3; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); TestVersion(); TestVerifyResult(); + TestGetShutdown(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index cf6c45ba17..86461acaa4 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 340 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 339 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -11,7 +11,7 @@ - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 2dd55fc6b4c04011c9e1d3d9c1cd4bcd4be630cc Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:55:07 +0200 Subject: [PATCH 015/612] feat(libSceLibreSsl): implement SSL_want --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 16 ++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 0e37122be4..45f3b5480c 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1358,11 +1358,6 @@ int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void) { return 0; } -int APS5_VABI SSL_want_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_write_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index cb226a9590..69780d7342 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -95,4 +95,8 @@ int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSsl* ssl) { return Internal(ssl, __func__).shutdown; } +int APS5_VABI SSL_want_nid_postfix(const LibreSsl* ssl) { + return Internal(ssl, __func__).rwstate; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 2bca9bdbdb..04abd2ad93 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -40,6 +40,7 @@ void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); +int APS5_VABI SSL_want_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -124,6 +125,20 @@ static void TestGetShutdown() { Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); } +static void TestWant() { + TestSslInternal internal {}; + TestSsl ssl {}; + RequireThrows([] { SSL_want_nid_postfix(nullptr); }); + RequireThrows([&] { SSL_want_nid_postfix(&ssl); }); + ssl.internal = &internal; + Require(SSL_want_nid_postfix(&ssl) == 0); + internal.rwstate = 1; + Require(SSL_want_nid_postfix(&ssl) == 1); + internal.shutdown = 2; + internal.rwstate = 3; + Require(SSL_want_nid_postfix(&ssl) == 3); +} + int main() { TestGetSslCtx(); TestGetRbio(); @@ -131,5 +146,6 @@ int main() { TestVersion(); TestVerifyResult(); TestGetShutdown(); + TestWant(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 86461acaa4..40bca10423 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 339 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 338 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -11,7 +11,7 @@ - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown, SSL_want](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 1dd032a8474f6ee464b3f30cc5c3385e8b1eac55 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 06:01:21 +0100 Subject: [PATCH 016/612] refactor(libSceLibreSsl): declare the SSL accessor exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 53 +++++++++++++++++--- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 40 +-------------- core/libs/tests/GuestLibreSslSsl.cpp | 53 +++++++++----------- 3 files changed, 71 insertions(+), 75 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index a9bfae70a0..cbc0105ead 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -51,6 +51,45 @@ struct Sha512Context { std::uint32_t num, mdLength; }; +struct LibreSslSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct LibreSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + int server; + void* s3; + void* d1; + void* param; + void* cipherList; + void* cert; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* session; + int verifyMode; + int error; + int errorCode; + void* ctx; + std::int64_t verifyResult; + int references; + int clientVersion; + unsigned int maxSendFragment; + char* tlsextHostname; + int tlsextStatusType; + void* initialCtx; + void* encReadCtx; + void* readHash; + LibreSslSslInternal* internal; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -302,7 +341,7 @@ int APS5_VABI SSL_free_nid_postfix(void); int APS5_VABI SSL_get0_alpn_selected_nid_postfix(void); int APS5_VABI SSL_get0_param_nid_postfix(void); int APS5_VABI SSL_get1_session_nid_postfix(void); -int APS5_VABI SSL_get_SSL_CTX_nid_postfix(void); +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_get_certificate_nid_postfix(void); int APS5_VABI SSL_get_current_cipher_nid_postfix(void); int APS5_VABI SSL_get_error_nid_postfix(void); @@ -313,12 +352,12 @@ int APS5_VABI SSL_get_fd_nid_postfix(void); int APS5_VABI SSL_get_peer_cert_chain_nid_postfix(void); int APS5_VABI SSL_get_peer_certificate_nid_postfix(void); int APS5_VABI SSL_get_privatekey_nid_postfix(void); -int APS5_VABI SSL_get_rbio_nid_postfix(void); +void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_get_servername_nid_postfix(void); int APS5_VABI SSL_get_session_nid_postfix(void); -int APS5_VABI SSL_get_shutdown_nid_postfix(void); -int APS5_VABI SSL_get_verify_result_nid_postfix(void); -int APS5_VABI SSL_get_wbio_nid_postfix(void); +int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSsl* ssl); +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSsl* ssl); +void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_library_init_nid_postfix(void); int APS5_VABI SSL_load_error_strings_nid_postfix(void); int APS5_VABI SSL_new_nid_postfix(void); @@ -336,8 +375,8 @@ int APS5_VABI SSL_set_verify_nid_postfix(void); int APS5_VABI SSL_shutdown_nid_postfix(void); int APS5_VABI SSL_use_PrivateKey_ASN1_nid_postfix(void); int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void); -int APS5_VABI SSL_version_nid_postfix(void); -int APS5_VABI SSL_want_nid_postfix(void); +int APS5_VABI SSL_version_nid_postfix(const LibreSsl* ssl); +int APS5_VABI SSL_want_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_write_nid_postfix(void); int APS5_VABI SSLeay_nid_postfix(void); int APS5_VABI SSLv23_client_method_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 69780d7342..74c3e5aa99 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -1,48 +1,10 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslSslInternal { - unsigned char beforeRwstate[156]; - int rwstate; - unsigned char beforeShutdown[8]; - int shutdown; - unsigned char rest[260]; -}; - -struct LibreSsl { - int version; - const void* method; - void* rbio; - void* wbio; - void* bbio; - int server; - void* s3; - void* d1; - void* param; - void* cipherList; - void* cert; - unsigned int sidCtxLength; - unsigned char sidCtx[32]; - void* session; - int verifyMode; - int error; - int errorCode; - void* ctx; - std::int64_t verifyResult; - int references; - int clientVersion; - unsigned int maxSendFragment; - char* tlsextHostname; - int tlsextStatusType; - void* initialCtx; - void* encReadCtx; - void* readHash; - LibreSslSslInternal* internal; -}; - static_assert(sizeof(LibreSslSslInternal) == 432); static_assert(offsetof(LibreSslSslInternal, rwstate) == 156); static_assert(offsetof(LibreSslSslInternal, shutdown) == 168); diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 04abd2ad93..e2549fab2f 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -33,14 +34,8 @@ static_assert(sizeof(TestSslInternal) == 432 && offsetof(TestSslInternal, rwstat static_assert(sizeof(TestSsl) == 232 && offsetof(TestSsl, session) == 128 && offsetof(TestSsl, ctx) == 152); static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, internal) == 224); -extern "C" { -void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); -void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); -void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); -int APS5_VABI SSL_version_nid_postfix(const TestSsl*); -std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); -int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); -int APS5_VABI SSL_want_nid_postfix(const TestSsl*); +static const LibreSsl* Ssl(const TestSsl* ssl) { + return reinterpret_cast(ssl); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -59,10 +54,10 @@ static void TestGetSslCtx() { int session = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_SSL_CTX_nid_postfix(nullptr); }); - Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == nullptr); + Require(SSL_get_SSL_CTX_nid_postfix(Ssl(&ssl)) == nullptr); ssl.ctx = &context; ssl.session = &session; - Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); + Require(SSL_get_SSL_CTX_nid_postfix(Ssl(&ssl)) == &context); } static void TestGetRbio() { @@ -70,10 +65,10 @@ static void TestGetRbio() { int write = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_rbio_nid_postfix(nullptr); }); - Require(SSL_get_rbio_nid_postfix(&ssl) == nullptr); + Require(SSL_get_rbio_nid_postfix(Ssl(&ssl)) == nullptr); ssl.rbio = &read; ssl.wbio = &write; - Require(SSL_get_rbio_nid_postfix(&ssl) == &read); + Require(SSL_get_rbio_nid_postfix(Ssl(&ssl)) == &read); } static void TestGetWbio() { @@ -82,61 +77,61 @@ static void TestGetWbio() { int buffer = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_wbio_nid_postfix(nullptr); }); - Require(SSL_get_wbio_nid_postfix(&ssl) == nullptr); + Require(SSL_get_wbio_nid_postfix(Ssl(&ssl)) == nullptr); ssl.rbio = &read; ssl.wbio = &write; ssl.bbio = &buffer; - Require(SSL_get_wbio_nid_postfix(&ssl) == &write); + Require(SSL_get_wbio_nid_postfix(Ssl(&ssl)) == &write); } static void TestVersion() { TestSsl ssl {}; RequireThrows([] { SSL_version_nid_postfix(nullptr); }); - Require(SSL_version_nid_postfix(&ssl) == 0); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0); ssl.version = 0x0303; - Require(SSL_version_nid_postfix(&ssl) == 0x0303); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0x0303); ssl.version = 0xFEFF; - Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0xFEFF); } static void TestVerifyResult() { TestSsl ssl {}; RequireThrows([] { SSL_get_verify_result_nid_postfix(nullptr); }); - Require(SSL_get_verify_result_nid_postfix(&ssl) == 0); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == 0); ssl.verifyResult = 20; - Require(SSL_get_verify_result_nid_postfix(&ssl) == 20); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == 20); ssl.verifyResult = INT64_C(0x100000002); - Require(SSL_get_verify_result_nid_postfix(&ssl) == INT64_C(0x100000002)); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == INT64_C(0x100000002)); ssl.verifyResult = -1; - Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == -1); } static void TestGetShutdown() { TestSslInternal internal {}; TestSsl ssl {}; RequireThrows([] { SSL_get_shutdown_nid_postfix(nullptr); }); - RequireThrows([&] { SSL_get_shutdown_nid_postfix(&ssl); }); + RequireThrows([&] { SSL_get_shutdown_nid_postfix(Ssl(&ssl)); }); ssl.internal = &internal; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 0); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 0); internal.rwstate = 3; internal.shutdown = 1; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 1); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 1); internal.shutdown = 3; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 3); } static void TestWant() { TestSslInternal internal {}; TestSsl ssl {}; RequireThrows([] { SSL_want_nid_postfix(nullptr); }); - RequireThrows([&] { SSL_want_nid_postfix(&ssl); }); + RequireThrows([&] { SSL_want_nid_postfix(Ssl(&ssl)); }); ssl.internal = &internal; - Require(SSL_want_nid_postfix(&ssl) == 0); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 0); internal.rwstate = 1; - Require(SSL_want_nid_postfix(&ssl) == 1); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 1); internal.shutdown = 2; internal.rwstate = 3; - Require(SSL_want_nid_postfix(&ssl) == 3); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 3); } int main() { From 79f0bc05eb13c033b928aacf4ec75197af4b1179 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:00:27 +0200 Subject: [PATCH 017/612] feat(libSceLibreSsl): implement SSL_CTX_get_verify_mode --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 ++++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 53 ++++++++++++++++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 60 +++++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 3 +- 5 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/SslCtx.cpp create mode 100644 core/libs/tests/GuestLibreSslSslCtx.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 20e7b85ba9..108b5a4291 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SslCtx.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -77,6 +77,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_error PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_ssl_ctx_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSslCtx.cpp) + target_include_directories(guest_libressl_ssl_ctx_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_ctx_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_ctx_tests) + add_test(NAME guest_libressl_ssl_ctx COMMAND guest_libressl_ssl_ctx_tests) + set_tests_properties(guest_libressl_ssl_ctx PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl_ctx PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index dfbd9c146d..2e1a1eee45 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1023,11 +1023,6 @@ int APS5_VABI SSL_CTX_get_ex_new_index_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_load_verify_locations_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp new file mode 100644 index 0000000000..f4566a99a9 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct LibreSslCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + int references; + void* extraCerts; + int verifyMode; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* param; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + LibreSslCtxInternal* internal; +}; + +static_assert(sizeof(LibreSslCtxInternal) == 464); +static_assert(offsetof(LibreSslCtxInternal, quietShutdown) == 292); +static_assert(sizeof(LibreSslCtx) == 120); +static_assert(offsetof(LibreSslCtx, certStore) == 16); +static_assert(offsetof(LibreSslCtx, verifyMode) == 48); +static_assert(offsetof(LibreSslCtx, defaultPasswdCallback) == 96); +static_assert(offsetof(LibreSslCtx, defaultPasswdCallbackUserdata) == 104); +static_assert(offsetof(LibreSslCtx, internal) == 112); + +namespace { + +LibreSslCtx& Context(LibreSslCtx* ctx, const char* function) { + if (!ctx) throw std::invalid_argument(std::string(function) + ": null context"); + return *ctx; +} + +} + +extern "C" { + +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx) { + return Context(ctx, __func__).verifyMode; +} + +} diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp new file mode 100644 index 0000000000..acbcf82c94 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -0,0 +1,60 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +struct TestCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct TestCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + unsigned char beforeVerifyMode[16]; + int verifyMode; + unsigned char beforeCallback[44]; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + TestCtxInternal* internal; +}; + +static_assert(sizeof(TestCtxInternal) == 464 && offsetof(TestCtxInternal, quietShutdown) == 292); +static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && offsetof(TestCtx, defaultPasswdCallback) == 96); + +extern "C" { +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL_CTX check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetVerifyMode() { + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_get_verify_mode_nid_postfix(nullptr); }); + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0); + ctx.verifyMode = 0x03; + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0x03); + ctx.verifyMode = -1; + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); +} + +int main() { + TestGetVerifyMode(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index e9885b8324..885fc3323b 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 345 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 344 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -11,6 +11,7 @@ - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. +- [SSL_CTX_get_verify_mode](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach it, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 747017dd572302284ffccb19fb3eb793ce6a6e08 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:22 +0200 Subject: [PATCH 018/612] feat(libSceLibreSsl): implement SSL_CTX_get_cert_store --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 2e1a1eee45..1558d88716 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1008,11 +1008,6 @@ int APS5_VABI SSL_CTX_free_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_get_cert_store_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index f4566a99a9..446eff72de 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -50,4 +50,8 @@ int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx) { return Context(ctx, __func__).verifyMode; } +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx) { + return Context(ctx, __func__).certStore; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index acbcf82c94..df6c8d1c3d 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -31,6 +31,7 @@ static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && o extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -54,7 +55,20 @@ static void TestGetVerifyMode() { Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); } +static void TestGetCertStore() { + int store = 0; + int ciphers = 0; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_get_cert_store_nid_postfix(nullptr); }); + Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == nullptr); + ctx.cipherList = &ciphers; + ctx.certStore = &store; + ctx.sessionTimeout = -1; + Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); +} + int main() { TestGetVerifyMode(); + TestGetCertStore(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 885fc3323b..138a2475e5 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 344 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 343 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -11,7 +11,7 @@ - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_CTX_get_verify_mode](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach it, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From f163179bf980b688ad91968e3fe081373aea1f6d Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:31 +0200 Subject: [PATCH 019/612] feat(libSceLibreSsl): implement SSL_CTX_set_quiet_shutdown --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 6 ++++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 18 ++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 1558d88716..bdcd9a8ed3 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1103,11 +1103,6 @@ int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 446eff72de..2a86c75d25 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -54,4 +54,10 @@ void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx) { return Context(ctx, __func__).certStore; } +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode) { + LibreSslCtx& context = Context(ctx, __func__); + if (!context.internal) throw std::invalid_argument(std::string(__func__) + ": null internal state"); + context.internal->quietShutdown = mode; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index df6c8d1c3d..38f3bb5fad 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -32,6 +32,7 @@ static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && o extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -67,8 +68,25 @@ static void TestGetCertStore() { Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); } +static void TestSetQuietShutdown() { + TestCtxInternal internal {}; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_set_quiet_shutdown_nid_postfix(nullptr, 1); }); + RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); }); + ctx.internal = &internal; + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); + Require(internal.quietShutdown == 1); + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, INT_MIN); + Require(internal.quietShutdown == INT_MIN); + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 0); + Require(internal.quietShutdown == 0); + for (unsigned char byte : internal.beforeQuietShutdown) Require(byte == 0); + for (unsigned char byte : internal.rest) Require(byte == 0); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); + TestSetQuietShutdown(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 138a2475e5..4f19883c06 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 343 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 342 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -11,7 +11,7 @@ - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 06d165f52ebde2919fb4f2d1ae95e5875ecf488f Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:41 +0200 Subject: [PATCH 020/612] feat(libSceLibreSsl): implement SSL_CTX_set_default_passwd_cb --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index bdcd9a8ed3..eff4f3d264 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1073,11 +1073,6 @@ int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 2a86c75d25..573dbf8978 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -60,4 +60,8 @@ void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode context.internal->quietShutdown = mode; } +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* callback) { + Context(ctx, __func__).defaultPasswdCallback = callback; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index 38f3bb5fad..613aed964a 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -33,6 +33,7 @@ extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -84,9 +85,20 @@ static void TestSetQuietShutdown() { for (unsigned char byte : internal.rest) Require(byte == 0); } +static void TestSetPasswordCallback() { + int callback = 0; + TestCtx ctx {}; + RequireThrows([&] { SSL_CTX_set_default_passwd_cb_nid_postfix(nullptr, &callback); }); + SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, &callback); + Require(ctx.defaultPasswdCallback == &callback && ctx.defaultPasswdCallbackUserdata == nullptr); + SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, nullptr); + Require(ctx.defaultPasswdCallback == nullptr); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); TestSetQuietShutdown(); + TestSetPasswordCallback(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 4f19883c06..4bb659564c 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 342 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 341 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -11,7 +11,7 @@ - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 3a694b299610d98f6a28085d197dfbfc3db0db55 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:51 +0200 Subject: [PATCH 021/612] feat(libSceLibreSsl): implement SSL_CTX_set_default_passwd_cb_userdata --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index eff4f3d264..4947746168 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1073,11 +1073,6 @@ int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 573dbf8978..b70a0d9f12 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -64,4 +64,8 @@ void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* Context(ctx, __func__).defaultPasswdCallback = callback; } +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* ctx, void* userdata) { + Context(ctx, __func__).defaultPasswdCallbackUserdata = userdata; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index 613aed964a..fe76a282de 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -34,6 +34,7 @@ int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(TestCtx*, void*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -95,10 +96,21 @@ static void TestSetPasswordCallback() { Require(ctx.defaultPasswdCallback == nullptr); } +static void TestSetPasswordUserdata() { + int userdata = 0; + TestCtx ctx {}; + RequireThrows([&] { SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(nullptr, &userdata); }); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, &userdata); + Require(ctx.defaultPasswdCallbackUserdata == &userdata && ctx.defaultPasswdCallback == nullptr && ctx.internal == nullptr); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, nullptr); + Require(ctx.defaultPasswdCallbackUserdata == nullptr); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); TestSetQuietShutdown(); TestSetPasswordCallback(); + TestSetPasswordUserdata(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 4bb659564c..bd7c15130e 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 341 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 340 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -11,7 +11,7 @@ - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb, SSL_CTX_set_default_passwd_cb_userdata](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 7bb8f74bb044be414aaad0aa8cf9b8502ce22cfc Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 05:59:27 +0100 Subject: [PATCH 022/612] refactor(libSceLibreSsl): declare the SSL_CTX accessor exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 32 +++++++++++++++--- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 23 +------------ core/libs/tests/GuestLibreSslSslCtx.cpp | 35 +++++++++----------- 3 files changed, 44 insertions(+), 46 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index a9bfae70a0..57d13e2f83 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -51,6 +51,28 @@ struct Sha512Context { std::uint32_t num, mdLength; }; +struct LibreSslCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct LibreSslCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + int references; + void* extraCerts; + int verifyMode; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* param; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + LibreSslCtxInternal* internal; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -261,10 +283,10 @@ int APS5_VABI SSL_CTX_callback_ctrl_nid_postfix(void); int APS5_VABI SSL_CTX_check_private_key_nid_postfix(void); int APS5_VABI SSL_CTX_ctrl_nid_postfix(void); int APS5_VABI SSL_CTX_free_nid_postfix(void); -int APS5_VABI SSL_CTX_get_cert_store_nid_postfix(void); +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx); int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void); int APS5_VABI SSL_CTX_get_ex_new_index_nid_postfix(void); -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void); +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx); int APS5_VABI SSL_CTX_load_verify_locations_nid_postfix(void); int APS5_VABI SSL_CTX_load_verify_mem_nid_postfix(void); int APS5_VABI SSL_CTX_new_nid_postfix(void); @@ -276,13 +298,13 @@ int APS5_VABI SSL_CTX_set_cert_store_nid_postfix(void); int APS5_VABI SSL_CTX_set_cert_verify_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_cipher_list_nid_postfix(void); int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void); +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* callback); +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* ctx, void* userdata); int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void); int APS5_VABI SSL_CTX_set_info_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_msg_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(void); +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode); int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void); int APS5_VABI SSL_CTX_set_verify_nid_postfix(void); int APS5_VABI SSL_CTX_use_PrivateKey_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index b70a0d9f12..99eb4fa5a1 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -1,31 +1,10 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslCtxInternal { - unsigned char beforeQuietShutdown[292]; - int quietShutdown; - unsigned char rest[168]; -}; - -struct LibreSslCtx { - const void* method; - void* cipherList; - void* certStore; - std::int64_t sessionTimeout; - int references; - void* extraCerts; - int verifyMode; - unsigned int sidCtxLength; - unsigned char sidCtx[32]; - void* param; - void* defaultPasswdCallback; - void* defaultPasswdCallbackUserdata; - LibreSslCtxInternal* internal; -}; - static_assert(sizeof(LibreSslCtxInternal) == 464); static_assert(offsetof(LibreSslCtxInternal, quietShutdown) == 292); static_assert(sizeof(LibreSslCtx) == 120); diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index fe76a282de..3e77a25e02 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -29,12 +30,8 @@ struct TestCtx { static_assert(sizeof(TestCtxInternal) == 464 && offsetof(TestCtxInternal, quietShutdown) == 292); static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && offsetof(TestCtx, defaultPasswdCallback) == 96); -extern "C" { -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); -void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); -void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); -void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); -void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(TestCtx*, void*); +static LibreSslCtx* Context(TestCtx* ctx) { + return reinterpret_cast(ctx); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -51,11 +48,11 @@ template static void RequireThrows(TAction action, std::source static void TestGetVerifyMode() { TestCtx ctx {}; RequireThrows([] { SSL_CTX_get_verify_mode_nid_postfix(nullptr); }); - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == 0); ctx.verifyMode = 0x03; - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0x03); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == 0x03); ctx.verifyMode = -1; - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == -1); } static void TestGetCertStore() { @@ -63,24 +60,24 @@ static void TestGetCertStore() { int ciphers = 0; TestCtx ctx {}; RequireThrows([] { SSL_CTX_get_cert_store_nid_postfix(nullptr); }); - Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == nullptr); + Require(SSL_CTX_get_cert_store_nid_postfix(Context(&ctx)) == nullptr); ctx.cipherList = &ciphers; ctx.certStore = &store; ctx.sessionTimeout = -1; - Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); + Require(SSL_CTX_get_cert_store_nid_postfix(Context(&ctx)) == &store); } static void TestSetQuietShutdown() { TestCtxInternal internal {}; TestCtx ctx {}; RequireThrows([] { SSL_CTX_set_quiet_shutdown_nid_postfix(nullptr, 1); }); - RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); }); + RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 1); }); ctx.internal = &internal; - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 1); Require(internal.quietShutdown == 1); - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, INT_MIN); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), INT_MIN); Require(internal.quietShutdown == INT_MIN); - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 0); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 0); Require(internal.quietShutdown == 0); for (unsigned char byte : internal.beforeQuietShutdown) Require(byte == 0); for (unsigned char byte : internal.rest) Require(byte == 0); @@ -90,9 +87,9 @@ static void TestSetPasswordCallback() { int callback = 0; TestCtx ctx {}; RequireThrows([&] { SSL_CTX_set_default_passwd_cb_nid_postfix(nullptr, &callback); }); - SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, &callback); + SSL_CTX_set_default_passwd_cb_nid_postfix(Context(&ctx), &callback); Require(ctx.defaultPasswdCallback == &callback && ctx.defaultPasswdCallbackUserdata == nullptr); - SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, nullptr); + SSL_CTX_set_default_passwd_cb_nid_postfix(Context(&ctx), nullptr); Require(ctx.defaultPasswdCallback == nullptr); } @@ -100,9 +97,9 @@ static void TestSetPasswordUserdata() { int userdata = 0; TestCtx ctx {}; RequireThrows([&] { SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(nullptr, &userdata); }); - SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, &userdata); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(Context(&ctx), &userdata); Require(ctx.defaultPasswdCallbackUserdata == &userdata && ctx.defaultPasswdCallback == nullptr && ctx.internal == nullptr); - SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, nullptr); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(Context(&ctx), nullptr); Require(ctx.defaultPasswdCallbackUserdata == nullptr); } From 29cfd3a502cff52a33ce5d13665b8e7f4e8aa7fc Mon Sep 17 00:00:00 2001 From: Ahmad Mujahid Date: Sun, 25 Oct 2026 22:38:26 +0400 Subject: [PATCH 023/612] feat(libSceLibreSsl): implement X509_NAME_get_index_by_NID and X509_NAME_get_text_by_NID --- core/libs/prx/libSceLibreSsl/Export.cpp | 10 -- core/libs/prx/libSceLibreSsl/include/Api.hpp | 4 +- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 137 +++++++++++++++++ core/libs/tests/GuestLibreSslX509Name.cpp | 141 ++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 3 +- 5 files changed, 282 insertions(+), 13 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 288b70634d..fbd7050c4d 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1523,16 +1523,6 @@ int APS5_VABI X509_LOOKUP_file_nid_postfix(void) { return 0; } -int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_NAME_oneline_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 667c40b44a..2f22d5d778 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -389,8 +389,8 @@ int APS5_VABI X509_INFO_free_nid_postfix(void); int APS5_VABI X509_LOOKUP_file_nid_postfix(void); void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry* entry); LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc); -int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void); -int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(void); +int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(const LibreSslX509Name* name, int nid, int lastpos); +int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(const LibreSslX509Name* name, int nid, char* buf, int len); int APS5_VABI X509_NAME_oneline_nid_postfix(void); int APS5_VABI X509_NAME_print_ex_nid_postfix(void); int APS5_VABI X509_STORE_CTX_get1_chain_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp index f68342f636..42dff3eb5c 100644 --- a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -1,5 +1,7 @@ #include "prx/libSceLibreSsl/include/Api.hpp" +#include #include +#include #include #include #include "prx/libc/include/General.hpp" @@ -11,6 +13,115 @@ static_assert(offsetof(LibreSslX509NameEntry, value) == 8); static_assert(sizeof(LibreSslX509Name) == 40); static_assert(offsetof(LibreSslX509Name, entries) == 0); +namespace { + +struct Asn1Object { + const char* shortName; + const char* longName; + int nid; + int length; + const unsigned char* data; + int flags; +}; + +static_assert(sizeof(Asn1Object) == 40 && offsetof(Asn1Object, length) == 20 && offsetof(Asn1Object, data) == 24); + +struct NameAttribute { + int nid; + int length; + unsigned char der[10]; +}; + +constexpr int NidCount = 956; + +constexpr NameAttribute NameAttributes[] { + {0, 0, {}}, + {13, 3, {0x55, 0x04, 0x03}}, + {14, 3, {0x55, 0x04, 0x06}}, + {15, 3, {0x55, 0x04, 0x07}}, + {16, 3, {0x55, 0x04, 0x08}}, + {17, 3, {0x55, 0x04, 0x0a}}, + {18, 3, {0x55, 0x04, 0x0b}}, + {48, 9, {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01}}, + {99, 3, {0x55, 0x04, 0x2a}}, + {100, 3, {0x55, 0x04, 0x04}}, + {101, 3, {0x55, 0x04, 0x2b}}, + {105, 3, {0x55, 0x04, 0x05}}, + {106, 3, {0x55, 0x04, 0x0c}}, + {107, 3, {0x55, 0x04, 0x0d}}, + {173, 3, {0x55, 0x04, 0x29}}, + {174, 3, {0x55, 0x04, 0x2e}}, + {391, 10, {0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19}}, + {400, 3, {0x55, 0x04, 0x48}}, + {458, 10, {0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x01}}, + {503, 3, {0x55, 0x04, 0x2d}}, + {509, 3, {0x55, 0x04, 0x2c}}, + {510, 3, {0x55, 0x04, 0x41}}, + {660, 3, {0x55, 0x04, 0x09}}, + {661, 3, {0x55, 0x04, 0x11}}, + {859, 3, {0x55, 0x04, 0x0e}}, + {860, 3, {0x55, 0x04, 0x0f}}, + {861, 3, {0x55, 0x04, 0x10}}, + {862, 3, {0x55, 0x04, 0x12}}, + {863, 3, {0x55, 0x04, 0x13}}, + {864, 3, {0x55, 0x04, 0x14}}, + {865, 3, {0x55, 0x04, 0x15}}, + {866, 3, {0x55, 0x04, 0x16}}, + {867, 3, {0x55, 0x04, 0x17}}, + {868, 3, {0x55, 0x04, 0x18}}, + {869, 3, {0x55, 0x04, 0x19}}, + {870, 3, {0x55, 0x04, 0x1a}}, + {871, 3, {0x55, 0x04, 0x1b}}, + {872, 3, {0x55, 0x04, 0x1c}}, + {873, 3, {0x55, 0x04, 0x1d}}, + {874, 3, {0x55, 0x04, 0x1e}}, + {875, 3, {0x55, 0x04, 0x1f}}, + {876, 3, {0x55, 0x04, 0x20}}, + {877, 3, {0x55, 0x04, 0x21}}, + {878, 3, {0x55, 0x04, 0x22}}, + {879, 3, {0x55, 0x04, 0x23}}, + {880, 3, {0x55, 0x04, 0x24}}, + {881, 3, {0x55, 0x04, 0x25}}, + {882, 3, {0x55, 0x04, 0x26}}, + {883, 3, {0x55, 0x04, 0x27}}, + {884, 3, {0x55, 0x04, 0x28}}, + {885, 3, {0x55, 0x04, 0x2f}}, + {886, 3, {0x55, 0x04, 0x30}}, + {887, 3, {0x55, 0x04, 0x31}}, + {888, 3, {0x55, 0x04, 0x32}}, + {889, 3, {0x55, 0x04, 0x33}}, + {890, 3, {0x55, 0x04, 0x34}}, + {891, 3, {0x55, 0x04, 0x35}}, + {892, 3, {0x55, 0x04, 0x36}}, +}; + +const NameAttribute* FindAttribute(int nid, const char* function) { + if (nid < 0 || nid >= NidCount) return nullptr; + for (const auto& attribute : NameAttributes) { + if (attribute.nid == nid) return &attribute; + } + NotImplemented_nid_no_patch((std::string(function) + ": NID " + std::to_string(nid) + " is not a name attribute type in the object identifier subset").c_str()); +} + +int IndexOf(const LibreSslX509Name* name, const NameAttribute& attribute, int lastpos, const char* function) { + if (!name) return -1; + if (lastpos == INT_MAX) throw std::invalid_argument(std::string(function) + ": last position overflows"); + if (lastpos < 0) lastpos = -1; + const int count = name->entries ? name->entries->num : -1; + for (++lastpos; lastpos < count; ++lastpos) { + const auto* entry = X509_NAME_get_entry_nid_postfix(name, lastpos); + if (!entry || !entry->object) throw std::invalid_argument(std::string(function) + ": null name entry or object"); + const auto* object = static_cast(entry->object); + if (object->length != attribute.length) continue; + if (attribute.length == 0) return lastpos; + if (!object->data) throw std::invalid_argument(std::string(function) + ": null object data"); + if (std::memcmp(object->data, attribute.der, attribute.length) == 0) return lastpos; + } + return -1; +} + +} + extern "C" { LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc) { @@ -24,4 +135,30 @@ void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry return entry->value; } +int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(const LibreSslX509Name* name, int nid, int lastpos) { + const auto* attribute = FindAttribute(nid, __func__); + if (!attribute) return -2; + return IndexOf(name, *attribute, lastpos, __func__); +} + +int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(const LibreSslX509Name* name, int nid, char* buf, int len) { + const auto* attribute = FindAttribute(nid, __func__); + if (!attribute) return -1; + const int index = IndexOf(name, *attribute, -1, __func__); + if (index < 0) return -1; + const auto* data = static_cast(X509_NAME_ENTRY_get_data_nid_postfix(X509_NAME_get_entry_nid_postfix(name, index))); + if (!data) throw std::invalid_argument(std::string(__func__) + ": null entry value"); + if (len == INT_MIN) throw std::invalid_argument(std::string(__func__) + ": buffer length overflows"); + const int count = data->length > len - 1 ? len - 1 : data->length; + if (!buf) return data->length; + if (count >= 0) { + if (count) { + if (!data->data) throw std::invalid_argument(std::string(__func__) + ": null entry string"); + std::memcpy(buf, data->data, count); + } + buf[count] = '\0'; + } + return count; +} + } diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp index 365b16002e..c9abef8c7c 100644 --- a/core/libs/tests/GuestLibreSslX509Name.cpp +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -30,10 +31,21 @@ struct TestName { int canonEnclen; }; +struct TestObject { + const char* shortName; + const char* longName; + int nid; + int length; + const unsigned char* data; + int flags; +}; + static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); static_assert(sizeof(TestNameEntry) == 24 && offsetof(TestNameEntry, value) == 8); static_assert(sizeof(TestName) == 40); +static_assert(sizeof(TestObject) == 40 && offsetof(TestObject, length) == 20 && offsetof(TestObject, data) == 24); + static const LibreSslX509Name* Name(const TestName* name) { return reinterpret_cast(name); } @@ -90,8 +102,137 @@ static void TestEntryGetData() { Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == &value); } +static const unsigned char CountryOid[] {0x55, 0x04, 0x06}; +static const unsigned char OrganizationOid[] {0x55, 0x04, 0x0a}; +static const unsigned char CommonNameOid[] {0x55, 0x04, 0x03}; +static const unsigned char UnitOid[] {0x55, 0x04, 0x0b}; +static const unsigned char EmailOid[] {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01}; +static const unsigned char PrivateOid[] {0x2a, 0x03, 0x04}; +static const unsigned char LocalityOid[] {0x55, 0x04, 0x07}; +static const unsigned char UnstructuredNameOid[] {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x02}; + +struct DecodedName { + TestObject objects[8]; + Asn1String values[8]; + TestNameEntry entries[8]; + char* slots[8]; + TestStack stack; + TestName name; + + DecodedName() { + const struct { const unsigned char* oid; int oidLength; const char* text; int textLength; } fields[] { + {CountryOid, 3, "US", 2}, + {OrganizationOid, 3, "Example", 7}, + {CommonNameOid, 3, "first", 5}, + {UnitOid, 3, "Unit", 4}, + {CommonNameOid, 3, "second", 6}, + {EmailOid, 9, "a@b.c", 5}, + {PrivateOid, 3, "x", 1}, + {LocalityOid, 3, "a\0b", 3}, + }; + for (int index = 0; index < 8; ++index) { + objects[index] = {nullptr, nullptr, 0, fields[index].oidLength, fields[index].oid, 0}; + values[index] = {fields[index].textLength, 12, reinterpret_cast(const_cast(fields[index].text)), 0}; + entries[index] = {&objects[index], &values[index], index, 0}; + slots[index] = reinterpret_cast(&entries[index]); + } + stack = {8, slots, 0, 8, nullptr}; + name = {&stack, 0, nullptr, nullptr, 0}; + } + + DecodedName(const DecodedName&) = delete; +}; + +static void TestIndexByNid() { + DecodedName decoded; + const auto* name = Name(&decoded.name); + const int commonName[][2] {{-1, 2}, {-5, 2}, {INT_MIN, 2}, {0, 2}, {2, 4}, {4, -1}, {7, -1}, {8, -1}, {100, -1}}; + for (const auto& [lastpos, index] : commonName) Require(X509_NAME_get_index_by_NID_nid_postfix(name, 13, lastpos) == index); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 14, -1) == 0); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 14, 0) == -1); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 48, -1) == 5); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 48, 4) == 5); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 48, 7) == -1); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 15, -1) == 7); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 15, 7) == -1); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 16, -1) == -1); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 0, -1) == -1); + for (int nid : {-1, 956, INT_MIN, INT_MAX}) Require(X509_NAME_get_index_by_NID_nid_postfix(name, nid, -1) == -2); + Require(X509_NAME_get_index_by_NID_nid_postfix(nullptr, 13, -1) == -1); + Require(X509_NAME_get_index_by_NID_nid_postfix(nullptr, -1, -1) == -2); + decoded.objects[6].nid = 13; + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 13, 4) == -1); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 458, -1) == -1); + decoded.objects[4] = {nullptr, nullptr, 0, 9, UnstructuredNameOid, 0}; + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 48, -1) == 5); + decoded.objects[6] = {nullptr, nullptr, 0, 0, nullptr, 0}; + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 0, -1) == 6); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 0, 6) == -1); + TestStack empty {0, nullptr, 0, 0, nullptr}; + TestName emptyName {&empty, 0, nullptr, nullptr, 0}; + TestName noEntries {}; + Require(X509_NAME_get_index_by_NID_nid_postfix(Name(&emptyName), 13, -1) == -1); + Require(X509_NAME_get_index_by_NID_nid_postfix(Name(&noEntries), 13, -1) == -1); + RequireThrows([&] { X509_NAME_get_index_by_NID_nid_postfix(name, 102, -1); }); + RequireThrows([&] { X509_NAME_get_index_by_NID_nid_postfix(name, 6, -1); }); + RequireThrows([&] { X509_NAME_get_index_by_NID_nid_postfix(name, 13, INT_MAX); }); + decoded.objects[2].data = nullptr; + RequireThrows([&] { X509_NAME_get_index_by_NID_nid_postfix(name, 13, -1); }); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 14, -1) == 0); + decoded.entries[1].object = nullptr; + RequireThrows([&] { X509_NAME_get_index_by_NID_nid_postfix(name, 13, -1); }); + decoded.slots[1] = nullptr; + RequireThrows([&] { X509_NAME_get_index_by_NID_nid_postfix(name, 13, -1); }); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 14, -1) == 0); +} + +static bool BufferHolds(const char* buffer, const char* prefix, std::size_t length) { + if (std::memcmp(buffer, prefix, length) != 0) return false; + for (std::size_t index = length; index < 64; ++index) { + if (buffer[index] != 0x7f) return false; + } + return true; +} + +static void TestTextByNid() { + DecodedName decoded; + const auto* name = Name(&decoded.name); + char buffer[64]; + const struct { int len; int result; const char* prefix; std::size_t length; } cases[] { + {64, 5, "first", 6}, {6, 5, "first", 6}, {5, 4, "firs", 5}, {3, 2, "fi", 3}, {2, 1, "f", 2}, {1, 0, "", 1}, + {0, -1, "", 0}, {-5, -6, "", 0}, + }; + for (const auto& item : cases) { + std::memset(buffer, 0x7f, sizeof(buffer)); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 13, buffer, item.len) == item.result); + Require(BufferHolds(buffer, item.prefix, item.length)); + } + std::memset(buffer, 0x7f, sizeof(buffer)); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 13, nullptr, 64) == 5); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 13, nullptr, 0) == 5); + for (int nid : {16, -1, 956}) Require(X509_NAME_get_text_by_NID_nid_postfix(name, nid, buffer, 64) == -1); + Require(X509_NAME_get_text_by_NID_nid_postfix(nullptr, 13, buffer, 64) == -1); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 16, buffer, INT_MIN) == -1); + Require(BufferHolds(buffer, "", 0)); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 15, buffer, 64) == 3); + Require(BufferHolds(buffer, "a\0b", 4)); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 48, buffer, 64) == 5); + Require(BufferHolds(buffer, "a@b.c", 6)); + RequireThrows([&] { X509_NAME_get_text_by_NID_nid_postfix(name, 13, buffer, INT_MIN); }); + RequireThrows([&] { X509_NAME_get_text_by_NID_nid_postfix(name, 6, buffer, 64); }); + decoded.values[2].data = nullptr; + RequireThrows([&] { X509_NAME_get_text_by_NID_nid_postfix(name, 13, buffer, 64); }); + std::memset(buffer, 0x7f, sizeof(buffer)); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 13, buffer, 1) == 0); + Require(BufferHolds(buffer, "", 1)); + decoded.entries[2].value = nullptr; + RequireThrows([&] { X509_NAME_get_text_by_NID_nid_postfix(name, 13, buffer, 64); }); +} + int main() { TestGetEntry(); TestEntryGetData(); + TestIndexByNid(); + TestTextByNid(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index d648cd352a..ec80101da2 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,10 +2,11 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 343 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 341 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. +- [X509_NAME_get_index_by_NID, X509_NAME_get_text_by_NID](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) - no console call is verified. Only `NID_undef`, the X.520 (2.5.4.x) attribute types, emailAddress, domainComponent and userId have OID bytes, from LibreSSL 2.5.4's [objects.txt](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/objects.txt). Other NIDs from 0 to 955 throw; for an unassigned one LibreSSL returns -2 or -1 and queues `OBJ_R_UNKNOWN_NID`. NIDs outside that range return -2 or -1, as [obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c) does while `OBJ_create`, which throws, has added no object. A `lastpos` of `INT_MAX` and a matched `len` of `INT_MIN` throw: [x509name.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509name.c) overflows a signed int there. - [RAND_add, RAND_bytes, RAND_status, RAND_load_file, RAND_file_name, CRYPTO_malloc, CRYPTO_free, CRYPTO_set_mem_functions](../../../core/libs/prx/libSceLibreSsl/src/Runtime.cpp) (libSceLibreSsl) - follow LibreSSL's [rand_lib.c, randfile.c](https://github.com/libressl/openbsd/tree/0f0b16dc977f6ca499f0f4540078d992c96da597/src/lib/libcrypto/rand) and [malloc-wrapper.c](https://github.com/libressl/openbsd/blob/0f0b16dc977f6ca499f0f4540078d992c96da597/src/lib/libcrypto/malloc-wrapper.c), which are the same in 2.6.5 (3.4.3 linked); nothing is checked on the console. `RAND_bytes` reads host entropy and `CRYPTO_malloc` and `CRYPTO_free` use the guest heap. `CRYPTO_malloc` returns null for a size of zero or less, as LibreSSL 2.5.4 to 3.4 do; later versions take a `size_t` size and call `malloc`. A null buffer with a positive length throws - [AES_set_encrypt_key, AES_set_decrypt_key, AES_encrypt, AES_decrypt, AES_ecb_encrypt, AES_cbc_encrypt, AES_cfb128_encrypt, AES_ctr128_encrypt](../../../core/libs/prx/libSceLibreSsl/src/Aes.cpp) (libSceLibreSsl) - follow LibreSSL's [aes.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/aes/aes.h) and [mode code](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/modes). Nothing is checked on the console. Key setup follows [LibreSSL 2.5.4's aes_core.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/aes_core.c), the version the `X509_verify_cert_error_string` entry names: a null pointer returns -1 and a key length other than 128, 192 or 256 bits returns -2 before the key is written, and only the 44, 52 or 60 words the key length needs are written. The `AES_KEY` schedule uses that file's generic C layout (big-endian words, `rounds` 10, 12 or 14). The 2.5.4 amd64 build uses [aes-x86_64](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/asm/aes-x86_64.pl) instead, which copies the key bytes without swapping, so each word is byte-reversed compared with the C layout (OpenSSL 3.0.13's x86-64 code gives the same schedules byte-reversed); the console's build options are unknown, so a schedule the title reads or fills is unverified. CTR leaves the last keystream block in `ecount_buf` even at a block boundary. Null pointers in the cipher and mode functions, other `rounds` values and a CFB or CTR position outside 0-15 throw - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names From 00846cdbfa80d5bdfdbbb8afcfa9754d585f00ee Mon Sep 17 00:00:00 2001 From: Ahmad Mujahid Date: Sun, 25 Oct 2026 23:21:47 +0400 Subject: [PATCH 024/612] feat(libSceLibreSsl): implement SSL_CTX_ctrl, SSL_CTX_callback_ctrl, SSL_CTX_set_msg_callback, SSL_CTX_set_verify and SSL_CTX_set_session_id_context --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 11 + core/libs/prx/libSceLibreSsl/Export.cpp | 25 - core/libs/prx/libSceLibreSsl/include/Api.hpp | 74 ++- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 224 +++++++++ core/libs/tests/GuestLibreSslSslCtxFields.cpp | 442 ++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 5 +- 6 files changed, 747 insertions(+), 34 deletions(-) create mode 100644 core/libs/tests/GuestLibreSslSslCtxFields.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index f8432a316f..3b9bbcb5b4 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -99,6 +99,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_ssl_ctx PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_ssl_ctx_fields_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSslCtxFields.cpp) + target_include_directories(guest_libressl_ssl_ctx_fields_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_ctx_fields_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_ssl_ctx_fields_tests) + add_test(NAME guest_libressl_ssl_ctx_fields COMMAND guest_libressl_ssl_ctx_fields_tests) + set_tests_properties(guest_libressl_ssl_ctx_fields PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl_ctx_fields PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 0a517dc250..d2a201ab09 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -973,21 +973,11 @@ int APS5_VABI SSL_CTX_add_client_CA_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_callback_ctrl_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_check_private_key_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI SSL_CTX_ctrl_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_free_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1068,26 +1058,11 @@ int APS5_VABI SSL_CTX_set_info_callback_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_msg_callback_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_CTX_set_verify_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_use_PrivateKey_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 7d6e44c712..745a5b4042 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -52,9 +52,69 @@ struct Sha512Context { }; struct LibreSslCtxInternal { - unsigned char beforeQuietShutdown[292]; + std::uint16_t minVersion; + std::uint16_t maxVersion; + std::uint64_t options; + std::uint64_t mode; + void* newSessionCb; + void* removeSessionCb; + void* getSessionCb; + void* appVerifyCallback; + void* appVerifyArg; + void* clientCertCb; + void* appGenCookieCb; + void* appVerifyCookieCb; + void* infoCallback; + void* msgCallback; + void* msgCallbackArg; + void* defaultVerifyCallback; + void* generateSessionId; + void* tlsextServernameCallback; + void* tlsextServernameArg; + void* tlsextTicketKeyCb; + void* tlsextStatusCb; + void* tlsextStatusArg; + void* sessions; + std::uint64_t sessionCacheSize; + void* sessionCacheHead; + void* sessionCacheTail; + int sessionCacheMode; + int sessConnect; + int sessConnectRenegotiate; + int sessConnectGood; + int sessAccept; + int sessAcceptRenegotiate; + int sessAcceptGood; + int sessMiss; + int sessTimeout; + int sessCacheFull; + int sessHit; + int sessCbHit; + void* exData; + void* cipherListById; + void* cert; + void* clientCa; + std::int64_t maxCertList; + int readAhead; int quietShutdown; - unsigned char rest[168]; + unsigned int maxSendFragment; + void* clientCertEngine; + unsigned char tlsextTickKeyName[16]; + unsigned char tlsextTickHmacKey[16]; + unsigned char tlsextTickAesKey[16]; + void* srtpProfiles; + void* nextProtosAdvertisedCb; + void* nextProtosAdvertisedCbArg; + void* nextProtoSelectCb; + void* nextProtoSelectCbArg; + void* alpnSelectCb; + void* alpnSelectCbArg; + unsigned char* alpnClientProtoList; + unsigned int alpnClientProtoListLen; + std::uint64_t tlsextEcpointformatlistLength; + std::uint8_t* tlsextEcpointformatlist; + std::uint64_t tlsextSupportedgroupsLength; + std::uint16_t* tlsextSupportedgroups; }; struct LibreSslCtx { @@ -279,9 +339,9 @@ int APS5_VABI SSL_CIPHER_get_bits_nid_postfix(const void* cipher, std::int32_t* const char* APS5_VABI SSL_CIPHER_get_name_nid_postfix(const void* cipher); char* APS5_VABI SSL_CIPHER_get_version_nid_postfix(const void* cipher); int APS5_VABI SSL_CTX_add_client_CA_nid_postfix(void); -int APS5_VABI SSL_CTX_callback_ctrl_nid_postfix(void); +std::int64_t APS5_VABI SSL_CTX_callback_ctrl_nid_postfix(LibreSslCtx* ctx, int cmd, void* callback); int APS5_VABI SSL_CTX_check_private_key_nid_postfix(void); -int APS5_VABI SSL_CTX_ctrl_nid_postfix(void); +std::int64_t APS5_VABI SSL_CTX_ctrl_nid_postfix(LibreSslCtx* ctx, int cmd, std::int64_t larg, void* parg); int APS5_VABI SSL_CTX_free_nid_postfix(void); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx); int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void); @@ -302,11 +362,11 @@ void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* ctx, void* userdata); int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void); int APS5_VABI SSL_CTX_set_info_callback_nid_postfix(void); -int APS5_VABI SSL_CTX_set_msg_callback_nid_postfix(void); +void APS5_VABI SSL_CTX_set_msg_callback_nid_postfix(LibreSslCtx* ctx, void* callback); int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void); void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode); -int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void); -int APS5_VABI SSL_CTX_set_verify_nid_postfix(void); +int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(LibreSslCtx* ctx, const unsigned char* sidCtx, unsigned int length); +void APS5_VABI SSL_CTX_set_verify_nid_postfix(LibreSslCtx* ctx, int mode, void* callback); int APS5_VABI SSL_CTX_use_PrivateKey_nid_postfix(void); int APS5_VABI SSL_CTX_use_PrivateKey_ASN1_nid_postfix(void); int APS5_VABI SSL_CTX_use_PrivateKey_file_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 99eb4fa5a1..3cda2bf40d 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -1,8 +1,12 @@ #include "prx/libSceLibreSsl/include/Api.hpp" +#include #include #include +#include #include #include +#include +#include "prx/libSceLibreSsl/include/Error.hpp" #include "prx/libc/include/General.hpp" static_assert(sizeof(LibreSslCtxInternal) == 464); @@ -13,14 +17,120 @@ static_assert(offsetof(LibreSslCtx, verifyMode) == 48); static_assert(offsetof(LibreSslCtx, defaultPasswdCallback) == 96); static_assert(offsetof(LibreSslCtx, defaultPasswdCallbackUserdata) == 104); static_assert(offsetof(LibreSslCtx, internal) == 112); +static_assert(offsetof(LibreSslCtx, extraCerts) == 40); +static_assert(offsetof(LibreSslCtx, sidCtxLength) == 52); +static_assert(offsetof(LibreSslCtx, sidCtx) == 56); +static_assert(offsetof(LibreSslCtxInternal, options) == 8); +static_assert(offsetof(LibreSslCtxInternal, mode) == 16); +static_assert(offsetof(LibreSslCtxInternal, msgCallback) == 96); +static_assert(offsetof(LibreSslCtxInternal, msgCallbackArg) == 104); +static_assert(offsetof(LibreSslCtxInternal, defaultVerifyCallback) == 112); +static_assert(offsetof(LibreSslCtxInternal, tlsextServernameCallback) == 128); +static_assert(offsetof(LibreSslCtxInternal, tlsextServernameArg) == 136); +static_assert(offsetof(LibreSslCtxInternal, tlsextTicketKeyCb) == 144); +static_assert(offsetof(LibreSslCtxInternal, tlsextStatusCb) == 152); +static_assert(offsetof(LibreSslCtxInternal, tlsextStatusArg) == 160); +static_assert(offsetof(LibreSslCtxInternal, sessionCacheSize) == 176); +static_assert(offsetof(LibreSslCtxInternal, sessionCacheMode) == 200); +static_assert(offsetof(LibreSslCtxInternal, sessConnect) == 204); +static_assert(offsetof(LibreSslCtxInternal, sessConnectRenegotiate) == 208); +static_assert(offsetof(LibreSslCtxInternal, sessConnectGood) == 212); +static_assert(offsetof(LibreSslCtxInternal, sessAccept) == 216); +static_assert(offsetof(LibreSslCtxInternal, sessAcceptRenegotiate) == 220); +static_assert(offsetof(LibreSslCtxInternal, sessAcceptGood) == 224); +static_assert(offsetof(LibreSslCtxInternal, sessMiss) == 228); +static_assert(offsetof(LibreSslCtxInternal, sessTimeout) == 232); +static_assert(offsetof(LibreSslCtxInternal, sessCacheFull) == 236); +static_assert(offsetof(LibreSslCtxInternal, sessHit) == 240); +static_assert(offsetof(LibreSslCtxInternal, sessCbHit) == 244); +static_assert(offsetof(LibreSslCtxInternal, maxCertList) == 280); +static_assert(offsetof(LibreSslCtxInternal, readAhead) == 288); +static_assert(offsetof(LibreSslCtxInternal, maxSendFragment) == 296); +static_assert(offsetof(LibreSslCtxInternal, tlsextTickKeyName) == 312); +static_assert(offsetof(LibreSslCtxInternal, tlsextTickHmacKey) == 328); +static_assert(offsetof(LibreSslCtxInternal, tlsextTickAesKey) == 344); namespace { +enum Command : int { + NeedTmpRsa = 1, + SetTmpRsa = 2, + SetTmpDh = 3, + SetTmpEcdh = 4, + SetTmpRsaCb = 5, + SetTmpDhCb = 6, + SetTmpEcdhCb = 7, + ExtraChainCert = 14, + SetMsgCallback = 15, + SetMsgCallbackArg = 16, + SessNumber = 20, + SessConnect = 21, + SessConnectGood = 22, + SessConnectRenegotiate = 23, + SessAccept = 24, + SessAcceptGood = 25, + SessAcceptRenegotiate = 26, + SessHit = 27, + SessCbHit = 28, + SessMisses = 29, + SessTimeouts = 30, + SessCacheFull = 31, + Options = 32, + Mode = 33, + GetReadAhead = 40, + SetReadAhead = 41, + SetSessCacheSize = 42, + GetSessCacheSize = 43, + SetSessCacheMode = 44, + GetSessCacheMode = 45, + GetMaxCertList = 50, + SetMaxCertList = 51, + SetMaxSendFragment = 52, + SetTlsextServernameCb = 53, + SetTlsextServernameArg = 54, + GetTlsextTicketKeys = 58, + SetTlsextTicketKeys = 59, + SetTlsextStatusReqCb = 63, + SetTlsextStatusReqCbArg = 64, + SetTlsextTicketKeyCb = 72, + ClearOptions = 77, + ClearMode = 78, + GetExtraChainCerts = 82, + ClearExtraChainCerts = 83, + SetGroups = 91, + SetGroupsList = 92, + SetEcdhAuto = 94, + SetDhAuto = 118, +}; + +constexpr int ErrLibSsl = 20; +constexpr int ErrFunctionNone = 0xfff; +constexpr int ShouldNotHaveBeenCalled = 66; +constexpr int SessionIdContextTooLong = 273; +constexpr int InvalidTicketKeysLength = 325; +constexpr std::int64_t TicketKeysLength = 48; +constexpr std::int64_t MinSendFragment = 512; +constexpr std::int64_t MaxPlainLength = 16384; + LibreSslCtx& Context(LibreSslCtx* ctx, const char* function) { if (!ctx) throw std::invalid_argument(std::string(function) + ": null context"); return *ctx; } +LibreSslCtxInternal& Internal(LibreSslCtx* ctx, const char* function) { + LibreSslCtx& context = Context(ctx, function); + if (!context.internal) throw std::invalid_argument(std::string(function) + ": null internal state"); + return *context.internal; +} + +[[noreturn]] void UnsupportedCommand(const char* function, int cmd) { + NotImplemented_nid_no_patch((std::string(function) + " command " + std::to_string(cmd)).c_str()); +} + +void PutSslError(int reason) { + LibreSslPutError_nid_no_patch(ErrLibSsl, ErrFunctionNone, reason); +} + } extern "C" { @@ -47,4 +157,118 @@ void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* c Context(ctx, __func__).defaultPasswdCallbackUserdata = userdata; } +std::int64_t APS5_VABI SSL_CTX_ctrl_nid_postfix(LibreSslCtx* ctx, int cmd, std::int64_t larg, void* parg) { + LibreSslCtxInternal& internal = Internal(ctx, __func__); + switch (cmd) { + case GetReadAhead: return internal.readAhead; + case SetReadAhead: return std::exchange(internal.readAhead, static_cast(larg)); + case SetMsgCallbackArg: internal.msgCallbackArg = parg; return 1; + case GetMaxCertList: return internal.maxCertList; + case SetMaxCertList: return std::exchange(internal.maxCertList, larg); + case SetSessCacheSize: return static_cast(std::exchange(internal.sessionCacheSize, static_cast(larg))); + case GetSessCacheSize: return static_cast(internal.sessionCacheSize); + case SetSessCacheMode: return std::exchange(internal.sessionCacheMode, static_cast(larg)); + case GetSessCacheMode: return internal.sessionCacheMode; + case SessConnect: return internal.sessConnect; + case SessConnectGood: return internal.sessConnectGood; + case SessConnectRenegotiate: return internal.sessConnectRenegotiate; + case SessAccept: return internal.sessAccept; + case SessAcceptGood: return internal.sessAcceptGood; + case SessAcceptRenegotiate: return internal.sessAcceptRenegotiate; + case SessHit: return internal.sessHit; + case SessCbHit: return internal.sessCbHit; + case SessMisses: return internal.sessMiss; + case SessTimeouts: return internal.sessTimeout; + case SessCacheFull: return internal.sessCacheFull; + case Options: return static_cast(internal.options |= static_cast(larg)); + case ClearOptions: return static_cast(internal.options &= ~static_cast(larg)); + case Mode: return static_cast(internal.mode |= static_cast(larg)); + case ClearMode: return static_cast(internal.mode &= ~static_cast(larg)); + case SetMaxSendFragment: + if (larg < MinSendFragment || larg > MaxPlainLength) return 0; + internal.maxSendFragment = static_cast(larg); + return 1; + case NeedTmpRsa: return 0; + case SetTmpRsa: + case SetTmpRsaCb: + case SetTmpDhCb: + case SetTmpEcdhCb: + PutSslError(ShouldNotHaveBeenCalled); + return 0; + case SetTlsextServernameArg: internal.tlsextServernameArg = parg; return 1; + case GetTlsextTicketKeys: + case SetTlsextTicketKeys: { + auto* keys = static_cast(parg); + if (!keys) return TicketKeysLength; + if (larg != TicketKeysLength) { + PutSslError(InvalidTicketKeysLength); + return 0; + } + if (cmd == SetTlsextTicketKeys) { + std::memcpy(internal.tlsextTickKeyName, keys, 16); + std::memcpy(internal.tlsextTickHmacKey, keys + 16, 16); + std::memcpy(internal.tlsextTickAesKey, keys + 32, 16); + } else { + std::memcpy(keys, internal.tlsextTickKeyName, 16); + std::memcpy(keys + 16, internal.tlsextTickHmacKey, 16); + std::memcpy(keys + 32, internal.tlsextTickAesKey, 16); + } + return 1; + } + case SetTlsextStatusReqCbArg: internal.tlsextStatusArg = parg; return 1; + case GetExtraChainCerts: + if (!parg) throw std::invalid_argument(std::string(__func__) + ": null extra chain certificates output"); + *static_cast(parg) = ctx->extraCerts; + return 1; + case SetTmpDh: + case SetTmpEcdh: + case ExtraChainCert: + case SessNumber: + case ClearExtraChainCerts: + case SetGroups: + case SetGroupsList: + case SetEcdhAuto: + case SetDhAuto: UnsupportedCommand(__func__, cmd); + default: return 0; + } +} + +std::int64_t APS5_VABI SSL_CTX_callback_ctrl_nid_postfix(LibreSslCtx* ctx, int cmd, void* callback) { + LibreSslCtxInternal& internal = Internal(ctx, __func__); + switch (cmd) { + case SetMsgCallback: internal.msgCallback = callback; return 1; + case SetTmpRsaCb: + PutSslError(ShouldNotHaveBeenCalled); + return 0; + case SetTmpDhCb: + case SetTmpEcdhCb: UnsupportedCommand(__func__, cmd); + case SetTlsextServernameCb: internal.tlsextServernameCallback = callback; return 1; + case SetTlsextStatusReqCb: internal.tlsextStatusCb = callback; return 1; + case SetTlsextTicketKeyCb: internal.tlsextTicketKeyCb = callback; return 1; + default: return 0; + } +} + +void APS5_VABI SSL_CTX_set_msg_callback_nid_postfix(LibreSslCtx* ctx, void* callback) { + Internal(ctx, __func__).msgCallback = callback; +} + +void APS5_VABI SSL_CTX_set_verify_nid_postfix(LibreSslCtx* ctx, int mode, void* callback) { + LibreSslCtxInternal& internal = Internal(ctx, __func__); + ctx->verifyMode = mode; + internal.defaultVerifyCallback = callback; +} + +int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(LibreSslCtx* ctx, const unsigned char* sidCtx, unsigned int length) { + LibreSslCtx& context = Context(ctx, __func__); + if (length > sizeof context.sidCtx) { + PutSslError(SessionIdContextTooLong); + return 0; + } + if (!sidCtx && length) throw std::invalid_argument(std::string(__func__) + ": null session id context"); + context.sidCtxLength = length; + std::copy_n(sidCtx, length, context.sidCtx); + return 1; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtxFields.cpp b/core/libs/tests/GuestLibreSslSslCtxFields.cpp new file mode 100644 index 0000000000..80e3c9cd17 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSslCtxFields.cpp @@ -0,0 +1,442 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +constexpr std::size_t CtxSize = 120; +constexpr std::size_t InternalSize = 464; + +constexpr std::size_t CtxExtraCerts = 40; +constexpr std::size_t CtxVerifyMode = 48; +constexpr std::size_t CtxSidCtxLength = 52; +constexpr std::size_t CtxSidCtx = 56; +constexpr std::size_t CtxInternal = 112; + +constexpr std::size_t MinVersion = 0; +constexpr std::size_t MaxVersion = 2; +constexpr std::size_t Options = 8; +constexpr std::size_t Mode = 16; +constexpr std::size_t MsgCallback = 96; +constexpr std::size_t MsgCallbackArg = 104; +constexpr std::size_t DefaultVerifyCallback = 112; +constexpr std::size_t ServernameCallback = 128; +constexpr std::size_t ServernameArg = 136; +constexpr std::size_t TicketKeyCallback = 144; +constexpr std::size_t StatusCallback = 152; +constexpr std::size_t StatusArg = 160; +constexpr std::size_t SessionCacheSize = 176; +constexpr std::size_t SessionCacheMode = 200; +constexpr std::size_t StatsConnect = 204; +constexpr std::size_t StatsConnectRenegotiate = 208; +constexpr std::size_t StatsConnectGood = 212; +constexpr std::size_t StatsAccept = 216; +constexpr std::size_t StatsAcceptRenegotiate = 220; +constexpr std::size_t StatsAcceptGood = 224; +constexpr std::size_t StatsMiss = 228; +constexpr std::size_t StatsTimeout = 232; +constexpr std::size_t StatsCacheFull = 236; +constexpr std::size_t StatsHit = 240; +constexpr std::size_t StatsCbHit = 244; +constexpr std::size_t MaxCertList = 280; +constexpr std::size_t ReadAhead = 288; +constexpr std::size_t MaxSendFragment = 296; +constexpr std::size_t TicketKeys = 312; + +constexpr std::uint64_t ShouldNotHaveBeenCalled = 0x14FFF042; +constexpr std::uint64_t InvalidTicketKeysLength = 0x14FFF145; +constexpr std::uint64_t SessionIdContextTooLong = 0x14FFF111; + +static_assert(sizeof(LibreSslCtx) == CtxSize && sizeof(LibreSslCtxInternal) == InternalSize); +static_assert(StatsCbHit + 4 == 248 && TicketKeys + 48 == 360 && CtxSidCtx + 32 == 88); + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL_CTX fields check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void* Pointer(std::uintptr_t value) { + return reinterpret_cast(value); +} + +template static void Store(unsigned char* base, std::size_t offset, TValue value) { + std::memcpy(base + offset, &value, sizeof value); +} + +template static TValue Load(const unsigned char* base, std::size_t offset) { + TValue value; + std::memcpy(&value, base + offset, sizeof value); + return value; +} + +struct Bytes { + alignas(8) unsigned char ctx[CtxSize]; + alignas(8) unsigned char internal[InternalSize]; +}; + +struct Fixture { + Bytes actual; + Bytes expected; + + Fixture() { + for (std::size_t i = 0; i < CtxSize; ++i) actual.ctx[i] = static_cast(i * 7 + 3); + for (std::size_t i = 0; i < InternalSize; ++i) actual.internal[i] = static_cast(i * 13 + 5); + Store(actual.ctx, CtxExtraCerts, nullptr); + Store(actual.ctx, CtxVerifyMode, 0); + Store(actual.ctx, CtxSidCtxLength, 0); + Store(actual.ctx, CtxInternal, actual.internal); + Store(actual.internal, MinVersion, 0x301); + Store(actual.internal, MaxVersion, 0x303); + Store(actual.internal, Options, 0x4); + Store(actual.internal, Mode, 0); + for (std::size_t offset : {MsgCallback, MsgCallbackArg, DefaultVerifyCallback, ServernameCallback, ServernameArg, TicketKeyCallback, StatusCallback, StatusArg}) + Store(actual.internal, offset, nullptr); + Store(actual.internal, SessionCacheSize, 20480); + Store(actual.internal, SessionCacheMode, 2); + for (std::size_t offset = StatsConnect; offset <= StatsCbHit; offset += 4) Store(actual.internal, offset, 0); + Store(actual.internal, MaxCertList, 102400); + Store(actual.internal, ReadAhead, 0); + Store(actual.internal, MaxSendFragment, 16384); + expected = actual; + } + + Fixture(const Fixture&) = delete; + Fixture& operator=(const Fixture&) = delete; + + LibreSslCtx* Context() { + return reinterpret_cast(actual.ctx); + } + + template void SetCtx(std::size_t offset, TValue value) { + Store(actual.ctx, offset, value); + Store(expected.ctx, offset, value); + } + + template void SetInternal(std::size_t offset, TValue value) { + Store(actual.internal, offset, value); + Store(expected.internal, offset, value); + } + + template void ExpectCtx(std::size_t offset, TValue value) { + Store(expected.ctx, offset, value); + } + + template void ExpectInternal(std::size_t offset, TValue value) { + Store(expected.internal, offset, value); + } + + void Verify(std::uint64_t error, std::source_location location) { + Require(std::memcmp(actual.ctx, expected.ctx, CtxSize) == 0, location); + Require(std::memcmp(actual.internal, expected.internal, InternalSize) == 0, location); + Require(ERR_get_error_nid_postfix() == error, location); + Require(ERR_get_error_nid_postfix() == 0, location); + } + + void Ctrl(int cmd, std::int64_t larg, void* parg, std::int64_t result, std::uint64_t error = 0, std::source_location location = std::source_location::current()) { + Require(SSL_CTX_ctrl_nid_postfix(Context(), cmd, larg, parg) == result, location); + Verify(error, location); + } + + void CallbackCtrl(int cmd, void* callback, std::int64_t result, std::uint64_t error = 0, std::source_location location = std::source_location::current()) { + Require(SSL_CTX_callback_ctrl_nid_postfix(Context(), cmd, callback) == result, location); + Verify(error, location); + } + + void SessionIdContext(const unsigned char* sidCtx, unsigned int length, int result, std::uint64_t error = 0, std::source_location location = std::source_location::current()) { + Require(SSL_CTX_set_session_id_context_nid_postfix(Context(), sidCtx, length) == result, location); + Verify(error, location); + } + + template void Throws(TAction action, std::source_location location = std::source_location::current()) { + RequireThrows(action, location); + Verify(0, location); + } +}; + +static void TestOptionsAndMode() { + Fixture f; + f.Ctrl(32, 0x4, nullptr, 0x4); + f.ExpectInternal(Options, 0x80000004); + f.Ctrl(32, 0x80000000, nullptr, 0x80000004); + f.ExpectInternal(Options, 0x80000000); + f.Ctrl(77, 0x4, nullptr, 0x80000000); + f.ExpectInternal(Mode, 0x2); + f.Ctrl(33, 0x2, nullptr, 0x2); + f.ExpectInternal(Mode, 0); + f.Ctrl(78, 0x2, nullptr, 0); + f.ExpectInternal(Options, UINT64_MAX); + f.Ctrl(32, -1, nullptr, -1); + f.ExpectInternal(Options, 0); + f.Ctrl(77, -1, nullptr, 0); + f.ExpectInternal(Mode, UINT64_MAX); + f.Ctrl(33, -1, nullptr, -1); + f.ExpectInternal(Mode, 0x8000000000000000); + f.Ctrl(78, INT64_MAX, nullptr, INT64_MIN); +} + +static void TestReadAhead() { + Fixture f; + f.Ctrl(40, 0, nullptr, 0); + f.ExpectInternal(ReadAhead, 1); + f.Ctrl(41, 1, nullptr, 0); + f.Ctrl(41, 0x100000001, nullptr, 1); + f.Ctrl(40, 0, nullptr, 1); + f.ExpectInternal(ReadAhead, 0); + f.Ctrl(41, 0x100000000, nullptr, 1); + f.Ctrl(40, 0, nullptr, 0); + f.ExpectInternal(ReadAhead, -1); + f.Ctrl(41, -1, nullptr, 0); + f.Ctrl(40, 0, nullptr, -1); +} + +static void TestMaxCertList() { + Fixture f; + f.Ctrl(50, 0, nullptr, 102400); + f.ExpectInternal(MaxCertList, 1234); + f.Ctrl(51, 1234, nullptr, 102400); + f.ExpectInternal(MaxCertList, -5); + f.Ctrl(51, -5, nullptr, 1234); + f.Ctrl(50, 0, nullptr, -5); +} + +static void TestSessionCache() { + Fixture f; + f.Ctrl(43, 0, nullptr, 20480); + f.ExpectInternal(SessionCacheSize, 7); + f.Ctrl(42, 7, nullptr, 20480); + f.ExpectInternal(SessionCacheSize, UINT64_MAX); + f.Ctrl(42, -1, nullptr, 7); + f.Ctrl(43, 0, nullptr, -1); + f.Ctrl(45, 0, nullptr, 2); + f.ExpectInternal(SessionCacheMode, 3); + f.Ctrl(44, 3, nullptr, 2); + f.ExpectInternal(SessionCacheMode, 2); + f.Ctrl(44, 0x100000002, nullptr, 3); + f.Ctrl(45, 0, nullptr, 2); +} + +static void TestMaxSendFragment() { + Fixture f; + f.Ctrl(52, 511, nullptr, 0); + f.ExpectInternal(MaxSendFragment, 512); + f.Ctrl(52, 512, nullptr, 1); + f.ExpectInternal(MaxSendFragment, 16384); + f.Ctrl(52, 16384, nullptr, 1); + f.Ctrl(52, 16385, nullptr, 0); + f.Ctrl(52, -1, nullptr, 0); + f.Ctrl(52, 0x100000200, nullptr, 0); +} + +static void TestStats() { + Fixture f; + for (int cmd = 21; cmd <= 31; ++cmd) f.Ctrl(cmd, 0, nullptr, 0); + const std::size_t offsets[] = {StatsConnect, StatsConnectRenegotiate, StatsConnectGood, StatsAccept, StatsAcceptRenegotiate, StatsAcceptGood, + StatsMiss, StatsTimeout, StatsCacheFull, StatsHit, StatsCbHit}; + for (int i = 0; i < 11; ++i) f.SetInternal(offsets[i], 101 + i); + f.Ctrl(21, 0, nullptr, 101); + f.Ctrl(22, 0, nullptr, 103); + f.Ctrl(23, 0, nullptr, 102); + f.Ctrl(24, 0, nullptr, 104); + f.Ctrl(25, 0, nullptr, 106); + f.Ctrl(26, 0, nullptr, 105); + f.Ctrl(27, 0, nullptr, 110); + f.Ctrl(28, 0, nullptr, 111); + f.Ctrl(29, 0, nullptr, 107); + f.Ctrl(30, 0, nullptr, 108); + f.Ctrl(31, 0, nullptr, 109); + f.SetInternal(StatsConnect, -1); + f.Ctrl(21, 0, nullptr, -1); +} + +static void TestCallbackArgs() { + Fixture f; + f.ExpectInternal(MsgCallbackArg, Pointer(0x2222)); + f.Ctrl(16, 0, Pointer(0x2222), 1); + f.ExpectInternal(ServernameArg, Pointer(0x3333)); + f.Ctrl(54, 0, Pointer(0x3333), 1); + f.ExpectInternal(StatusArg, Pointer(0x4444)); + f.Ctrl(64, 0, Pointer(0x4444), 1); + f.ExpectInternal(MsgCallbackArg, nullptr); + f.Ctrl(16, 5, nullptr, 1); +} + +static void TestTicketKeys() { + Fixture f; + f.Ctrl(58, 0, nullptr, 48); + f.Ctrl(59, 0, nullptr, 48); + f.Ctrl(58, 47, nullptr, 48); + f.Ctrl(59, 47, nullptr, 48); + unsigned char buffer[48]; + std::memset(buffer, 0xee, sizeof buffer); + f.Ctrl(58, 47, buffer, 0, InvalidTicketKeysLength); + for (unsigned char byte : buffer) Require(byte == 0xee); + f.Ctrl(59, 47, buffer, 0, InvalidTicketKeysLength); + f.Ctrl(59, 49, buffer, 0, InvalidTicketKeysLength); + f.Ctrl(58, 48, buffer, 1); + Require(std::memcmp(buffer, f.actual.internal + TicketKeys, 48) == 0); + for (int i = 0; i < 48; ++i) buffer[i] = static_cast(i); + for (int i = 0; i < 48; ++i) f.ExpectInternal(TicketKeys + i, static_cast(i)); + f.Ctrl(59, 48, buffer, 1); + unsigned char keys[48]; + std::memset(keys, 0xee, sizeof keys); + f.Ctrl(58, 48, keys, 1); + for (int i = 0; i < 48; ++i) Require(keys[i] == i); +} + +static void TestExtraChainCerts() { + Fixture f; + void* slot = Pointer(0x5555); + f.Ctrl(82, 0, &slot, 1); + Require(slot == nullptr); + f.SetCtx(CtxExtraCerts, Pointer(0x7777)); + f.Ctrl(82, 0, &slot, 1); + Require(slot == Pointer(0x7777)); + f.Throws([&] { SSL_CTX_ctrl_nid_postfix(f.Context(), 82, 0, nullptr); }); +} + +static void TestTemporaryKeys() { + Fixture f; + f.Ctrl(1, 0, nullptr, 0); + f.Ctrl(2, 0, nullptr, 0, ShouldNotHaveBeenCalled); + f.Ctrl(5, 0, nullptr, 0, ShouldNotHaveBeenCalled); + f.Ctrl(6, 0, nullptr, 0, ShouldNotHaveBeenCalled); + f.Ctrl(7, 0, nullptr, 0, ShouldNotHaveBeenCalled); +} + +static void TestOtherCtrlCommands() { + Fixture f; + for (int cmd : {0, 8, 13, 15, 17, 55, 65, 999, -1}) f.Ctrl(cmd, 0, nullptr, 0); + for (int cmd : {3, 4, 14, 20, 83, 91, 92, 94, 118}) f.Throws([&] { SSL_CTX_ctrl_nid_postfix(f.Context(), cmd, 0, Pointer(0x1111)); }); +} + +static void TestCallbackCtrl() { + Fixture f; + f.ExpectInternal(MsgCallback, Pointer(0x6666)); + f.CallbackCtrl(15, Pointer(0x6666), 1); + f.ExpectInternal(ServernameCallback, Pointer(0x7777)); + f.CallbackCtrl(53, Pointer(0x7777), 1); + f.ExpectInternal(StatusCallback, Pointer(0x8888)); + f.CallbackCtrl(63, Pointer(0x8888), 1); + f.ExpectInternal(TicketKeyCallback, Pointer(0x9999)); + f.CallbackCtrl(72, Pointer(0x9999), 1); + f.CallbackCtrl(5, Pointer(0x1111), 0, ShouldNotHaveBeenCalled); + f.CallbackCtrl(0, Pointer(0x1111), 0); + f.CallbackCtrl(16, Pointer(0x1111), 0); + f.CallbackCtrl(999, Pointer(0x1111), 0); + for (int cmd : {6, 7}) f.Throws([&] { SSL_CTX_callback_ctrl_nid_postfix(f.Context(), cmd, Pointer(0x1111)); }); +} + +static void TestSetMsgCallback() { + Fixture f; + f.ExpectInternal(MsgCallback, Pointer(0xaaaa)); + SSL_CTX_set_msg_callback_nid_postfix(f.Context(), Pointer(0xaaaa)); + f.Verify(0, std::source_location::current()); + f.ExpectInternal(MsgCallback, nullptr); + SSL_CTX_set_msg_callback_nid_postfix(f.Context(), nullptr); + f.Verify(0, std::source_location::current()); +} + +static void TestSetVerify() { + Fixture f; + f.ExpectCtx(CtxVerifyMode, 3); + f.ExpectInternal(DefaultVerifyCallback, Pointer(0xbbbb)); + SSL_CTX_set_verify_nid_postfix(f.Context(), 3, Pointer(0xbbbb)); + f.Verify(0, std::source_location::current()); + f.ExpectCtx(CtxVerifyMode, -1); + f.ExpectInternal(DefaultVerifyCallback, nullptr); + SSL_CTX_set_verify_nid_postfix(f.Context(), -1, nullptr); + f.Verify(0, std::source_location::current()); + f.ExpectCtx(CtxVerifyMode, 0x7fffffff); + f.ExpectInternal(DefaultVerifyCallback, Pointer(0xcccc)); + SSL_CTX_set_verify_nid_postfix(f.Context(), 0x7fffffff, Pointer(0xcccc)); + f.Verify(0, std::source_location::current()); +} + +static void TestSetSessionIdContext() { + Fixture f; + const unsigned char abc[] = {'a', 'b', 'c'}; + unsigned char sequence[33]; + for (int i = 0; i < 33; ++i) sequence[i] = static_cast(i + 1); + const unsigned char q[] = {'q'}; + const unsigned char zz[] = {'z', 'z'}; + f.ExpectCtx(CtxSidCtxLength, 3); + for (int i = 0; i < 3; ++i) f.ExpectCtx(CtxSidCtx + i, abc[i]); + f.SessionIdContext(abc, 3, 1); + f.ExpectCtx(CtxSidCtxLength, 32); + for (int i = 0; i < 32; ++i) f.ExpectCtx(CtxSidCtx + i, sequence[i]); + f.SessionIdContext(sequence, 32, 1); + f.SessionIdContext(sequence, 33, 0, SessionIdContextTooLong); + f.SessionIdContext(sequence, UINT32_MAX, 0, SessionIdContextTooLong); + f.SessionIdContext(nullptr, 33, 0, SessionIdContextTooLong); + f.ExpectCtx(CtxSidCtxLength, 0); + f.SessionIdContext(q, 0, 1); + f.SessionIdContext(nullptr, 0, 1); + f.ExpectCtx(CtxSidCtxLength, 32); + f.SessionIdContext(sequence, 32, 1); + f.ExpectCtx(CtxSidCtxLength, 2); + f.ExpectCtx(CtxSidCtx, 'z'); + f.ExpectCtx(CtxSidCtx + 1, 'z'); + f.SessionIdContext(zz, 2, 1); + for (int i = 2; i < 32; ++i) Require(f.actual.ctx[CtxSidCtx + i] == i + 1); + f.Throws([&] { SSL_CTX_set_session_id_context_nid_postfix(f.Context(), nullptr, 1); }); +} + +static void TestNullContext() { + const unsigned char sidCtx[] = {1}; + RequireThrows([] { SSL_CTX_ctrl_nid_postfix(nullptr, 32, 1, nullptr); }); + RequireThrows([] { SSL_CTX_callback_ctrl_nid_postfix(nullptr, 15, nullptr); }); + RequireThrows([] { SSL_CTX_set_msg_callback_nid_postfix(nullptr, nullptr); }); + RequireThrows([] { SSL_CTX_set_verify_nid_postfix(nullptr, 1, nullptr); }); + RequireThrows([&] { SSL_CTX_set_session_id_context_nid_postfix(nullptr, sidCtx, 1); }); + Require(ERR_get_error_nid_postfix() == 0); +} + +static void TestNullInternal() { + Fixture f; + f.SetCtx(CtxInternal, nullptr); + unsigned char keys[48] {}; + for (int cmd = -1; cmd <= 120; ++cmd) { + f.Throws([&] { SSL_CTX_ctrl_nid_postfix(f.Context(), cmd, 48, keys); }); + f.Throws([&] { SSL_CTX_callback_ctrl_nid_postfix(f.Context(), cmd, Pointer(0x1111)); }); + } + f.Throws([&] { SSL_CTX_ctrl_nid_postfix(f.Context(), 999, 0, nullptr); }); + f.Throws([&] { SSL_CTX_callback_ctrl_nid_postfix(f.Context(), 999, nullptr); }); + f.Throws([&] { SSL_CTX_set_msg_callback_nid_postfix(f.Context(), Pointer(0xaaaa)); }); + f.Throws([&] { SSL_CTX_set_verify_nid_postfix(f.Context(), 3, Pointer(0xbbbb)); }); + const unsigned char abc[] = {'a', 'b', 'c'}; + f.ExpectCtx(CtxSidCtxLength, 3); + for (int i = 0; i < 3; ++i) f.ExpectCtx(CtxSidCtx + i, abc[i]); + f.SessionIdContext(abc, 3, 1); +} + +int main() { + ERR_clear_error_nid_postfix(); + TestOptionsAndMode(); + TestReadAhead(); + TestMaxCertList(); + TestSessionCache(); + TestMaxSendFragment(); + TestStats(); + TestCallbackArgs(); + TestTicketKeys(); + TestExtraChainCerts(); + TestTemporaryKeys(); + TestOtherCtrlCommands(); + TestCallbackCtrl(); + TestSetMsgCallback(); + TestSetVerify(); + TestSetSessionIdContext(); + TestNullContext(); + TestNullInternal(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 68a8f6d282..1dbe7e4c9c 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,17 +2,18 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 337 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 332 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. - [RAND_add, RAND_bytes, RAND_status, RAND_load_file, RAND_file_name, CRYPTO_malloc, CRYPTO_free, CRYPTO_set_mem_functions](../../../core/libs/prx/libSceLibreSsl/src/Runtime.cpp) (libSceLibreSsl) - follow LibreSSL's [rand_lib.c, randfile.c](https://github.com/libressl/openbsd/tree/0f0b16dc977f6ca499f0f4540078d992c96da597/src/lib/libcrypto/rand) and [malloc-wrapper.c](https://github.com/libressl/openbsd/blob/0f0b16dc977f6ca499f0f4540078d992c96da597/src/lib/libcrypto/malloc-wrapper.c), which are the same in 2.6.5 (3.4.3 linked); nothing is checked on the console. `RAND_bytes` reads host entropy and `CRYPTO_malloc` and `CRYPTO_free` use the guest heap. `CRYPTO_malloc` returns null for a size of zero or less, as LibreSSL 2.5.4 to 3.4 do; later versions take a `size_t` size and call `malloc`. A null buffer with a positive length throws - [AES_set_encrypt_key, AES_set_decrypt_key, AES_encrypt, AES_decrypt, AES_ecb_encrypt, AES_cbc_encrypt, AES_cfb128_encrypt, AES_ctr128_encrypt](../../../core/libs/prx/libSceLibreSsl/src/Aes.cpp) (libSceLibreSsl) - follow LibreSSL's [aes.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/aes/aes.h) and [mode code](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/modes). Nothing is checked on the console. Key setup follows [LibreSSL 2.5.4's aes_core.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/aes_core.c), the version the `X509_verify_cert_error_string` entry names: a null pointer returns -1 and a key length other than 128, 192 or 256 bits returns -2 before the key is written, and only the 44, 52 or 60 words the key length needs are written. The `AES_KEY` schedule uses that file's generic C layout (big-endian words, `rounds` 10, 12 or 14). The 2.5.4 amd64 build uses [aes-x86_64](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/asm/aes-x86_64.pl) instead, which copies the key bytes without swapping, so each word is byte-reversed compared with the C layout (OpenSSL 3.0.13's x86-64 code gives the same schedules byte-reversed); the console's build options are unknown, so a schedule the title reads or fills is unverified. CTR leaves the last keystream block in `ecount_buf` even at a block boundary. Null pointers in the cipher and mode functions, other `rounds` values and a CFB or CTR position outside 0-15 throw -- [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names +- [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - the per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. - [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb, SSL_CTX_set_default_passwd_cb_userdata](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_ctrl, SSL_CTX_callback_ctrl, SSL_CTX_set_msg_callback, SSL_CTX_set_verify, SSL_CTX_set_session_id_context](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The internal layout, commands and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl_locl.h`, `ssl_lib.c` and `s3_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Control commands 3, 4, 14, 20, 83, 91, 92, 94 and 118 and callback commands 6 and 7 throw for every input, as they use certificate, session table, DH, ECDH, stack or group state that no export builds yet. Commands outside 2.5.4's switch return 0, as in that release. A null context, a null internal state, a null output for command 82 and a null session ID context with a nonzero length throw ### Functional From 7d0e976ac9bf53055344829f9315fd5791f5ce23 Mon Sep 17 00:00:00 2001 From: Ahmad Mujahid Date: Sun, 25 Oct 2026 23:16:29 +0400 Subject: [PATCH 025/612] feat(libSceLibreSsl): implement SSL_set_verify, SSL_get0_alpn_selected, SSL_set_connect_state and SSL_ctrl --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 11 + core/libs/prx/libSceLibreSsl/Export.cpp | 20 - core/libs/prx/libSceLibreSsl/include/Api.hpp | 32 +- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 186 ++++++++ core/libs/tests/GuestLibreSslSslSettings.cpp | 446 +++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 3 +- 6 files changed, 671 insertions(+), 27 deletions(-) create mode 100644 core/libs/tests/GuestLibreSslSslSettings.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index b3b0fa1bdc..fc93184c56 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -99,6 +99,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_ssl PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_ssl_settings_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSslSettings.cpp) + target_include_directories(guest_libressl_ssl_settings_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_settings_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_settings_tests) + add_test(NAME guest_libressl_ssl_settings COMMAND guest_libressl_ssl_settings_tests) + set_tests_properties(guest_libressl_ssl_settings PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl_settings PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index d23cfdf71c..4ba91b8366 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1173,21 +1173,11 @@ int APS5_VABI SSL_connect_nid_postfix(void) { return 0; } -int APS5_VABI SSL_ctrl_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_free_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI SSL_get0_alpn_selected_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_get0_param_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1303,11 +1293,6 @@ int APS5_VABI SSL_set_bio_nid_postfix(void) { return 0; } -int APS5_VABI SSL_set_connect_state_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_set_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1323,11 +1308,6 @@ int APS5_VABI SSL_set_session_nid_postfix(void) { return 0; } -int APS5_VABI SSL_set_verify_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_shutdown_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 9c3ba0042b..83baf8d30e 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -52,11 +52,31 @@ struct Sha512Context { }; struct LibreSslSslInternal { - unsigned char beforeRwstate[156]; + unsigned char beforeOptions[8]; + std::uint64_t options; + std::uint64_t mode; + unsigned char beforeHandshakeFunc[32]; + void* handshakeFunc; + void* msgCallback; + void* msgCallbackArg; + unsigned char beforeVerifyCallback[8]; + void* verifyCallback; + unsigned char beforeRwstate[60]; int rwstate; unsigned char beforeShutdown[8]; int shutdown; - unsigned char rest[260]; + unsigned char beforeReadAhead[40]; + int readAhead; + unsigned char beforeAeadReadCtx[24]; + void* aeadReadCtx; + void* aeadWriteCtx; + void* encWriteCtx; + void* writeHash; + unsigned char beforeMaxCertList[24]; + std::int64_t maxCertList; + unsigned char beforeState[108]; + int state; + unsigned char rest[16]; }; struct LibreSsl { @@ -336,9 +356,9 @@ int APS5_VABI SSL_accept_nid_postfix(void); int APS5_VABI SSL_alert_desc_string_long_nid_postfix(void); int APS5_VABI SSL_check_private_key_nid_postfix(void); int APS5_VABI SSL_connect_nid_postfix(void); -int APS5_VABI SSL_ctrl_nid_postfix(void); +std::int64_t APS5_VABI SSL_ctrl_nid_postfix(LibreSsl* ssl, int command, std::int64_t argument, void* pointer); int APS5_VABI SSL_free_nid_postfix(void); -int APS5_VABI SSL_get0_alpn_selected_nid_postfix(void); +void APS5_VABI SSL_get0_alpn_selected_nid_postfix(const LibreSsl* ssl, const unsigned char** data, unsigned int* length); int APS5_VABI SSL_get0_param_nid_postfix(void); int APS5_VABI SSL_get1_session_nid_postfix(void); void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl); @@ -367,11 +387,11 @@ int APS5_VABI SSL_read_nid_postfix(void); int APS5_VABI SSL_select_next_proto_nid_postfix(void); int APS5_VABI SSL_set_SSL_CTX_nid_postfix(void); int APS5_VABI SSL_set_bio_nid_postfix(void); -int APS5_VABI SSL_set_connect_state_nid_postfix(void); +void APS5_VABI SSL_set_connect_state_nid_postfix(LibreSsl* ssl); int APS5_VABI SSL_set_ex_data_nid_postfix(void); int APS5_VABI SSL_set_fd_nid_postfix(void); int APS5_VABI SSL_set_session_nid_postfix(void); -int APS5_VABI SSL_set_verify_nid_postfix(void); +void APS5_VABI SSL_set_verify_nid_postfix(LibreSsl* ssl, int mode, void* callback); int APS5_VABI SSL_shutdown_nid_postfix(void); int APS5_VABI SSL_use_PrivateKey_ASN1_nid_postfix(void); int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 74c3e5aa99..be01abbe3f 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -15,6 +15,27 @@ static_assert(offsetof(LibreSsl, session) == 128); static_assert(offsetof(LibreSsl, ctx) == 152); static_assert(offsetof(LibreSsl, verifyResult) == 160); static_assert(offsetof(LibreSsl, internal) == 224); +static_assert(offsetof(LibreSslSslInternal, options) == 8); +static_assert(offsetof(LibreSslSslInternal, mode) == 16); +static_assert(offsetof(LibreSslSslInternal, handshakeFunc) == 56); +static_assert(offsetof(LibreSslSslInternal, msgCallback) == 64); +static_assert(offsetof(LibreSslSslInternal, msgCallbackArg) == 72); +static_assert(offsetof(LibreSslSslInternal, verifyCallback) == 88); +static_assert(offsetof(LibreSslSslInternal, readAhead) == 212); +static_assert(offsetof(LibreSslSslInternal, aeadReadCtx) == 240); +static_assert(offsetof(LibreSslSslInternal, aeadWriteCtx) == 248); +static_assert(offsetof(LibreSslSslInternal, encWriteCtx) == 256); +static_assert(offsetof(LibreSslSslInternal, writeHash) == 264); +static_assert(offsetof(LibreSslSslInternal, maxCertList) == 296); +static_assert(offsetof(LibreSslSslInternal, state) == 412); +static_assert(offsetof(LibreSsl, method) == 8); +static_assert(offsetof(LibreSsl, server) == 40); +static_assert(offsetof(LibreSsl, s3) == 48); +static_assert(offsetof(LibreSsl, d1) == 56); +static_assert(offsetof(LibreSsl, verifyMode) == 136); +static_assert(offsetof(LibreSsl, maxSendFragment) == 176); +static_assert(offsetof(LibreSsl, encReadCtx) == 208); +static_assert(offsetof(LibreSsl, readHash) == 216); namespace { @@ -29,6 +50,95 @@ const LibreSslSslInternal& Internal(const LibreSsl* ssl, const char* function) { return *connection.internal; } +struct Ssl3StateInternal { + unsigned char beforeSendConnectionBinding[1012]; + int sendConnectionBinding; + unsigned char beforeAlpnSelected[8]; + const unsigned char* alpnSelected; + unsigned int alpnSelectedLength; + unsigned char rest[4]; +}; + +struct Ssl3State { + unsigned char beforeInternal[136]; + const Ssl3StateInternal* internal; +}; + +struct Dtls1StateInternal { + unsigned char beforeMtu[636]; + std::uint32_t mtu; +}; + +struct Dtls1State { + unsigned char beforeInternal[32]; + Dtls1StateInternal* internal; +}; + +struct SslMethodInternal { + int version; + unsigned char beforeConnect[36]; + void* connect; + unsigned char rest[112]; +}; + +struct SslMethod { + unsigned char beforeInternal[40]; + const SslMethodInternal* internal; +}; + +static_assert(sizeof(Ssl3StateInternal) == 1040); +static_assert(offsetof(Ssl3StateInternal, sendConnectionBinding) == 1012); +static_assert(offsetof(Ssl3StateInternal, alpnSelected) == 1024); +static_assert(offsetof(Ssl3StateInternal, alpnSelectedLength) == 1032); +static_assert(sizeof(Ssl3State) == 144); +static_assert(offsetof(Dtls1StateInternal, mtu) == 636); +static_assert(sizeof(Dtls1State) == 40); +static_assert(sizeof(SslMethodInternal) == 160); +static_assert(offsetof(SslMethodInternal, connect) == 40); +static_assert(sizeof(SslMethod) == 48); + +constexpr int ControlSetMessageCallbackArgument = 16; +constexpr int ControlSetMtu = 17; +constexpr int ControlOptions = 32; +constexpr int ControlMode = 33; +constexpr int ControlGetReadAhead = 40; +constexpr int ControlSetReadAhead = 41; +constexpr int ControlGetMaxCertList = 50; +constexpr int ControlSetMaxCertList = 51; +constexpr int ControlSetMaxSendFragment = 52; +constexpr int ControlGetRenegotiationInfoSupport = 76; +constexpr int ControlClearOptions = 77; +constexpr int ControlClearMode = 78; +constexpr int Dtls1Version = 0xFEFF; +constexpr int StateConnectBefore = 0x5000; +constexpr std::int64_t DtlsMinimumMtu = 228; +constexpr std::int64_t MinimumSendFragment = 512; +constexpr std::int64_t MaximumPlainLength = 16384; + +LibreSsl& Connection(LibreSsl* ssl, const char* function) { + if (!ssl) throw std::invalid_argument(std::string(function) + ": null connection"); + return *ssl; +} + +LibreSslSslInternal& Internal(LibreSsl* ssl, const char* function) { + LibreSsl& connection = Connection(ssl, function); + if (!connection.internal) throw std::invalid_argument(std::string(function) + ": null internal state"); + return *connection.internal; +} + +const SslMethodInternal& Method(const LibreSsl& connection, const char* function) { + const auto* method = static_cast(connection.method); + if (!method || !method->internal) throw std::invalid_argument(std::string(function) + ": null method"); + return *method->internal; +} + +const Ssl3StateInternal* Ssl3Internal(const LibreSsl& connection, const char* function) { + const auto* state = static_cast(connection.s3); + if (!state) return nullptr; + if (!state->internal) throw std::invalid_argument(std::string(function) + ": null SSL3 state"); + return state->internal; +} + } extern "C" { @@ -61,4 +171,80 @@ int APS5_VABI SSL_want_nid_postfix(const LibreSsl* ssl) { return Internal(ssl, __func__).rwstate; } +void APS5_VABI SSL_set_verify_nid_postfix(LibreSsl* ssl, int mode, void* callback) { + LibreSsl& connection = Connection(ssl, __func__); + if (callback) Internal(ssl, __func__).verifyCallback = callback; + connection.verifyMode = mode; +} + +void APS5_VABI SSL_get0_alpn_selected_nid_postfix(const LibreSsl* ssl, const unsigned char** data, unsigned int* length) { + if (!data || !length) throw std::invalid_argument(std::string(__func__) + ": null output"); + const Ssl3StateInternal* state = Ssl3Internal(Connection(ssl, __func__), __func__); + *data = state ? state->alpnSelected : nullptr; + *length = state ? state->alpnSelectedLength : 0; +} + +void APS5_VABI SSL_set_connect_state_nid_postfix(LibreSsl* ssl) { + LibreSsl& connection = Connection(ssl, __func__); + LibreSslSslInternal& internal = Internal(ssl, __func__); + const SslMethodInternal& method = Method(connection, __func__); + if (connection.encReadCtx || connection.readHash || internal.encWriteCtx || internal.aeadReadCtx || internal.aeadWriteCtx || internal.writeHash) + NotImplemented_nid_no_patch("SSL_set_connect_state: freeing a live cipher or hash context"); + connection.server = 0; + internal.shutdown = 0; + internal.state = StateConnectBefore; + internal.handshakeFunc = method.connect; +} + +std::int64_t APS5_VABI SSL_ctrl_nid_postfix(LibreSsl* ssl, int command, std::int64_t argument, void* pointer) { + switch (command) { + case ControlGetReadAhead: + return Internal(ssl, __func__).readAhead; + case ControlSetReadAhead: { + LibreSslSslInternal& internal = Internal(ssl, __func__); + const int previous = internal.readAhead; + internal.readAhead = static_cast(argument); + return previous; + } + case ControlSetMessageCallbackArgument: + Internal(ssl, __func__).msgCallbackArg = pointer; + return 1; + case ControlOptions: + return static_cast(Internal(ssl, __func__).options |= static_cast(argument)); + case ControlClearOptions: + return static_cast(Internal(ssl, __func__).options &= ~static_cast(argument)); + case ControlMode: + return static_cast(Internal(ssl, __func__).mode |= static_cast(argument)); + case ControlClearMode: + return static_cast(Internal(ssl, __func__).mode &= ~static_cast(argument)); + case ControlGetMaxCertList: + return Internal(ssl, __func__).maxCertList; + case ControlSetMaxCertList: { + LibreSslSslInternal& internal = Internal(ssl, __func__); + const std::int64_t previous = internal.maxCertList; + internal.maxCertList = argument; + return previous; + } + case ControlSetMtu: { + if (argument < DtlsMinimumMtu) return 0; + const LibreSsl& connection = Connection(ssl, __func__); + if (Method(connection, __func__).version != Dtls1Version) return 0; + const auto* state = static_cast(connection.d1); + if (!state || !state->internal) throw std::invalid_argument(std::string(__func__) + ": null DTLS state"); + state->internal->mtu = static_cast(argument); + return argument; + } + case ControlSetMaxSendFragment: + if (argument < MinimumSendFragment || argument > MaximumPlainLength) return 0; + Connection(ssl, __func__).maxSendFragment = static_cast(argument); + return 1; + case ControlGetRenegotiationInfoSupport: { + const Ssl3StateInternal* state = Ssl3Internal(Connection(ssl, __func__), __func__); + return state ? state->sendConnectionBinding : 0; + } + default: + NotImplemented_nid_no_patch(("SSL_ctrl: command " + std::to_string(command)).c_str()); + } +} + } diff --git a/core/libs/tests/GuestLibreSslSslSettings.cpp b/core/libs/tests/GuestLibreSslSslSettings.cpp new file mode 100644 index 0000000000..58a6408299 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSslSettings.cpp @@ -0,0 +1,446 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include + +struct TestSslInternal { + unsigned char beforeOptions[8]; + std::uint64_t options; + std::uint64_t mode; + unsigned char beforeHandshakeFunc[32]; + void* handshakeFunc; + void* msgCallback; + void* msgCallbackArg; + unsigned char beforeVerifyCallback[8]; + void* verifyCallback; + unsigned char beforeShutdown[72]; + int shutdown; + unsigned char beforeReadAhead[40]; + int readAhead; + unsigned char beforeAeadReadCtx[24]; + void* aeadReadCtx; + void* aeadWriteCtx; + void* encWriteCtx; + void* writeHash; + unsigned char beforeMaxCertList[24]; + std::int64_t maxCertList; + unsigned char beforeState[108]; + int state; + int rstate; + unsigned char rest[12]; +}; + +struct TestSsl3Internal { + unsigned char beforeSendConnectionBinding[1012]; + int sendConnectionBinding; + unsigned char beforeAlpnSelected[8]; + const unsigned char* alpnSelected; + unsigned int alpnSelectedLength; + unsigned char rest[4]; +}; + +struct TestSsl3 { + unsigned char beforeInternal[136]; + TestSsl3Internal* internal; +}; + +struct TestDtls1Internal { + unsigned char beforeMtu[636]; + std::uint32_t mtu; + unsigned char rest[4]; +}; + +struct TestDtls1 { + unsigned char beforeInternal[32]; + TestDtls1Internal* internal; +}; + +struct TestMethodInternal { + int version; + unsigned char beforeAccept[28]; + void* accept; + void* connect; + unsigned char rest[112]; +}; + +struct TestMethod { + unsigned char beforeInternal[40]; + TestMethodInternal* internal; +}; + +struct TestSsl { + int version; + unsigned char beforeMethod[4]; + TestMethod* method; + unsigned char beforeServer[24]; + int server; + unsigned char beforeS3[4]; + TestSsl3* s3; + TestDtls1* d1; + unsigned char beforeVerifyMode[72]; + int verifyMode; + unsigned char beforeClientVersion[32]; + int clientVersion; + unsigned int maxSendFragment; + unsigned char beforeTlsextHostname[4]; + char* tlsextHostname; + unsigned char beforeEncReadCtx[16]; + void* encReadCtx; + void* readHash; + TestSslInternal* internal; +}; + +static_assert(sizeof(TestSslInternal) == 432 && offsetof(TestSslInternal, options) == 8 && offsetof(TestSslInternal, mode) == 16); +static_assert(offsetof(TestSslInternal, handshakeFunc) == 56 && offsetof(TestSslInternal, msgCallback) == 64 && offsetof(TestSslInternal, msgCallbackArg) == 72); +static_assert(offsetof(TestSslInternal, verifyCallback) == 88 && offsetof(TestSslInternal, shutdown) == 168 && offsetof(TestSslInternal, readAhead) == 212); +static_assert(offsetof(TestSslInternal, aeadReadCtx) == 240 && offsetof(TestSslInternal, aeadWriteCtx) == 248 && offsetof(TestSslInternal, encWriteCtx) == 256); +static_assert(offsetof(TestSslInternal, writeHash) == 264 && offsetof(TestSslInternal, maxCertList) == 296); +static_assert(offsetof(TestSslInternal, state) == 412 && offsetof(TestSslInternal, rstate) == 416); +static_assert(sizeof(TestSsl3Internal) == 1040 && offsetof(TestSsl3Internal, sendConnectionBinding) == 1012); +static_assert(offsetof(TestSsl3Internal, alpnSelected) == 1024 && offsetof(TestSsl3Internal, alpnSelectedLength) == 1032); +static_assert(sizeof(TestSsl3) == 144 && sizeof(TestDtls1) == 40 && offsetof(TestDtls1Internal, mtu) == 636); +static_assert(sizeof(TestMethodInternal) == 160 && offsetof(TestMethodInternal, accept) == 32 && offsetof(TestMethodInternal, connect) == 40); +static_assert(sizeof(TestMethod) == 48 && sizeof(TestSsl) == 232 && offsetof(TestSsl, method) == 8 && offsetof(TestSsl, server) == 40); +static_assert(offsetof(TestSsl, s3) == 48 && offsetof(TestSsl, d1) == 56 && offsetof(TestSsl, verifyMode) == 136); +static_assert(offsetof(TestSsl, clientVersion) == 172 && offsetof(TestSsl, maxSendFragment) == 176 && offsetof(TestSsl, tlsextHostname) == 184); +static_assert(offsetof(TestSsl, encReadCtx) == 208 && offsetof(TestSsl, readHash) == 216 && offsetof(TestSsl, internal) == 224); + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL settings check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +template static void Fill(T& value, unsigned char seed) { + auto* bytes = reinterpret_cast(&value); + for (std::size_t index = 0; index < sizeof(T); ++index) bytes[index] = static_cast(seed + index); +} + +template static bool Same(const T& left, const T& right) { + return std::memcmp(&left, &right, sizeof(T)) == 0; +} + +struct Connection { + TestSsl ssl; + TestSslInternal internal; + + Connection() { + Fill(ssl, 0x10); + Fill(internal, 0x40); + ssl.method = nullptr; + ssl.s3 = nullptr; + ssl.d1 = nullptr; + ssl.encReadCtx = nullptr; + ssl.readHash = nullptr; + ssl.internal = &internal; + internal.aeadReadCtx = nullptr; + internal.aeadWriteCtx = nullptr; + internal.encWriteCtx = nullptr; + internal.writeHash = nullptr; + } + + LibreSsl* Ssl() { + return reinterpret_cast(&ssl); + } +}; + +static std::int64_t Control(Connection& connection, int command, std::int64_t argument, void* pointer = nullptr) { + return SSL_ctrl_nid_postfix(connection.Ssl(), command, argument, pointer); +} + +static void TestSetVerify() { + int first = 0; + int second = 0; + Connection connection; + Connection expected = connection; + RequireThrows([&] { SSL_set_verify_nid_postfix(nullptr, 1, &first); }); + SSL_set_verify_nid_postfix(connection.Ssl(), 3, &first); + expected.ssl.verifyMode = 3; + expected.internal.verifyCallback = &first; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + SSL_set_verify_nid_postfix(connection.Ssl(), -1, nullptr); + expected.ssl.verifyMode = -1; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + SSL_set_verify_nid_postfix(connection.Ssl(), 1, &second); + expected.ssl.verifyMode = 1; + expected.internal.verifyCallback = &second; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + connection.ssl.internal = nullptr; + SSL_set_verify_nid_postfix(connection.Ssl(), 2, nullptr); + Require(connection.ssl.verifyMode == 2); + RequireThrows([&] { SSL_set_verify_nid_postfix(connection.Ssl(), 5, &first); }); + Require(connection.ssl.verifyMode == 2); +} + +static void TestGetAlpnSelected() { + static const unsigned char protocol[] = "h2"; + TestSsl3Internal stateInternal {}; + TestSsl3 state {}; + Connection connection; + const Connection expected = connection; + const unsigned char* data = reinterpret_cast(1); + unsigned int length = 77; + RequireThrows([&] { SSL_get0_alpn_selected_nid_postfix(nullptr, &data, &length); }); + RequireThrows([&] { SSL_get0_alpn_selected_nid_postfix(connection.Ssl(), nullptr, &length); }); + RequireThrows([&] { SSL_get0_alpn_selected_nid_postfix(connection.Ssl(), &data, nullptr); }); + SSL_get0_alpn_selected_nid_postfix(connection.Ssl(), &data, &length); + Require(data == nullptr && length == 0); + connection.ssl.s3 = &state; + RequireThrows([&] { SSL_get0_alpn_selected_nid_postfix(connection.Ssl(), &data, &length); }); + state.internal = &stateInternal; + Fill(stateInternal, 0x70); + stateInternal.alpnSelected = protocol; + stateInternal.alpnSelectedLength = 2; + data = nullptr; + SSL_get0_alpn_selected_nid_postfix(connection.Ssl(), &data, &length); + Require(data == protocol && length == 2); + stateInternal.alpnSelected = nullptr; + stateInternal.alpnSelectedLength = 0; + SSL_get0_alpn_selected_nid_postfix(connection.Ssl(), &data, &length); + Require(data == nullptr && length == 0); + connection.ssl.s3 = nullptr; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); +} + +static void TestSetConnectState() { + int accept = 0; + int connect = 0; + int context = 0; + TestMethodInternal methodInternal {}; + TestMethod method {}; + Fill(methodInternal, 0x30); + methodInternal.accept = &accept; + methodInternal.connect = &connect; + method.internal = &methodInternal; + Connection connection; + connection.ssl.method = &method; + connection.ssl.server = 1; + connection.internal.shutdown = 3; + connection.internal.state = 0x1234; + Connection expected = connection; + RequireThrows([] { SSL_set_connect_state_nid_postfix(nullptr); }); + SSL_set_connect_state_nid_postfix(connection.Ssl()); + expected.ssl.server = 0; + expected.internal.shutdown = 0; + expected.internal.state = 0x5000; + expected.internal.handshakeFunc = &connect; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + methodInternal.connect = nullptr; + connection.internal.handshakeFunc = &accept; + SSL_set_connect_state_nid_postfix(connection.Ssl()); + Require(connection.internal.handshakeFunc == nullptr); + void** contexts[] = {&connection.ssl.encReadCtx, &connection.ssl.readHash, &connection.internal.encWriteCtx, + &connection.internal.aeadReadCtx, &connection.internal.aeadWriteCtx, &connection.internal.writeHash}; + for (void** slot : contexts) { + connection.ssl.server = 1; + connection.internal.shutdown = 2; + connection.internal.state = 0x1234; + *slot = &context; + const Connection before = connection; + RequireThrows([&] { SSL_set_connect_state_nid_postfix(connection.Ssl()); }); + Require(Same(connection.ssl, before.ssl) && Same(connection.internal, before.internal)); + *slot = nullptr; + } + method.internal = nullptr; + RequireThrows([&] { SSL_set_connect_state_nid_postfix(connection.Ssl()); }); + connection.ssl.method = nullptr; + RequireThrows([&] { SSL_set_connect_state_nid_postfix(connection.Ssl()); }); + connection.ssl.method = &method; + method.internal = &methodInternal; + connection.ssl.internal = nullptr; + RequireThrows([&] { SSL_set_connect_state_nid_postfix(connection.Ssl()); }); + Require(connection.ssl.server == 1); +} + +static void TestControlReadAhead() { + Connection connection; + Connection expected = connection; + RequireThrows([] { SSL_ctrl_nid_postfix(nullptr, 40, 0, nullptr); }); + connection.internal.readAhead = -1; + expected.internal.readAhead = -1; + Require(Control(connection, 40, 5) == -1); + Require(Control(connection, 41, INT64_C(0x123456789ABCDEF0)) == -1); + expected.internal.readAhead = static_cast(0x9ABCDEF0u); + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + Require(Control(connection, 40, 0) == INT64_C(-1698898192)); + Require(Control(connection, 41, 1) == INT64_C(-1698898192)); + Require(Control(connection, 40, 0) == 1); + connection.ssl.internal = nullptr; + RequireThrows([&] { Control(connection, 40, 0); }); + RequireThrows([&] { Control(connection, 41, 0); }); +} + +static void TestControlMessageCallbackArgument() { + int argument = 0; + Connection connection; + Connection expected = connection; + Require(Control(connection, 16, 9, &argument) == 1); + expected.internal.msgCallbackArg = &argument; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + Require(Control(connection, 16, 0, nullptr) == 1); + Require(connection.internal.msgCallbackArg == nullptr); + connection.ssl.internal = nullptr; + RequireThrows([&] { Control(connection, 16, 0, &argument); }); +} + +static void TestControlOptionsAndMode() { + Connection connection; + connection.internal.options = 0x4; + connection.internal.mode = 0; + Connection expected = connection; + Require(Control(connection, 32, 0x10) == 0x14); + Require(Control(connection, 32, INT64_C(0x100000000)) == INT64_C(0x100000014)); + Require(Control(connection, 77, 0x4) == INT64_C(0x100000010)); + Require(Control(connection, 32, 0) == INT64_C(0x100000010)); + Require(Control(connection, 32, -1) == -1); + Require(Control(connection, 77, -1) == 0); + Require(Control(connection, 33, 0x2) == 0x2); + Require(Control(connection, 33, INT64_C(0x800000000)) == INT64_C(0x800000002)); + Require(Control(connection, 78, 0x2) == INT64_C(0x800000000)); + Require(Control(connection, 78, -1) == 0); + Require(Control(connection, 33, 0x5) == 0x5); + expected.internal.options = 0; + expected.internal.mode = 0x5; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + connection.ssl.internal = nullptr; + RequireThrows([&] { Control(connection, 32, 1); }); + RequireThrows([&] { Control(connection, 77, 1); }); + RequireThrows([&] { Control(connection, 33, 1); }); + RequireThrows([&] { Control(connection, 78, 1); }); +} + +static void TestControlMaxCertList() { + Connection connection; + connection.internal.maxCertList = 102400; + Connection expected = connection; + Require(Control(connection, 50, 7) == 102400); + Require(Control(connection, 51, INT64_C(0x123456789)) == 102400); + Require(Control(connection, 50, 0) == INT64_C(0x123456789)); + Require(Control(connection, 51, -5) == INT64_C(0x123456789)); + expected.internal.maxCertList = -5; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + connection.ssl.internal = nullptr; + RequireThrows([&] { Control(connection, 50, 0); }); + RequireThrows([&] { Control(connection, 51, 0); }); +} + +static void TestControlMtu() { + TestMethodInternal methodInternal {}; + TestMethod method {}; + TestDtls1Internal dtlsInternal {}; + TestDtls1 dtls {}; + Fill(dtlsInternal, 0x50); + method.internal = &methodInternal; + methodInternal.version = 0x0303; + Connection connection; + connection.ssl.internal = nullptr; + Connection expected = connection; + Require(Control(connection, 17, 227) == 0); + Require(Control(connection, 17, -1) == 0); + Require(Control(connection, 17, INT64_MIN) == 0); + Require(SSL_ctrl_nid_postfix(nullptr, 17, 227, nullptr) == 0); + Require(SSL_ctrl_nid_postfix(nullptr, 17, -1, nullptr) == 0); + RequireThrows([] { SSL_ctrl_nid_postfix(nullptr, 17, 228, nullptr); }); + RequireThrows([&] { Control(connection, 17, 228); }); + connection.ssl.method = &method; + expected.ssl.method = &method; + Require(Control(connection, 17, 228) == 0); + Require(Control(connection, 17, 1500) == 0); + Require(Same(connection.ssl, expected.ssl)); + methodInternal.version = 0xFEFF; + RequireThrows([&] { Control(connection, 17, 1500); }); + connection.ssl.d1 = &dtls; + RequireThrows([&] { Control(connection, 17, 1500); }); + dtls.internal = &dtlsInternal; + TestDtls1Internal dtlsExpected = dtlsInternal; + Require(Control(connection, 17, 227) == 0); + Require(Same(dtlsInternal, dtlsExpected)); + Require(Control(connection, 17, 228) == 228); + dtlsExpected.mtu = 228; + Require(Same(dtlsInternal, dtlsExpected)); + Require(Control(connection, 17, INT64_C(0x100000500)) == INT64_C(0x100000500)); + dtlsExpected.mtu = 0x500; + Require(Same(dtlsInternal, dtlsExpected)); + method.internal = nullptr; + RequireThrows([&] { Control(connection, 17, 1500); }); +} + +static void TestControlMaxSendFragment() { + Connection connection; + connection.ssl.internal = nullptr; + connection.ssl.maxSendFragment = 16384; + Connection expected = connection; + for (std::int64_t argument : {INT64_C(511), INT64_C(16385), INT64_C(0), INT64_C(-1), INT64_C(0x100000200), INT64_C(1) << 60}) { + Require(Control(connection, 52, argument) == 0); + Require(Same(connection.ssl, expected.ssl)); + } + Require(SSL_ctrl_nid_postfix(nullptr, 52, 511, nullptr) == 0); + Require(SSL_ctrl_nid_postfix(nullptr, 52, 16385, nullptr) == 0); + RequireThrows([] { SSL_ctrl_nid_postfix(nullptr, 52, 512, nullptr); }); + Require(Control(connection, 52, 512) == 1); + expected.ssl.maxSendFragment = 512; + Require(Same(connection.ssl, expected.ssl)); + Require(Control(connection, 52, 16384) == 1); + expected.ssl.maxSendFragment = 16384; + Require(Same(connection.ssl, expected.ssl)); + Require(Control(connection, 52, 4096) == 1); + expected.ssl.maxSendFragment = 4096; + Require(Same(connection.ssl, expected.ssl)); +} + +static void TestControlRenegotiationInfoSupport() { + TestSsl3Internal stateInternal {}; + TestSsl3 state {}; + Connection connection; + connection.ssl.internal = nullptr; + Connection expected = connection; + RequireThrows([] { SSL_ctrl_nid_postfix(nullptr, 76, 0, nullptr); }); + Require(Control(connection, 76, 1) == 0); + connection.ssl.s3 = &state; + RequireThrows([&] { Control(connection, 76, 0); }); + state.internal = &stateInternal; + Fill(stateInternal, 0x60); + stateInternal.sendConnectionBinding = 1; + Require(Control(connection, 76, 0) == 1); + stateInternal.sendConnectionBinding = -1; + Require(Control(connection, 76, 0) == -1); + stateInternal.sendConnectionBinding = 0; + Require(Control(connection, 76, 0) == 0); + connection.ssl.s3 = nullptr; + Require(Same(connection.ssl, expected.ssl)); +} + +static void TestControlUnhandled() { + Connection connection; + const Connection expected = connection; + RequireThrows([] { SSL_ctrl_nid_postfix(nullptr, 55, 1, nullptr); }); + for (int command : {16, 32, 33, 50, 51, 77, 78}) RequireThrows([&] { SSL_ctrl_nid_postfix(nullptr, command, 1, nullptr); }); + for (int command : {55, 0, -1, 1, 15, 18, 34, 42, 53, 75, 79, 0x10000 + 40}) { + RequireThrows([&] { Control(connection, command, 1); }); + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + } +} + +int main() { + TestSetVerify(); + TestGetAlpnSelected(); + TestSetConnectState(); + TestControlReadAhead(); + TestControlMessageCallbackArgument(); + TestControlOptionsAndMode(); + TestControlMaxCertList(); + TestControlMtu(); + TestControlMaxSendFragment(); + TestControlRenegotiationInfoSupport(); + TestControlUnhandled(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index d262d7c7d2..10b227ba72 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 335 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 331 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -13,6 +13,7 @@ - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. - [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown, SSL_want](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_set_verify, SSL_get0_alpn_selected, SSL_set_connect_state, SSL_ctrl](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. `SSL_ctrl` throws for the commands LibreSSL passes to `ssl3_ctrl` or `dtls1_ctrl`, including `SSL_set_tlsext_host_name`. `SSL_set_connect_state` throws when a cipher or hash context is set, which LibreSSL frees; they are null before a handshake. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export creates a connection or `SSL_METHOD` yet, so only structures the caller builds reach them. Null pointers that LibreSSL dereferences throw ### Functional From 7a4c7771b7668cab66384f3cb6aacad21a178e8b Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:40:47 +0200 Subject: [PATCH 026/612] feat(libSceLibreSsl): implement X509_NAME_get_entry --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 ++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 44 +++++++++++ core/libs/tests/GuestLibreSslX509Name.cpp | 79 +++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 3 +- 5 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/X509Name.cpp create mode 100644 core/libs/tests/GuestLibreSslX509Name.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 7a8ad2999c..98d20e978c 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509Name.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -88,6 +88,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_ocsp PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_x509_name_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslX509Name.cpp) + target_include_directories(guest_libressl_x509_name_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_x509_name_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_x509_name_tests) + add_test(NAME guest_libressl_x509_name COMMAND guest_libressl_x509_name_tests) + set_tests_properties(guest_libressl_x509_name PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_x509_name PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 93387ef0ba..3f068dc957 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1513,11 +1513,6 @@ int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void) { return 0; } -int APS5_VABI X509_NAME_get_entry_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp new file mode 100644 index 0000000000..85fb8215a6 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct LibreSslX509NameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct LibreSslX509Name { + LibreSslStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + +static_assert(sizeof(LibreSslStack) == 32); +static_assert(offsetof(LibreSslStack, data) == 8); +static_assert(sizeof(LibreSslX509NameEntry) == 24); +static_assert(offsetof(LibreSslX509NameEntry, value) == 8); +static_assert(sizeof(LibreSslX509Name) == 40); +static_assert(offsetof(LibreSslX509Name, entries) == 0); + +extern "C" { + +LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc) { + if (!name || loc < 0 || !name->entries || name->entries->num <= loc) return nullptr; + if (!name->entries->data) throw std::invalid_argument(std::string(__func__) + ": null entry array"); + return reinterpret_cast(name->entries->data[loc]); +} + +} diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp new file mode 100644 index 0000000000..ac574ce133 --- /dev/null +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -0,0 +1,79 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct TestStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct TestNameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct TestName { + TestStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + +static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); +static_assert(sizeof(TestNameEntry) == 24 && offsetof(TestNameEntry, value) == 8); +static_assert(sizeof(TestName) == 40); + +extern "C" { +TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL X509_NAME check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetEntry() { + TestNameEntry first {}; + TestNameEntry second {}; + char* slots[] {reinterpret_cast(&first), reinterpret_cast(&second), nullptr}; + TestStack entries {2, slots, 0, 3, nullptr}; + TestName name {}; + Require(X509_NAME_get_entry_nid_postfix(nullptr, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + name.entries = &entries; + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == &first); + Require(X509_NAME_get_entry_nid_postfix(&name, 1) == &second); + Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, INT_MIN) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, INT_MAX) == nullptr); + entries.num = 0; + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + entries.num = 3; + Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + entries.data = nullptr; + RequireThrows([&] { X509_NAME_get_entry_nid_postfix(&name, 0); }); + Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); +} + +int main() { + TestGetEntry(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index bd201c0765..814f0541b3 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 342 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 341 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,6 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. +- [X509_NAME_get_entry](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names the caller builds reach it. A positive entry count with a null entry array, which LibreSSL dereferences, throws ### Functional From c179b4a5cfb3e69bb52b4586e6a3e325241b0e8e Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:42:33 +0200 Subject: [PATCH 027/612] feat(libSceLibreSsl): implement X509_NAME_ENTRY_get_data --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 5 +++++ core/libs/tests/GuestLibreSslX509Name.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 3f068dc957..86cad24caa 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1508,11 +1508,6 @@ int APS5_VABI X509_LOOKUP_file_nid_postfix(void) { return 0; } -int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp index 85fb8215a6..57efc6993b 100644 --- a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -41,4 +41,9 @@ LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX return reinterpret_cast(name->entries->data[loc]); } +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry* entry) { + if (!entry) return nullptr; + return entry->value; +} + } diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp index ac574ce133..1dea64955e 100644 --- a/core/libs/tests/GuestLibreSslX509Name.cpp +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -35,6 +35,7 @@ static_assert(sizeof(TestName) == 40); extern "C" { TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const TestNameEntry*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -73,7 +74,20 @@ static void TestGetEntry() { Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); } +static void TestEntryGetData() { + int value = 0; + TestNameEntry entry {}; + Require(X509_NAME_ENTRY_get_data_nid_postfix(nullptr) == nullptr); + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == nullptr); + entry.value = &value; + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + entry.object = &entry; + entry.set = 7; + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); +} + int main() { TestGetEntry(); + TestEntryGetData(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 814f0541b3..633d785fe0 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 341 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 340 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [X509_NAME_get_entry](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names the caller builds reach it. A positive entry count with a null entry array, which LibreSSL dereferences, throws +- [X509_NAME_get_entry, X509_NAME_ENTRY_get_data](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names and entries the caller builds reach them. A positive entry count with a null entry array, which LibreSSL dereferences, throws ### Functional From aa4fd175d5b4ffa4aee342f2c5becc25ba961d50 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 06:00:22 +0100 Subject: [PATCH 028/612] refactor(libSceLibreSsl): declare the X509_NAME exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 27 ++++++++++++- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 24 +---------- core/libs/tests/GuestLibreSslX509Name.cpp | 40 ++++++++++--------- 3 files changed, 48 insertions(+), 43 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index c7edd129f6..11cc769dce 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -51,6 +51,29 @@ struct Sha512Context { std::uint32_t num, mdLength; }; +struct LibreSslStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct LibreSslX509NameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct LibreSslX509Name { + LibreSslStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -364,8 +387,8 @@ int APS5_VABI X509_EXTENSION_get_data_nid_postfix(void); int APS5_VABI X509_EXTENSION_get_object_nid_postfix(void); int APS5_VABI X509_INFO_free_nid_postfix(void); int APS5_VABI X509_LOOKUP_file_nid_postfix(void); -int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void); -int APS5_VABI X509_NAME_get_entry_nid_postfix(void); +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry* entry); +LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc); int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void); int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(void); int APS5_VABI X509_NAME_oneline_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp index 57efc6993b..f68342f636 100644 --- a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -1,31 +1,9 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslStack { - int num; - char** data; - int sorted; - int numAlloc; - void* comp; -}; - -struct LibreSslX509NameEntry { - void* object; - void* value; - int set; - int size; -}; - -struct LibreSslX509Name { - LibreSslStack* entries; - int modified; - void* bytes; - unsigned char* canonEnc; - int canonEnclen; -}; - static_assert(sizeof(LibreSslStack) == 32); static_assert(offsetof(LibreSslStack, data) == 8); static_assert(sizeof(LibreSslX509NameEntry) == 24); diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp index 1dea64955e..365b16002e 100644 --- a/core/libs/tests/GuestLibreSslX509Name.cpp +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -33,9 +34,12 @@ static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); static_assert(sizeof(TestNameEntry) == 24 && offsetof(TestNameEntry, value) == 8); static_assert(sizeof(TestName) == 40); -extern "C" { -TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); -void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const TestNameEntry*); +static const LibreSslX509Name* Name(const TestName* name) { + return reinterpret_cast(name); +} + +static const LibreSslX509NameEntry* Entry(const TestNameEntry* entry) { + return reinterpret_cast(entry); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -56,34 +60,34 @@ static void TestGetEntry() { TestStack entries {2, slots, 0, 3, nullptr}; TestName name {}; Require(X509_NAME_get_entry_nid_postfix(nullptr, 0) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), -1) == nullptr); name.entries = &entries; - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == &first); - Require(X509_NAME_get_entry_nid_postfix(&name, 1) == &second); - Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, INT_MIN) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, INT_MAX) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == Entry(&first)); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 1) == Entry(&second)); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), INT_MIN) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), INT_MAX) == nullptr); entries.num = 0; - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == nullptr); entries.num = 3; - Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 2) == nullptr); entries.data = nullptr; - RequireThrows([&] { X509_NAME_get_entry_nid_postfix(&name, 0); }); - Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); + RequireThrows([&] { X509_NAME_get_entry_nid_postfix(Name(&name), 0); }); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 3) == nullptr); } static void TestEntryGetData() { int value = 0; TestNameEntry entry {}; Require(X509_NAME_ENTRY_get_data_nid_postfix(nullptr) == nullptr); - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == nullptr); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == nullptr); entry.value = &value; - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == &value); entry.object = &entry; entry.set = 7; - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == &value); } int main() { From 6a50a046542f69190e8ed739f0d59249ac31d763 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:53:15 +0200 Subject: [PATCH 029/612] feat(libSceLibreSsl): implement SSL_get_SSL_CTX --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 +++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 78 +++++++++++++++++++++ core/libs/tests/GuestLibreSslSsl.cpp | 65 +++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 3 +- 5 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/Ssl.cpp create mode 100644 core/libs/tests/GuestLibreSslSsl.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 7a8ad2999c..b3b0fa1bdc 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ssl.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -88,6 +88,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_ocsp PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_ssl_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSsl.cpp) + target_include_directories(guest_libressl_ssl_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_tests) + add_test(NAME guest_libressl_ssl COMMAND guest_libressl_ssl_tests) + set_tests_properties(guest_libressl_ssl PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 93387ef0ba..9fd20f2f5f 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1198,11 +1198,6 @@ int APS5_VABI SSL_get1_session_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_SSL_CTX_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_get_certificate_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp new file mode 100644 index 0000000000..c817055b09 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct LibreSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + int server; + void* s3; + void* d1; + void* param; + void* cipherList; + void* cert; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* session; + int verifyMode; + int error; + int errorCode; + void* ctx; + std::int64_t verifyResult; + int references; + int clientVersion; + unsigned int maxSendFragment; + char* tlsextHostname; + int tlsextStatusType; + void* initialCtx; + void* encReadCtx; + void* readHash; + LibreSslSslInternal* internal; +}; + +static_assert(sizeof(LibreSslSslInternal) == 432); +static_assert(offsetof(LibreSslSslInternal, rwstate) == 156); +static_assert(offsetof(LibreSslSslInternal, shutdown) == 168); +static_assert(sizeof(LibreSsl) == 232); +static_assert(offsetof(LibreSsl, rbio) == 16); +static_assert(offsetof(LibreSsl, wbio) == 24); +static_assert(offsetof(LibreSsl, session) == 128); +static_assert(offsetof(LibreSsl, ctx) == 152); +static_assert(offsetof(LibreSsl, verifyResult) == 160); +static_assert(offsetof(LibreSsl, internal) == 224); + +namespace { + +const LibreSsl& Connection(const LibreSsl* ssl, const char* function) { + if (!ssl) throw std::invalid_argument(std::string(function) + ": null connection"); + return *ssl; +} + +const LibreSslSslInternal& Internal(const LibreSsl* ssl, const char* function) { + const LibreSsl& connection = Connection(ssl, function); + if (!connection.internal) throw std::invalid_argument(std::string(function) + ": null internal state"); + return *connection.internal; +} + +} + +extern "C" { + +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).ctx; +} + +} diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp new file mode 100644 index 0000000000..db130edf56 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -0,0 +1,65 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct TestSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct TestSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + unsigned char beforeSession[88]; + void* session; + unsigned char beforeCtx[16]; + void* ctx; + std::int64_t verifyResult; + unsigned char beforeInternal[56]; + TestSslInternal* internal; +}; + +static_assert(sizeof(TestSslInternal) == 432 && offsetof(TestSslInternal, rwstate) == 156 && offsetof(TestSslInternal, shutdown) == 168); +static_assert(sizeof(TestSsl) == 232 && offsetof(TestSsl, session) == 128 && offsetof(TestSsl, ctx) == 152); +static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, internal) == 224); + +extern "C" { +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetSslCtx() { + int context = 0; + int session = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_SSL_CTX_nid_postfix(nullptr); }); + Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == nullptr); + ssl.ctx = &context; + ssl.session = &session; + Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); +} + +int main() { + TestGetSslCtx(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index bd201c0765..084f154a32 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 342 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 341 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,6 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. +- [SSL_get_SSL_CTX](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach it. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 5627a3caca883eaf5b457487451b8b72b3fc81f8 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:17 +0200 Subject: [PATCH 030/612] feat(libSceLibreSsl): implement SSL_get_rbio --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 13 +++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 9fd20f2f5f..1b17d655dc 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1248,11 +1248,6 @@ int APS5_VABI SSL_get_privatekey_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_rbio_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_get_servername_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index c817055b09..3069c3cc5d 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -75,4 +75,8 @@ void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).ctx; } +void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).rbio; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index db130edf56..95fa53ff94 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -35,6 +35,7 @@ static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, intern extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); +void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -59,7 +60,19 @@ static void TestGetSslCtx() { Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); } +static void TestGetRbio() { + int read = 0; + int write = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_rbio_nid_postfix(nullptr); }); + Require(SSL_get_rbio_nid_postfix(&ssl) == nullptr); + ssl.rbio = &read; + ssl.wbio = &write; + Require(SSL_get_rbio_nid_postfix(&ssl) == &read); +} + int main() { TestGetSslCtx(); + TestGetRbio(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 084f154a32..dbe1b6fb6f 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 341 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 340 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach it. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From d7f54c18b2f6f8dc0f86e1ae7d85381d41c19fb9 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:27 +0200 Subject: [PATCH 031/612] feat(libSceLibreSsl): implement SSL_get_wbio --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 15 +++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 1b17d655dc..148c7143bf 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1268,11 +1268,6 @@ int APS5_VABI SSL_get_verify_result_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_wbio_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 3069c3cc5d..2c544b2aa2 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -79,4 +79,8 @@ void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).rbio; } +void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).wbio; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 95fa53ff94..6547b2aac1 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -36,6 +36,7 @@ static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, intern extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); +void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -71,8 +72,22 @@ static void TestGetRbio() { Require(SSL_get_rbio_nid_postfix(&ssl) == &read); } +static void TestGetWbio() { + int read = 0; + int write = 0; + int buffer = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_wbio_nid_postfix(nullptr); }); + Require(SSL_get_wbio_nid_postfix(&ssl) == nullptr); + ssl.rbio = &read; + ssl.wbio = &write; + ssl.bbio = &buffer; + Require(SSL_get_wbio_nid_postfix(&ssl) == &write); +} + int main() { TestGetSslCtx(); TestGetRbio(); + TestGetWbio(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index dbe1b6fb6f..7d223dba48 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 340 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 339 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX, SSL_get_rbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 4188aee68b1749f37deb27699255800479ef1d9f Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:37 +0200 Subject: [PATCH 032/612] feat(libSceLibreSsl): implement SSL_version --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 148c7143bf..db2ca375cf 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1353,11 +1353,6 @@ int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void) { return 0; } -int APS5_VABI SSL_version_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_want_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 2c544b2aa2..a980c12199 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -83,4 +83,8 @@ void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).wbio; } +int APS5_VABI SSL_version_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).version; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 6547b2aac1..10945d54ba 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -37,6 +37,7 @@ extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); +int APS5_VABI SSL_version_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -85,9 +86,20 @@ static void TestGetWbio() { Require(SSL_get_wbio_nid_postfix(&ssl) == &write); } +static void TestVersion() { + TestSsl ssl {}; + RequireThrows([] { SSL_version_nid_postfix(nullptr); }); + Require(SSL_version_nid_postfix(&ssl) == 0); + ssl.version = 0x0303; + Require(SSL_version_nid_postfix(&ssl) == 0x0303); + ssl.version = 0xFEFF; + Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); + TestVersion(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 7d223dba48..7621080e28 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 339 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 338 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From c0bca5bab533fec250a4399883237428b25de6ed Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:47 +0200 Subject: [PATCH 033/612] feat(libSceLibreSsl): implement SSL_get_verify_result --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index db2ca375cf..2b47fb05ae 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1263,11 +1263,6 @@ int APS5_VABI SSL_get_shutdown_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_verify_result_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index a980c12199..89c984e2d6 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -87,4 +87,8 @@ int APS5_VABI SSL_version_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).version; } +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).verifyResult; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 10945d54ba..2d1a43528c 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -38,6 +38,7 @@ void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -96,10 +97,23 @@ static void TestVersion() { Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); } +static void TestVerifyResult() { + TestSsl ssl {}; + RequireThrows([] { SSL_get_verify_result_nid_postfix(nullptr); }); + Require(SSL_get_verify_result_nid_postfix(&ssl) == 0); + ssl.verifyResult = 20; + Require(SSL_get_verify_result_nid_postfix(&ssl) == 20); + ssl.verifyResult = INT64_C(0x100000002); + Require(SSL_get_verify_result_nid_postfix(&ssl) == INT64_C(0x100000002)); + ssl.verifyResult = -1; + Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); TestVersion(); + TestVerifyResult(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 7621080e28..504bcb1137 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 338 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 337 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 52a936c73e70fce332885d4f3180c1d86b1234e6 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:57 +0200 Subject: [PATCH 034/612] feat(libSceLibreSsl): implement SSL_get_shutdown --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 16 ++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 2b47fb05ae..46e600c3b8 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1258,11 +1258,6 @@ int APS5_VABI SSL_get_session_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_shutdown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 89c984e2d6..cb226a9590 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -91,4 +91,8 @@ std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).verifyResult; } +int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSsl* ssl) { + return Internal(ssl, __func__).shutdown; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 2d1a43528c..2bca9bdbdb 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -39,6 +39,7 @@ void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); +int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -109,11 +110,26 @@ static void TestVerifyResult() { Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); } +static void TestGetShutdown() { + TestSslInternal internal {}; + TestSsl ssl {}; + RequireThrows([] { SSL_get_shutdown_nid_postfix(nullptr); }); + RequireThrows([&] { SSL_get_shutdown_nid_postfix(&ssl); }); + ssl.internal = &internal; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 0); + internal.rwstate = 3; + internal.shutdown = 1; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 1); + internal.shutdown = 3; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); TestVersion(); TestVerifyResult(); + TestGetShutdown(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 504bcb1137..9484a7e21a 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 337 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 336 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 19191150c9f29b4fdee42b8fa6e2864b1fe2ae7f Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:55:07 +0200 Subject: [PATCH 035/612] feat(libSceLibreSsl): implement SSL_want --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 16 ++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 46e600c3b8..d23cfdf71c 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1343,11 +1343,6 @@ int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void) { return 0; } -int APS5_VABI SSL_want_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_write_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index cb226a9590..69780d7342 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -95,4 +95,8 @@ int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSsl* ssl) { return Internal(ssl, __func__).shutdown; } +int APS5_VABI SSL_want_nid_postfix(const LibreSsl* ssl) { + return Internal(ssl, __func__).rwstate; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 2bca9bdbdb..04abd2ad93 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -40,6 +40,7 @@ void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); +int APS5_VABI SSL_want_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -124,6 +125,20 @@ static void TestGetShutdown() { Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); } +static void TestWant() { + TestSslInternal internal {}; + TestSsl ssl {}; + RequireThrows([] { SSL_want_nid_postfix(nullptr); }); + RequireThrows([&] { SSL_want_nid_postfix(&ssl); }); + ssl.internal = &internal; + Require(SSL_want_nid_postfix(&ssl) == 0); + internal.rwstate = 1; + Require(SSL_want_nid_postfix(&ssl) == 1); + internal.shutdown = 2; + internal.rwstate = 3; + Require(SSL_want_nid_postfix(&ssl) == 3); +} + int main() { TestGetSslCtx(); TestGetRbio(); @@ -131,5 +146,6 @@ int main() { TestVersion(); TestVerifyResult(); TestGetShutdown(); + TestWant(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 9484a7e21a..d262d7c7d2 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 336 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 335 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown, SSL_want](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From b7ceda9cb4f78466f163659d9eb21215c26acc47 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 06:01:21 +0100 Subject: [PATCH 036/612] refactor(libSceLibreSsl): declare the SSL accessor exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 53 +++++++++++++++++--- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 40 +-------------- core/libs/tests/GuestLibreSslSsl.cpp | 53 +++++++++----------- 3 files changed, 71 insertions(+), 75 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index c7edd129f6..9c3ba0042b 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -51,6 +51,45 @@ struct Sha512Context { std::uint32_t num, mdLength; }; +struct LibreSslSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct LibreSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + int server; + void* s3; + void* d1; + void* param; + void* cipherList; + void* cert; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* session; + int verifyMode; + int error; + int errorCode; + void* ctx; + std::int64_t verifyResult; + int references; + int clientVersion; + unsigned int maxSendFragment; + char* tlsextHostname; + int tlsextStatusType; + void* initialCtx; + void* encReadCtx; + void* readHash; + LibreSslSslInternal* internal; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -302,7 +341,7 @@ int APS5_VABI SSL_free_nid_postfix(void); int APS5_VABI SSL_get0_alpn_selected_nid_postfix(void); int APS5_VABI SSL_get0_param_nid_postfix(void); int APS5_VABI SSL_get1_session_nid_postfix(void); -int APS5_VABI SSL_get_SSL_CTX_nid_postfix(void); +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_get_certificate_nid_postfix(void); int APS5_VABI SSL_get_current_cipher_nid_postfix(void); int APS5_VABI SSL_get_error_nid_postfix(void); @@ -313,12 +352,12 @@ int APS5_VABI SSL_get_fd_nid_postfix(void); int APS5_VABI SSL_get_peer_cert_chain_nid_postfix(void); int APS5_VABI SSL_get_peer_certificate_nid_postfix(void); int APS5_VABI SSL_get_privatekey_nid_postfix(void); -int APS5_VABI SSL_get_rbio_nid_postfix(void); +void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_get_servername_nid_postfix(void); int APS5_VABI SSL_get_session_nid_postfix(void); -int APS5_VABI SSL_get_shutdown_nid_postfix(void); -int APS5_VABI SSL_get_verify_result_nid_postfix(void); -int APS5_VABI SSL_get_wbio_nid_postfix(void); +int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSsl* ssl); +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSsl* ssl); +void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_library_init_nid_postfix(void); int APS5_VABI SSL_load_error_strings_nid_postfix(void); int APS5_VABI SSL_new_nid_postfix(void); @@ -336,8 +375,8 @@ int APS5_VABI SSL_set_verify_nid_postfix(void); int APS5_VABI SSL_shutdown_nid_postfix(void); int APS5_VABI SSL_use_PrivateKey_ASN1_nid_postfix(void); int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void); -int APS5_VABI SSL_version_nid_postfix(void); -int APS5_VABI SSL_want_nid_postfix(void); +int APS5_VABI SSL_version_nid_postfix(const LibreSsl* ssl); +int APS5_VABI SSL_want_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_write_nid_postfix(void); int APS5_VABI SSLeay_nid_postfix(void); int APS5_VABI SSLv23_client_method_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 69780d7342..74c3e5aa99 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -1,48 +1,10 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslSslInternal { - unsigned char beforeRwstate[156]; - int rwstate; - unsigned char beforeShutdown[8]; - int shutdown; - unsigned char rest[260]; -}; - -struct LibreSsl { - int version; - const void* method; - void* rbio; - void* wbio; - void* bbio; - int server; - void* s3; - void* d1; - void* param; - void* cipherList; - void* cert; - unsigned int sidCtxLength; - unsigned char sidCtx[32]; - void* session; - int verifyMode; - int error; - int errorCode; - void* ctx; - std::int64_t verifyResult; - int references; - int clientVersion; - unsigned int maxSendFragment; - char* tlsextHostname; - int tlsextStatusType; - void* initialCtx; - void* encReadCtx; - void* readHash; - LibreSslSslInternal* internal; -}; - static_assert(sizeof(LibreSslSslInternal) == 432); static_assert(offsetof(LibreSslSslInternal, rwstate) == 156); static_assert(offsetof(LibreSslSslInternal, shutdown) == 168); diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 04abd2ad93..e2549fab2f 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -33,14 +34,8 @@ static_assert(sizeof(TestSslInternal) == 432 && offsetof(TestSslInternal, rwstat static_assert(sizeof(TestSsl) == 232 && offsetof(TestSsl, session) == 128 && offsetof(TestSsl, ctx) == 152); static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, internal) == 224); -extern "C" { -void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); -void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); -void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); -int APS5_VABI SSL_version_nid_postfix(const TestSsl*); -std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); -int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); -int APS5_VABI SSL_want_nid_postfix(const TestSsl*); +static const LibreSsl* Ssl(const TestSsl* ssl) { + return reinterpret_cast(ssl); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -59,10 +54,10 @@ static void TestGetSslCtx() { int session = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_SSL_CTX_nid_postfix(nullptr); }); - Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == nullptr); + Require(SSL_get_SSL_CTX_nid_postfix(Ssl(&ssl)) == nullptr); ssl.ctx = &context; ssl.session = &session; - Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); + Require(SSL_get_SSL_CTX_nid_postfix(Ssl(&ssl)) == &context); } static void TestGetRbio() { @@ -70,10 +65,10 @@ static void TestGetRbio() { int write = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_rbio_nid_postfix(nullptr); }); - Require(SSL_get_rbio_nid_postfix(&ssl) == nullptr); + Require(SSL_get_rbio_nid_postfix(Ssl(&ssl)) == nullptr); ssl.rbio = &read; ssl.wbio = &write; - Require(SSL_get_rbio_nid_postfix(&ssl) == &read); + Require(SSL_get_rbio_nid_postfix(Ssl(&ssl)) == &read); } static void TestGetWbio() { @@ -82,61 +77,61 @@ static void TestGetWbio() { int buffer = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_wbio_nid_postfix(nullptr); }); - Require(SSL_get_wbio_nid_postfix(&ssl) == nullptr); + Require(SSL_get_wbio_nid_postfix(Ssl(&ssl)) == nullptr); ssl.rbio = &read; ssl.wbio = &write; ssl.bbio = &buffer; - Require(SSL_get_wbio_nid_postfix(&ssl) == &write); + Require(SSL_get_wbio_nid_postfix(Ssl(&ssl)) == &write); } static void TestVersion() { TestSsl ssl {}; RequireThrows([] { SSL_version_nid_postfix(nullptr); }); - Require(SSL_version_nid_postfix(&ssl) == 0); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0); ssl.version = 0x0303; - Require(SSL_version_nid_postfix(&ssl) == 0x0303); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0x0303); ssl.version = 0xFEFF; - Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0xFEFF); } static void TestVerifyResult() { TestSsl ssl {}; RequireThrows([] { SSL_get_verify_result_nid_postfix(nullptr); }); - Require(SSL_get_verify_result_nid_postfix(&ssl) == 0); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == 0); ssl.verifyResult = 20; - Require(SSL_get_verify_result_nid_postfix(&ssl) == 20); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == 20); ssl.verifyResult = INT64_C(0x100000002); - Require(SSL_get_verify_result_nid_postfix(&ssl) == INT64_C(0x100000002)); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == INT64_C(0x100000002)); ssl.verifyResult = -1; - Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == -1); } static void TestGetShutdown() { TestSslInternal internal {}; TestSsl ssl {}; RequireThrows([] { SSL_get_shutdown_nid_postfix(nullptr); }); - RequireThrows([&] { SSL_get_shutdown_nid_postfix(&ssl); }); + RequireThrows([&] { SSL_get_shutdown_nid_postfix(Ssl(&ssl)); }); ssl.internal = &internal; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 0); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 0); internal.rwstate = 3; internal.shutdown = 1; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 1); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 1); internal.shutdown = 3; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 3); } static void TestWant() { TestSslInternal internal {}; TestSsl ssl {}; RequireThrows([] { SSL_want_nid_postfix(nullptr); }); - RequireThrows([&] { SSL_want_nid_postfix(&ssl); }); + RequireThrows([&] { SSL_want_nid_postfix(Ssl(&ssl)); }); ssl.internal = &internal; - Require(SSL_want_nid_postfix(&ssl) == 0); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 0); internal.rwstate = 1; - Require(SSL_want_nid_postfix(&ssl) == 1); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 1); internal.shutdown = 2; internal.rwstate = 3; - Require(SSL_want_nid_postfix(&ssl) == 3); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 3); } int main() { From 48b6d116dd5764ad13df27964d16c537cfa28f23 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:00:27 +0200 Subject: [PATCH 037/612] feat(libSceLibreSsl): implement SSL_CTX_get_verify_mode --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 ++++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 53 ++++++++++++++++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 60 +++++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 3 +- 5 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/SslCtx.cpp create mode 100644 core/libs/tests/GuestLibreSslSslCtx.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 7a8ad2999c..f8432a316f 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SslCtx.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -88,6 +88,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_ocsp PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_ssl_ctx_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSslCtx.cpp) + target_include_directories(guest_libressl_ssl_ctx_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_ctx_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_ctx_tests) + add_test(NAME guest_libressl_ssl_ctx COMMAND guest_libressl_ssl_ctx_tests) + set_tests_properties(guest_libressl_ssl_ctx PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl_ctx PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 93387ef0ba..3c44d10bb6 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1008,11 +1008,6 @@ int APS5_VABI SSL_CTX_get_ex_new_index_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_load_verify_locations_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp new file mode 100644 index 0000000000..f4566a99a9 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct LibreSslCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + int references; + void* extraCerts; + int verifyMode; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* param; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + LibreSslCtxInternal* internal; +}; + +static_assert(sizeof(LibreSslCtxInternal) == 464); +static_assert(offsetof(LibreSslCtxInternal, quietShutdown) == 292); +static_assert(sizeof(LibreSslCtx) == 120); +static_assert(offsetof(LibreSslCtx, certStore) == 16); +static_assert(offsetof(LibreSslCtx, verifyMode) == 48); +static_assert(offsetof(LibreSslCtx, defaultPasswdCallback) == 96); +static_assert(offsetof(LibreSslCtx, defaultPasswdCallbackUserdata) == 104); +static_assert(offsetof(LibreSslCtx, internal) == 112); + +namespace { + +LibreSslCtx& Context(LibreSslCtx* ctx, const char* function) { + if (!ctx) throw std::invalid_argument(std::string(function) + ": null context"); + return *ctx; +} + +} + +extern "C" { + +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx) { + return Context(ctx, __func__).verifyMode; +} + +} diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp new file mode 100644 index 0000000000..acbcf82c94 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -0,0 +1,60 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +struct TestCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct TestCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + unsigned char beforeVerifyMode[16]; + int verifyMode; + unsigned char beforeCallback[44]; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + TestCtxInternal* internal; +}; + +static_assert(sizeof(TestCtxInternal) == 464 && offsetof(TestCtxInternal, quietShutdown) == 292); +static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && offsetof(TestCtx, defaultPasswdCallback) == 96); + +extern "C" { +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL_CTX check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetVerifyMode() { + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_get_verify_mode_nid_postfix(nullptr); }); + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0); + ctx.verifyMode = 0x03; + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0x03); + ctx.verifyMode = -1; + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); +} + +int main() { + TestGetVerifyMode(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index bd201c0765..cecb4e481b 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 342 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 341 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,6 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. +- [SSL_CTX_get_verify_mode](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach it, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From f3c2f306c957b9c7714c8251ac51adce7e70af2b Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:22 +0200 Subject: [PATCH 038/612] feat(libSceLibreSsl): implement SSL_CTX_get_cert_store --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 3c44d10bb6..57b6fb74b1 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -993,11 +993,6 @@ int APS5_VABI SSL_CTX_free_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_get_cert_store_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index f4566a99a9..446eff72de 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -50,4 +50,8 @@ int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx) { return Context(ctx, __func__).verifyMode; } +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx) { + return Context(ctx, __func__).certStore; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index acbcf82c94..df6c8d1c3d 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -31,6 +31,7 @@ static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && o extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -54,7 +55,20 @@ static void TestGetVerifyMode() { Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); } +static void TestGetCertStore() { + int store = 0; + int ciphers = 0; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_get_cert_store_nid_postfix(nullptr); }); + Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == nullptr); + ctx.cipherList = &ciphers; + ctx.certStore = &store; + ctx.sessionTimeout = -1; + Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); +} + int main() { TestGetVerifyMode(); + TestGetCertStore(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index cecb4e481b..1408175569 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 341 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 340 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_CTX_get_verify_mode](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach it, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 61898e906a5a63297573890487edce6b11d23fd4 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:31 +0200 Subject: [PATCH 039/612] feat(libSceLibreSsl): implement SSL_CTX_set_quiet_shutdown --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 6 ++++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 18 ++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 57b6fb74b1..2c8eac63f5 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1088,11 +1088,6 @@ int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 446eff72de..2a86c75d25 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -54,4 +54,10 @@ void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx) { return Context(ctx, __func__).certStore; } +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode) { + LibreSslCtx& context = Context(ctx, __func__); + if (!context.internal) throw std::invalid_argument(std::string(__func__) + ": null internal state"); + context.internal->quietShutdown = mode; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index df6c8d1c3d..38f3bb5fad 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -32,6 +32,7 @@ static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && o extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -67,8 +68,25 @@ static void TestGetCertStore() { Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); } +static void TestSetQuietShutdown() { + TestCtxInternal internal {}; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_set_quiet_shutdown_nid_postfix(nullptr, 1); }); + RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); }); + ctx.internal = &internal; + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); + Require(internal.quietShutdown == 1); + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, INT_MIN); + Require(internal.quietShutdown == INT_MIN); + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 0); + Require(internal.quietShutdown == 0); + for (unsigned char byte : internal.beforeQuietShutdown) Require(byte == 0); + for (unsigned char byte : internal.rest) Require(byte == 0); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); + TestSetQuietShutdown(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 1408175569..f7be910857 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 340 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 339 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 2ae827b7cbe5f0023f6d8d895c9cebe6f25d8719 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:41 +0200 Subject: [PATCH 040/612] feat(libSceLibreSsl): implement SSL_CTX_set_default_passwd_cb --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 2c8eac63f5..de06d88bce 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1058,11 +1058,6 @@ int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 2a86c75d25..573dbf8978 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -60,4 +60,8 @@ void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode context.internal->quietShutdown = mode; } +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* callback) { + Context(ctx, __func__).defaultPasswdCallback = callback; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index 38f3bb5fad..613aed964a 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -33,6 +33,7 @@ extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -84,9 +85,20 @@ static void TestSetQuietShutdown() { for (unsigned char byte : internal.rest) Require(byte == 0); } +static void TestSetPasswordCallback() { + int callback = 0; + TestCtx ctx {}; + RequireThrows([&] { SSL_CTX_set_default_passwd_cb_nid_postfix(nullptr, &callback); }); + SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, &callback); + Require(ctx.defaultPasswdCallback == &callback && ctx.defaultPasswdCallbackUserdata == nullptr); + SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, nullptr); + Require(ctx.defaultPasswdCallback == nullptr); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); TestSetQuietShutdown(); + TestSetPasswordCallback(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index f7be910857..d8acfe12fc 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 339 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 338 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From a9a754cf134242d007490309ac33ae88e628a248 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:51 +0200 Subject: [PATCH 041/612] feat(libSceLibreSsl): implement SSL_CTX_set_default_passwd_cb_userdata --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index de06d88bce..0a517dc250 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1058,11 +1058,6 @@ int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 573dbf8978..b70a0d9f12 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -64,4 +64,8 @@ void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* Context(ctx, __func__).defaultPasswdCallback = callback; } +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* ctx, void* userdata) { + Context(ctx, __func__).defaultPasswdCallbackUserdata = userdata; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index 613aed964a..fe76a282de 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -34,6 +34,7 @@ int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(TestCtx*, void*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -95,10 +96,21 @@ static void TestSetPasswordCallback() { Require(ctx.defaultPasswdCallback == nullptr); } +static void TestSetPasswordUserdata() { + int userdata = 0; + TestCtx ctx {}; + RequireThrows([&] { SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(nullptr, &userdata); }); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, &userdata); + Require(ctx.defaultPasswdCallbackUserdata == &userdata && ctx.defaultPasswdCallback == nullptr && ctx.internal == nullptr); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, nullptr); + Require(ctx.defaultPasswdCallbackUserdata == nullptr); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); TestSetQuietShutdown(); TestSetPasswordCallback(); + TestSetPasswordUserdata(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index d8acfe12fc..68a8f6d282 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 338 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 337 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb, SSL_CTX_set_default_passwd_cb_userdata](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From b0d8acf2d3eddb44f6be6329703c88d79370bf3d Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 05:59:27 +0100 Subject: [PATCH 042/612] refactor(libSceLibreSsl): declare the SSL_CTX accessor exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 32 +++++++++++++++--- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 23 +------------ core/libs/tests/GuestLibreSslSslCtx.cpp | 35 +++++++++----------- 3 files changed, 44 insertions(+), 46 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index c7edd129f6..7d6e44c712 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -51,6 +51,28 @@ struct Sha512Context { std::uint32_t num, mdLength; }; +struct LibreSslCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct LibreSslCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + int references; + void* extraCerts; + int verifyMode; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* param; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + LibreSslCtxInternal* internal; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -261,10 +283,10 @@ int APS5_VABI SSL_CTX_callback_ctrl_nid_postfix(void); int APS5_VABI SSL_CTX_check_private_key_nid_postfix(void); int APS5_VABI SSL_CTX_ctrl_nid_postfix(void); int APS5_VABI SSL_CTX_free_nid_postfix(void); -int APS5_VABI SSL_CTX_get_cert_store_nid_postfix(void); +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx); int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void); int APS5_VABI SSL_CTX_get_ex_new_index_nid_postfix(void); -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void); +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx); int APS5_VABI SSL_CTX_load_verify_locations_nid_postfix(void); int APS5_VABI SSL_CTX_load_verify_mem_nid_postfix(void); int APS5_VABI SSL_CTX_new_nid_postfix(void); @@ -276,13 +298,13 @@ int APS5_VABI SSL_CTX_set_cert_store_nid_postfix(void); int APS5_VABI SSL_CTX_set_cert_verify_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_cipher_list_nid_postfix(void); int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void); +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* callback); +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* ctx, void* userdata); int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void); int APS5_VABI SSL_CTX_set_info_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_msg_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(void); +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode); int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void); int APS5_VABI SSL_CTX_set_verify_nid_postfix(void); int APS5_VABI SSL_CTX_use_PrivateKey_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index b70a0d9f12..99eb4fa5a1 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -1,31 +1,10 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslCtxInternal { - unsigned char beforeQuietShutdown[292]; - int quietShutdown; - unsigned char rest[168]; -}; - -struct LibreSslCtx { - const void* method; - void* cipherList; - void* certStore; - std::int64_t sessionTimeout; - int references; - void* extraCerts; - int verifyMode; - unsigned int sidCtxLength; - unsigned char sidCtx[32]; - void* param; - void* defaultPasswdCallback; - void* defaultPasswdCallbackUserdata; - LibreSslCtxInternal* internal; -}; - static_assert(sizeof(LibreSslCtxInternal) == 464); static_assert(offsetof(LibreSslCtxInternal, quietShutdown) == 292); static_assert(sizeof(LibreSslCtx) == 120); diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index fe76a282de..3e77a25e02 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -29,12 +30,8 @@ struct TestCtx { static_assert(sizeof(TestCtxInternal) == 464 && offsetof(TestCtxInternal, quietShutdown) == 292); static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && offsetof(TestCtx, defaultPasswdCallback) == 96); -extern "C" { -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); -void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); -void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); -void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); -void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(TestCtx*, void*); +static LibreSslCtx* Context(TestCtx* ctx) { + return reinterpret_cast(ctx); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -51,11 +48,11 @@ template static void RequireThrows(TAction action, std::source static void TestGetVerifyMode() { TestCtx ctx {}; RequireThrows([] { SSL_CTX_get_verify_mode_nid_postfix(nullptr); }); - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == 0); ctx.verifyMode = 0x03; - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0x03); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == 0x03); ctx.verifyMode = -1; - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == -1); } static void TestGetCertStore() { @@ -63,24 +60,24 @@ static void TestGetCertStore() { int ciphers = 0; TestCtx ctx {}; RequireThrows([] { SSL_CTX_get_cert_store_nid_postfix(nullptr); }); - Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == nullptr); + Require(SSL_CTX_get_cert_store_nid_postfix(Context(&ctx)) == nullptr); ctx.cipherList = &ciphers; ctx.certStore = &store; ctx.sessionTimeout = -1; - Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); + Require(SSL_CTX_get_cert_store_nid_postfix(Context(&ctx)) == &store); } static void TestSetQuietShutdown() { TestCtxInternal internal {}; TestCtx ctx {}; RequireThrows([] { SSL_CTX_set_quiet_shutdown_nid_postfix(nullptr, 1); }); - RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); }); + RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 1); }); ctx.internal = &internal; - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 1); Require(internal.quietShutdown == 1); - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, INT_MIN); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), INT_MIN); Require(internal.quietShutdown == INT_MIN); - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 0); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 0); Require(internal.quietShutdown == 0); for (unsigned char byte : internal.beforeQuietShutdown) Require(byte == 0); for (unsigned char byte : internal.rest) Require(byte == 0); @@ -90,9 +87,9 @@ static void TestSetPasswordCallback() { int callback = 0; TestCtx ctx {}; RequireThrows([&] { SSL_CTX_set_default_passwd_cb_nid_postfix(nullptr, &callback); }); - SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, &callback); + SSL_CTX_set_default_passwd_cb_nid_postfix(Context(&ctx), &callback); Require(ctx.defaultPasswdCallback == &callback && ctx.defaultPasswdCallbackUserdata == nullptr); - SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, nullptr); + SSL_CTX_set_default_passwd_cb_nid_postfix(Context(&ctx), nullptr); Require(ctx.defaultPasswdCallback == nullptr); } @@ -100,9 +97,9 @@ static void TestSetPasswordUserdata() { int userdata = 0; TestCtx ctx {}; RequireThrows([&] { SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(nullptr, &userdata); }); - SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, &userdata); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(Context(&ctx), &userdata); Require(ctx.defaultPasswdCallbackUserdata == &userdata && ctx.defaultPasswdCallback == nullptr && ctx.internal == nullptr); - SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, nullptr); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(Context(&ctx), nullptr); Require(ctx.defaultPasswdCallbackUserdata == nullptr); } From 19e3b8cf55c9986443058424e921bb60410871bf Mon Sep 17 00:00:00 2001 From: Ahmad Mujahid Date: Mon, 26 Oct 2026 00:21:44 +0400 Subject: [PATCH 043/612] feat(libSceLibreSsl): implement OCSP_check_validity --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 - core/libs/prx/libSceLibreSsl/include/Api.hpp | 2 +- core/libs/prx/libSceLibreSsl/src/Ocsp.cpp | 65 ++++++++++++ core/libs/tests/GuestLibreSslOcsp.cpp | 103 +++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 5 +- 5 files changed, 172 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 93387ef0ba..7dee0f4220 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -813,11 +813,6 @@ int APS5_VABI OCSP_basic_verify_nid_postfix(void) { return 0; } -int APS5_VABI OCSP_check_validity_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI OCSP_resp_count_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index c7edd129f6..bbfbf36e19 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -220,7 +220,7 @@ int APS5_VABI OCSP_RESPONSE_free_nid_postfix(void); int APS5_VABI OCSP_basic_add1_cert_nid_postfix(void); int APS5_VABI OCSP_basic_verify_nid_postfix(void); const char* APS5_VABI OCSP_cert_status_str_nid_postfix(std::int64_t status); -int APS5_VABI OCSP_check_validity_nid_postfix(void); +int APS5_VABI OCSP_check_validity_nid_postfix(const Asn1String* thisUpdate, const Asn1String* nextUpdate, std::int64_t slack, std::int64_t maxAge); const char* APS5_VABI OCSP_crl_reason_str_nid_postfix(std::int64_t reason); int APS5_VABI OCSP_resp_count_nid_postfix(void); int APS5_VABI OCSP_resp_get0_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/Ocsp.cpp b/core/libs/prx/libSceLibreSsl/src/Ocsp.cpp index e64c504978..a27827f88c 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ocsp.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ocsp.cpp @@ -1,6 +1,10 @@ #include "prx/libSceLibreSsl/include/Api.hpp" #include #include +#include +#include "prx/libSceLibreSsl/include/Error.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/General.hpp" namespace { struct OcspString { @@ -15,6 +19,45 @@ const char* TableToString(std::int64_t code, const OcspString (&table)[Count]) { } return "(UNKNOWN)"; } + +constexpr int OcspLibrary = 39; +constexpr int OcspFunction = 0xfff; +constexpr int ErrorInNextUpdate = 122; +constexpr int ErrorInThisUpdate = 123; +constexpr int NextUpdateBeforeThisUpdate = 124; +constexpr int StatusExpired = 125; +constexpr int StatusNotYetValid = 126; +constexpr int StatusTooOld = 127; +constexpr int GeneralizedTimeLength = 15; + +int Fail(int reason) { + LibreSslPutError_nid_no_patch(OcspLibrary, OcspFunction, reason); + return 0; +} + +bool ParseGeneralizedTime(const Asn1String& time, GuestTm& result) { + if (!time.data || time.length != GeneralizedTimeLength || time.data[GeneralizedTimeLength - 1] != 'Z') return false; + for (int i = 0; i < GeneralizedTimeLength - 1; ++i) { + if (time.data[i] < '0' || time.data[i] > '9') return false; + } + const auto pair = [&time](int index) { return (time.data[index] - '0') * 10 + (time.data[index + 1] - '0'); }; + result.tm_year = pair(0) * 100 + pair(2) - 1900; + result.tm_mon = pair(4) - 1; + result.tm_mday = pair(6); + result.tm_hour = pair(8); + result.tm_min = pair(10); + result.tm_sec = pair(12); + return result.tm_mon <= 11 && result.tm_mon >= 0 && result.tm_mday >= 1 && result.tm_mday <= 31 && result.tm_hour <= 23 && result.tm_min <= 59 && result.tm_sec <= 59; +} + +bool TimeAt(std::int64_t now, std::uint64_t offset, GuestTm& result) { + const auto time = static_cast(static_cast(now) + offset); + return gmtime_r_nid_postfix(&time, &result) != nullptr; +} + +auto Fields(const GuestTm& time) { + return std::tie(time.tm_year, time.tm_mon, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec); +} } extern "C" { @@ -54,4 +97,26 @@ const char* APS5_VABI OCSP_crl_reason_str_nid_postfix(std::int64_t reason) { return TableToString(reason, table); } +int APS5_VABI OCSP_check_validity_nid_postfix(const Asn1String* thisUpdate, const Asn1String* nextUpdate, std::int64_t slack, std::int64_t maxAge) { + if (!thisUpdate) NotImplemented_nid_no_patch(__func__); + std::int64_t now = 0; + time_nid_postfix(&now); + GuestTm thisTime{}; + GuestTm limit{}; + if (!ParseGeneralizedTime(*thisUpdate, thisTime)) return Fail(ErrorInThisUpdate); + if (!TimeAt(now, static_cast(slack), limit)) return 0; + if (Fields(thisTime) > Fields(limit)) return Fail(StatusNotYetValid); + if (maxAge >= 0) { + if (!TimeAt(now, 0 - static_cast(maxAge), limit)) return 0; + if (Fields(thisTime) < Fields(limit)) return Fail(StatusTooOld); + } + if (!nextUpdate) return 1; + GuestTm nextTime{}; + if (!ParseGeneralizedTime(*nextUpdate, nextTime)) return Fail(ErrorInNextUpdate); + if (!TimeAt(now, 0 - static_cast(slack), limit)) return 0; + if (Fields(nextTime) < Fields(limit)) return Fail(StatusExpired); + if (Fields(nextTime) < Fields(thisTime)) return Fail(NextUpdateBeforeThisUpdate); + return 1; +} + } diff --git a/core/libs/tests/GuestLibreSslOcsp.cpp b/core/libs/tests/GuestLibreSslOcsp.cpp index f0c3f82ad8..f49e763280 100644 --- a/core/libs/tests/GuestLibreSslOcsp.cpp +++ b/core/libs/tests/GuestLibreSslOcsp.cpp @@ -4,7 +4,9 @@ #include #include #include +#include #include +#include using StringFunction = const char* (APS5_VABI*)(std::int64_t); @@ -16,7 +18,108 @@ void Check(StringFunction function, const char* name, std::int64_t value, const } } +constexpr std::int32_t GeneralizedTime = 24; +constexpr std::uint64_t OcspError = 0x27fffull << 12; +constexpr std::uint64_t ErrorInNextUpdate = OcspError | 122; +constexpr std::uint64_t ErrorInThisUpdate = OcspError | 123; +constexpr std::uint64_t NextUpdateBeforeThisUpdate = OcspError | 124; +constexpr std::uint64_t StatusExpired = OcspError | 125; +constexpr std::uint64_t StatusNotYetValid = OcspError | 126; +constexpr std::uint64_t StatusTooOld = OcspError | 127; +constexpr std::int64_t Century = 3155760000; + +struct TimeString { + std::string text; + Asn1String value; + + explicit TimeString(const char* input, std::int32_t type = GeneralizedTime) + : text(input), value{static_cast(text.size()), type, reinterpret_cast(text.data()), 0} {} + TimeString(const TimeString&) = delete; + TimeString& operator=(const TimeString&) = delete; +}; + +void CheckValidity(const char* name, const Asn1String* thisUpdate, const Asn1String* nextUpdate, std::int64_t slack, std::int64_t maxAge, int expected, std::uint64_t error) { + ERR_clear_error_nid_postfix(); + const int actual = OCSP_check_validity_nid_postfix(thisUpdate, nextUpdate, slack, maxAge); + const std::uint64_t first = ERR_get_error_nid_postfix(); + const std::uint64_t second = ERR_get_error_nid_postfix(); + if (actual != expected || first != error || second != 0) { + std::fprintf(stderr, "OCSP_check_validity %s: expected %d and 0x%llx, got %d, 0x%llx and 0x%llx\n", name, expected, static_cast(error), actual, + static_cast(first), static_cast(second)); + std::abort(); + } +} + +void CheckValidity() { + constexpr auto Maximum = std::numeric_limits::max(); + constexpr auto Minimum = std::numeric_limits::min(); + const TimeString past("20000101000000Z"); + const TimeString pastSecond("20000101000001Z"); + const TimeString future("99991231235959Z"); + const TimeString yearZero("00000101000000Z"); + const TimeString february("20000231000000Z"); + const TimeString march("20000301000000Z"); + const TimeString utcType("20000101000000Z", 23); + const TimeString octetType("20000101000000Z", 4); + const TimeString untyped("99991231235959Z", 0); + + CheckValidity("valid window", &past.value, &future.value, 0, -1, 1, 0); + CheckValidity("no next update", &past.value, nullptr, 0, -1, 1, 0); + CheckValidity("minimum age skips the age check", &past.value, &future.value, 0, Minimum, 1, 0); + CheckValidity("year zero", &yearZero.value, &future.value, 0, -1, 1, 0); + CheckValidity("UTCTime type", &utcType.value, &untyped.value, 0, -1, 1, 0); + CheckValidity("octet string type", &octetType.value, &untyped.value, 0, -1, 1, 0); + CheckValidity("not yet valid", &future.value, &future.value, 0, -1, 0, StatusNotYetValid); + CheckValidity("not yet valid before expired", &future.value, &past.value, 0, -1, 0, StatusNotYetValid); + CheckValidity("expired", &past.value, &past.value, 0, -1, 0, StatusExpired); + CheckValidity("too old", &past.value, &future.value, 0, 3600, 0, StatusTooOld); + CheckValidity("zero age", &past.value, &future.value, 0, 0, 0, StatusTooOld); + CheckValidity("next update before this update", &pastSecond.value, &past.value, Century, -1, 0, NextUpdateBeforeThisUpdate); + CheckValidity("next update equal to this update", &past.value, &past.value, Century, -1, 1, 0); + CheckValidity("expired before next update before this update", &pastSecond.value, &past.value, 0, -1, 0, StatusExpired); + CheckValidity("too old before expired", &past.value, &past.value, 0, 3600, 0, StatusTooOld); + CheckValidity("not yet valid before too old", &past.value, &future.value, -Century, 0, 0, StatusNotYetValid); + CheckValidity("February 31 before March 1", &february.value, &march.value, Century, -1, 1, 0); + CheckValidity("March 1 after February 31", &march.value, &february.value, Century, -1, 0, NextUpdateBeforeThisUpdate); + + const char* invalid[] {"000101000000Z", "20000101000000.5Z", "2000010100000aZ", "200001010000000", "20000101000000z", "20001301000000Z", "20000001000000Z", + "20000100000000Z", "20000132000000Z", "20000101240000Z", "20000101006000Z", "20000101000060Z", " 0000101000000Z", "-0000101000000Z", "+0000101000000Z", + "\xb2" "0000101000000Z", "20000101000000Z0", ""}; + for (const char* text : invalid) { + const TimeString time(text); + CheckValidity(text, &time.value, &future.value, 0, -1, 0, ErrorInThisUpdate); + CheckValidity(text, &past.value, &time.value, 0, -1, 0, ErrorInNextUpdate); + } + const TimeString month("20001301000000Z"); + CheckValidity("invalid this update without next update", &month.value, nullptr, 0, -1, 0, ErrorInThisUpdate); + CheckValidity("not yet valid before invalid next update", &future.value, &month.value, 0, -1, 0, StatusNotYetValid); + const Asn1String missing{15, GeneralizedTime, nullptr, 0}; + CheckValidity("null data", &missing, &future.value, 0, -1, 0, ErrorInThisUpdate); + CheckValidity("null next update data", &past.value, &missing, 0, -1, 0, ErrorInNextUpdate); + const Asn1String negative{-1, GeneralizedTime, past.value.data, 0}; + CheckValidity("negative length", &negative, &future.value, 0, -1, 0, ErrorInThisUpdate); + + CheckValidity("wrapping slack", &past.value, &future.value, Maximum, -1, 0, 0); + CheckValidity("wrapping slack without next update", &past.value, nullptr, Maximum, -1, 0, 0); + CheckValidity("wrapping negative slack", &past.value, &future.value, Minimum, -1, 0, 0); + CheckValidity("slack beyond the calendar", &past.value, &future.value, 70000000000000000, -1, 0, 0); + CheckValidity("negative slack beyond the calendar", &past.value, &future.value, -70000000000000000, -1, 0, 0); + CheckValidity("age beyond the calendar", &past.value, &future.value, 0, Maximum, 0, 0); + CheckValidity("slack inside the calendar", &past.value, &future.value, 60000000000000000, -1, 1, 0); + CheckValidity("age inside the calendar", &past.value, nullptr, 0, 60000000000000000, 1, 0); + + try { + OCSP_check_validity_nid_postfix(nullptr, &future.value, 0, -1); + } catch (const std::exception&) { + return; + } + std::fprintf(stderr, "OCSP_check_validity: a null this update must throw\n"); + std::abort(); +} + int main() { + CheckValidity(); + constexpr auto Unknown = "(UNKNOWN)"; constexpr std::int64_t Outside[] {-1, 4, 7, 9, 0x100, 0x100000000, std::numeric_limits::min(), std::numeric_limits::max()}; diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index bd201c0765..8384119d72 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,14 +2,15 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 342 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 341 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. - [RAND_add, RAND_bytes, RAND_status, RAND_load_file, RAND_file_name, CRYPTO_malloc, CRYPTO_free, CRYPTO_set_mem_functions](../../../core/libs/prx/libSceLibreSsl/src/Runtime.cpp) (libSceLibreSsl) - follow LibreSSL's [rand_lib.c, randfile.c](https://github.com/libressl/openbsd/tree/0f0b16dc977f6ca499f0f4540078d992c96da597/src/lib/libcrypto/rand) and [malloc-wrapper.c](https://github.com/libressl/openbsd/blob/0f0b16dc977f6ca499f0f4540078d992c96da597/src/lib/libcrypto/malloc-wrapper.c), which are the same in 2.6.5 (3.4.3 linked); nothing is checked on the console. `RAND_bytes` reads host entropy and `CRYPTO_malloc` and `CRYPTO_free` use the guest heap. `CRYPTO_malloc` returns null for a size of zero or less, as LibreSSL 2.5.4 to 3.4 do; later versions take a `size_t` size and call `malloc`. A null buffer with a positive length throws - [AES_set_encrypt_key, AES_set_decrypt_key, AES_encrypt, AES_decrypt, AES_ecb_encrypt, AES_cbc_encrypt, AES_cfb128_encrypt, AES_ctr128_encrypt](../../../core/libs/prx/libSceLibreSsl/src/Aes.cpp) (libSceLibreSsl) - follow LibreSSL's [aes.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/aes/aes.h) and [mode code](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/modes). Nothing is checked on the console. Key setup follows [LibreSSL 2.5.4's aes_core.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/aes_core.c), the version the `X509_verify_cert_error_string` entry names: a null pointer returns -1 and a key length other than 128, 192 or 256 bits returns -2 before the key is written, and only the 44, 52 or 60 words the key length needs are written. The `AES_KEY` schedule uses that file's generic C layout (big-endian words, `rounds` 10, 12 or 14). The 2.5.4 amd64 build uses [aes-x86_64](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/asm/aes-x86_64.pl) instead, which copies the key bytes without swapping, so each word is byte-reversed compared with the C layout (OpenSSL 3.0.13's x86-64 code gives the same schedules byte-reversed); the console's build options are unknown, so a schedule the title reads or fills is unverified. CTR leaves the last keystream block in `ecount_buf` even at a block boundary. Null pointers in the cipher and mode functions, other `rounds` values and a CFB or CTR position outside 0-15 throw -- [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names +- [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - the per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` +- [OCSP_check_validity](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - nothing is checked on the console, and no export produces an `ASN1_GENERALIZEDTIME` yet. It follows LibreSSL 2.5.4 [ocsp_cl.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_cl.c#L323-L392) and [a_time_tm.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/a_time_tm.c#L32-L209): only the 15-character `YYYYMMDDHHMMSSZ` form parses, the type field is ignored, times compare field by field, and each failed check queues one OCSP error. The current time comes from guest `time`. A null `thisupd` throws where LibreSSL faults. The time plus or minus `nsec` or minus `maxsec` wraps around as in that build, and a result outside the guest `gmtime_r` range returns 0 without an error; the console's `gmtime_r` range is unverified - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. From 730d130f0ba96c0e39fac59fe90cb7055c09d450 Mon Sep 17 00:00:00 2001 From: Ahmad Mujahid Date: Mon, 26 Oct 2026 09:43:00 +0400 Subject: [PATCH 044/612] docs(debt): name the gmtime_s call behind OCSP_check_validity's time range --- docs/dev/debt/libSceLibreSsl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 8384119d72..fd520488d7 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -10,7 +10,7 @@ - [AES_set_encrypt_key, AES_set_decrypt_key, AES_encrypt, AES_decrypt, AES_ecb_encrypt, AES_cbc_encrypt, AES_cfb128_encrypt, AES_ctr128_encrypt](../../../core/libs/prx/libSceLibreSsl/src/Aes.cpp) (libSceLibreSsl) - follow LibreSSL's [aes.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/aes/aes.h) and [mode code](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/modes). Nothing is checked on the console. Key setup follows [LibreSSL 2.5.4's aes_core.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/aes_core.c), the version the `X509_verify_cert_error_string` entry names: a null pointer returns -1 and a key length other than 128, 192 or 256 bits returns -2 before the key is written, and only the 44, 52 or 60 words the key length needs are written. The `AES_KEY` schedule uses that file's generic C layout (big-endian words, `rounds` 10, 12 or 14). The 2.5.4 amd64 build uses [aes-x86_64](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/asm/aes-x86_64.pl) instead, which copies the key bytes without swapping, so each word is byte-reversed compared with the C layout (OpenSSL 3.0.13's x86-64 code gives the same schedules byte-reversed); the console's build options are unknown, so a schedule the title reads or fills is unverified. CTR leaves the last keystream block in `ecount_buf` even at a block boundary. Null pointers in the cipher and mode functions, other `rounds` values and a CFB or CTR position outside 0-15 throw - [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - the per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` -- [OCSP_check_validity](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - nothing is checked on the console, and no export produces an `ASN1_GENERALIZEDTIME` yet. It follows LibreSSL 2.5.4 [ocsp_cl.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_cl.c#L323-L392) and [a_time_tm.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/a_time_tm.c#L32-L209): only the 15-character `YYYYMMDDHHMMSSZ` form parses, the type field is ignored, times compare field by field, and each failed check queues one OCSP error. The current time comes from guest `time`. A null `thisupd` throws where LibreSSL faults. The time plus or minus `nsec` or minus `maxsec` wraps around as in that build, and a result outside the guest `gmtime_r` range returns 0 without an error; the console's `gmtime_r` range is unverified +- [OCSP_check_validity](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) - nothing is checked on the console, and no export produces an `ASN1_GENERALIZEDTIME` yet. It follows LibreSSL 2.5.4 [ocsp_cl.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_cl.c#L323-L392) and [a_time_tm.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/a_time_tm.c#L32-L209): only the 15-character `YYYYMMDDHHMMSSZ` form parses, the type field is ignored, times compare field by field, and each failed check queues one OCSP error. The current time comes from guest `time`. A null `thisupd` throws where LibreSSL faults. The time plus or minus `nsec` or minus `maxsec` wraps around as in that build, and a result `gmtime_s` rejects returns 0 without an error, as in the [13.42 module](https://github.com/boykopovar/AnyPS5/pull/11174#issuecomment-6406163923); its console range is unverified - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. From 4f6b565e519a50fe96ff450f126e97a8f616aba0 Mon Sep 17 00:00:00 2001 From: Ahmad Mujahid Date: Mon, 26 Oct 2026 17:47:08 +0400 Subject: [PATCH 045/612] test(libSceLibreSsl): stop requiring unsupported SSL_CTX_ctrl commands to throw --- core/libs/tests/GuestLibreSslSslCtxFields.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/libs/tests/GuestLibreSslSslCtxFields.cpp b/core/libs/tests/GuestLibreSslSslCtxFields.cpp index 80e3c9cd17..9f1e2feeea 100644 --- a/core/libs/tests/GuestLibreSslSslCtxFields.cpp +++ b/core/libs/tests/GuestLibreSslSslCtxFields.cpp @@ -316,7 +316,6 @@ static void TestTemporaryKeys() { static void TestOtherCtrlCommands() { Fixture f; for (int cmd : {0, 8, 13, 15, 17, 55, 65, 999, -1}) f.Ctrl(cmd, 0, nullptr, 0); - for (int cmd : {3, 4, 14, 20, 83, 91, 92, 94, 118}) f.Throws([&] { SSL_CTX_ctrl_nid_postfix(f.Context(), cmd, 0, Pointer(0x1111)); }); } static void TestCallbackCtrl() { @@ -333,7 +332,6 @@ static void TestCallbackCtrl() { f.CallbackCtrl(0, Pointer(0x1111), 0); f.CallbackCtrl(16, Pointer(0x1111), 0); f.CallbackCtrl(999, Pointer(0x1111), 0); - for (int cmd : {6, 7}) f.Throws([&] { SSL_CTX_callback_ctrl_nid_postfix(f.Context(), cmd, Pointer(0x1111)); }); } static void TestSetMsgCallback() { From ade042c23188aaa3b642337095150bdc23bf91f1 Mon Sep 17 00:00:00 2001 From: Ahmad Mujahid Date: Mon, 26 Oct 2026 17:54:32 +0400 Subject: [PATCH 046/612] test(libSceLibreSsl): use Api.hpp name types and stop pinning the NID placeholder throw --- core/libs/tests/GuestLibreSslX509Name.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp index c9abef8c7c..6dd12d19c4 100644 --- a/core/libs/tests/GuestLibreSslX509Name.cpp +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -114,10 +114,10 @@ static const unsigned char UnstructuredNameOid[] {0x2a, 0x86, 0x48, 0x86, 0xf7, struct DecodedName { TestObject objects[8]; Asn1String values[8]; - TestNameEntry entries[8]; + LibreSslX509NameEntry entries[8]; char* slots[8]; - TestStack stack; - TestName name; + LibreSslStack stack; + LibreSslX509Name name; DecodedName() { const struct { const unsigned char* oid; int oidLength; const char* text; int textLength; } fields[] { @@ -145,7 +145,7 @@ struct DecodedName { static void TestIndexByNid() { DecodedName decoded; - const auto* name = Name(&decoded.name); + const auto* name = &decoded.name; const int commonName[][2] {{-1, 2}, {-5, 2}, {INT_MIN, 2}, {0, 2}, {2, 4}, {4, -1}, {7, -1}, {8, -1}, {100, -1}}; for (const auto& [lastpos, index] : commonName) Require(X509_NAME_get_index_by_NID_nid_postfix(name, 13, lastpos) == index); Require(X509_NAME_get_index_by_NID_nid_postfix(name, 14, -1) == 0); @@ -168,13 +168,11 @@ static void TestIndexByNid() { decoded.objects[6] = {nullptr, nullptr, 0, 0, nullptr, 0}; Require(X509_NAME_get_index_by_NID_nid_postfix(name, 0, -1) == 6); Require(X509_NAME_get_index_by_NID_nid_postfix(name, 0, 6) == -1); - TestStack empty {0, nullptr, 0, 0, nullptr}; - TestName emptyName {&empty, 0, nullptr, nullptr, 0}; - TestName noEntries {}; - Require(X509_NAME_get_index_by_NID_nid_postfix(Name(&emptyName), 13, -1) == -1); - Require(X509_NAME_get_index_by_NID_nid_postfix(Name(&noEntries), 13, -1) == -1); - RequireThrows([&] { X509_NAME_get_index_by_NID_nid_postfix(name, 102, -1); }); - RequireThrows([&] { X509_NAME_get_index_by_NID_nid_postfix(name, 6, -1); }); + LibreSslStack empty {0, nullptr, 0, 0, nullptr}; + LibreSslX509Name emptyName {&empty, 0, nullptr, nullptr, 0}; + LibreSslX509Name noEntries {}; + Require(X509_NAME_get_index_by_NID_nid_postfix(&emptyName, 13, -1) == -1); + Require(X509_NAME_get_index_by_NID_nid_postfix(&noEntries, 13, -1) == -1); RequireThrows([&] { X509_NAME_get_index_by_NID_nid_postfix(name, 13, INT_MAX); }); decoded.objects[2].data = nullptr; RequireThrows([&] { X509_NAME_get_index_by_NID_nid_postfix(name, 13, -1); }); @@ -196,7 +194,7 @@ static bool BufferHolds(const char* buffer, const char* prefix, std::size_t leng static void TestTextByNid() { DecodedName decoded; - const auto* name = Name(&decoded.name); + const auto* name = &decoded.name; char buffer[64]; const struct { int len; int result; const char* prefix; std::size_t length; } cases[] { {64, 5, "first", 6}, {6, 5, "first", 6}, {5, 4, "firs", 5}, {3, 2, "fi", 3}, {2, 1, "f", 2}, {1, 0, "", 1}, @@ -219,7 +217,6 @@ static void TestTextByNid() { Require(X509_NAME_get_text_by_NID_nid_postfix(name, 48, buffer, 64) == 5); Require(BufferHolds(buffer, "a@b.c", 6)); RequireThrows([&] { X509_NAME_get_text_by_NID_nid_postfix(name, 13, buffer, INT_MIN); }); - RequireThrows([&] { X509_NAME_get_text_by_NID_nid_postfix(name, 6, buffer, 64); }); decoded.values[2].data = nullptr; RequireThrows([&] { X509_NAME_get_text_by_NID_nid_postfix(name, 13, buffer, 64); }); std::memset(buffer, 0x7f, sizeof(buffer)); From d117274235e7fe797d42e55fbb88efc811181b7b Mon Sep 17 00:00:00 2001 From: Ahmad Mujahid Date: Mon, 26 Oct 2026 17:47:44 +0400 Subject: [PATCH 047/612] test(libSceLibreSsl): drop the SSL_set_connect_state live-context and SSL_ctrl unhandled-command loops Both loops pinned paths that are only unimplemented here: 2.5.4 SSL_set_connect_state frees the cipher and hash contexts, and SSL_ctrl command 1 returns 0. Ssl.cpp keeps refusing them as the debt entry says. --- core/libs/tests/GuestLibreSslSslSettings.cpp | 25 +++----------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/core/libs/tests/GuestLibreSslSslSettings.cpp b/core/libs/tests/GuestLibreSslSslSettings.cpp index 58a6408299..3e6888ee65 100644 --- a/core/libs/tests/GuestLibreSslSslSettings.cpp +++ b/core/libs/tests/GuestLibreSslSslSettings.cpp @@ -214,7 +214,6 @@ static void TestGetAlpnSelected() { static void TestSetConnectState() { int accept = 0; int connect = 0; - int context = 0; TestMethodInternal methodInternal {}; TestMethod method {}; Fill(methodInternal, 0x30); @@ -238,18 +237,7 @@ static void TestSetConnectState() { connection.internal.handshakeFunc = &accept; SSL_set_connect_state_nid_postfix(connection.Ssl()); Require(connection.internal.handshakeFunc == nullptr); - void** contexts[] = {&connection.ssl.encReadCtx, &connection.ssl.readHash, &connection.internal.encWriteCtx, - &connection.internal.aeadReadCtx, &connection.internal.aeadWriteCtx, &connection.internal.writeHash}; - for (void** slot : contexts) { - connection.ssl.server = 1; - connection.internal.shutdown = 2; - connection.internal.state = 0x1234; - *slot = &context; - const Connection before = connection; - RequireThrows([&] { SSL_set_connect_state_nid_postfix(connection.Ssl()); }); - Require(Same(connection.ssl, before.ssl) && Same(connection.internal, before.internal)); - *slot = nullptr; - } + connection.ssl.server = 1; method.internal = nullptr; RequireThrows([&] { SSL_set_connect_state_nid_postfix(connection.Ssl()); }); connection.ssl.method = nullptr; @@ -419,15 +407,8 @@ static void TestControlRenegotiationInfoSupport() { Require(Same(connection.ssl, expected.ssl)); } -static void TestControlUnhandled() { - Connection connection; - const Connection expected = connection; - RequireThrows([] { SSL_ctrl_nid_postfix(nullptr, 55, 1, nullptr); }); +static void TestControlNullConnection() { for (int command : {16, 32, 33, 50, 51, 77, 78}) RequireThrows([&] { SSL_ctrl_nid_postfix(nullptr, command, 1, nullptr); }); - for (int command : {55, 0, -1, 1, 15, 18, 34, 42, 53, 75, 79, 0x10000 + 40}) { - RequireThrows([&] { Control(connection, command, 1); }); - Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); - } } int main() { @@ -441,6 +422,6 @@ int main() { TestControlMtu(); TestControlMaxSendFragment(); TestControlRenegotiationInfoSupport(); - TestControlUnhandled(); + TestControlNullConnection(); return 0; } From ea429eaefa7aa591d8a47c97d3c98d5bb94e0e8a Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:53:15 +0200 Subject: [PATCH 048/612] feat(libSceLibreSsl): implement SSL_get_SSL_CTX --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 +++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 78 +++++++++++++++++++++ core/libs/tests/GuestLibreSslSsl.cpp | 65 +++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 3 +- 5 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/Ssl.cpp create mode 100644 core/libs/tests/GuestLibreSslSsl.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 0eae0a5d98..31590d3bf1 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ssl.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -88,6 +88,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_ocsp PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_ssl_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSsl.cpp) + target_include_directories(guest_libressl_ssl_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_tests) + add_test(NAME guest_libressl_ssl COMMAND guest_libressl_ssl_tests) + set_tests_properties(guest_libressl_ssl PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index cee8b3e31f..cdaba7f3ad 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1193,11 +1193,6 @@ int APS5_VABI SSL_get1_session_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_SSL_CTX_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_get_certificate_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp new file mode 100644 index 0000000000..c817055b09 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct LibreSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + int server; + void* s3; + void* d1; + void* param; + void* cipherList; + void* cert; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* session; + int verifyMode; + int error; + int errorCode; + void* ctx; + std::int64_t verifyResult; + int references; + int clientVersion; + unsigned int maxSendFragment; + char* tlsextHostname; + int tlsextStatusType; + void* initialCtx; + void* encReadCtx; + void* readHash; + LibreSslSslInternal* internal; +}; + +static_assert(sizeof(LibreSslSslInternal) == 432); +static_assert(offsetof(LibreSslSslInternal, rwstate) == 156); +static_assert(offsetof(LibreSslSslInternal, shutdown) == 168); +static_assert(sizeof(LibreSsl) == 232); +static_assert(offsetof(LibreSsl, rbio) == 16); +static_assert(offsetof(LibreSsl, wbio) == 24); +static_assert(offsetof(LibreSsl, session) == 128); +static_assert(offsetof(LibreSsl, ctx) == 152); +static_assert(offsetof(LibreSsl, verifyResult) == 160); +static_assert(offsetof(LibreSsl, internal) == 224); + +namespace { + +const LibreSsl& Connection(const LibreSsl* ssl, const char* function) { + if (!ssl) throw std::invalid_argument(std::string(function) + ": null connection"); + return *ssl; +} + +const LibreSslSslInternal& Internal(const LibreSsl* ssl, const char* function) { + const LibreSsl& connection = Connection(ssl, function); + if (!connection.internal) throw std::invalid_argument(std::string(function) + ": null internal state"); + return *connection.internal; +} + +} + +extern "C" { + +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).ctx; +} + +} diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp new file mode 100644 index 0000000000..db130edf56 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -0,0 +1,65 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct TestSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct TestSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + unsigned char beforeSession[88]; + void* session; + unsigned char beforeCtx[16]; + void* ctx; + std::int64_t verifyResult; + unsigned char beforeInternal[56]; + TestSslInternal* internal; +}; + +static_assert(sizeof(TestSslInternal) == 432 && offsetof(TestSslInternal, rwstate) == 156 && offsetof(TestSslInternal, shutdown) == 168); +static_assert(sizeof(TestSsl) == 232 && offsetof(TestSsl, session) == 128 && offsetof(TestSsl, ctx) == 152); +static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, internal) == 224); + +extern "C" { +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetSslCtx() { + int context = 0; + int session = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_SSL_CTX_nid_postfix(nullptr); }); + Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == nullptr); + ssl.ctx = &context; + ssl.session = &session; + Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); +} + +int main() { + TestGetSslCtx(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 1f8ebb39eb..9f643b792b 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 340 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 339 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,6 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. +- [SSL_get_SSL_CTX](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach it. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 83910060b89c62a75b094158ce6bd5d5c73f3e89 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:17 +0200 Subject: [PATCH 049/612] feat(libSceLibreSsl): implement SSL_get_rbio --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 13 +++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index cdaba7f3ad..3a62b23a50 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1243,11 +1243,6 @@ int APS5_VABI SSL_get_privatekey_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_rbio_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_get_servername_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index c817055b09..3069c3cc5d 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -75,4 +75,8 @@ void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).ctx; } +void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).rbio; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index db130edf56..95fa53ff94 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -35,6 +35,7 @@ static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, intern extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); +void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -59,7 +60,19 @@ static void TestGetSslCtx() { Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); } +static void TestGetRbio() { + int read = 0; + int write = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_rbio_nid_postfix(nullptr); }); + Require(SSL_get_rbio_nid_postfix(&ssl) == nullptr); + ssl.rbio = &read; + ssl.wbio = &write; + Require(SSL_get_rbio_nid_postfix(&ssl) == &read); +} + int main() { TestGetSslCtx(); + TestGetRbio(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 9f643b792b..fb5b2c37bf 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 339 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 338 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach it. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 472a793a1e152137c6a46cecca3bcc2384dd188d Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:27 +0200 Subject: [PATCH 050/612] feat(libSceLibreSsl): implement SSL_get_wbio --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 15 +++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 3a62b23a50..25eb305d94 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1263,11 +1263,6 @@ int APS5_VABI SSL_get_verify_result_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_wbio_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 3069c3cc5d..2c544b2aa2 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -79,4 +79,8 @@ void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).rbio; } +void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).wbio; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 95fa53ff94..6547b2aac1 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -36,6 +36,7 @@ static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, intern extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); +void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -71,8 +72,22 @@ static void TestGetRbio() { Require(SSL_get_rbio_nid_postfix(&ssl) == &read); } +static void TestGetWbio() { + int read = 0; + int write = 0; + int buffer = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_wbio_nid_postfix(nullptr); }); + Require(SSL_get_wbio_nid_postfix(&ssl) == nullptr); + ssl.rbio = &read; + ssl.wbio = &write; + ssl.bbio = &buffer; + Require(SSL_get_wbio_nid_postfix(&ssl) == &write); +} + int main() { TestGetSslCtx(); TestGetRbio(); + TestGetWbio(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index fb5b2c37bf..de123b6c3f 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 338 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 337 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX, SSL_get_rbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 638a1958416bbe6b6475338a03905e551ddd981d Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:37 +0200 Subject: [PATCH 051/612] feat(libSceLibreSsl): implement SSL_version --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 25eb305d94..0c994dbc94 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1348,11 +1348,6 @@ int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void) { return 0; } -int APS5_VABI SSL_version_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_want_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 2c544b2aa2..a980c12199 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -83,4 +83,8 @@ void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).wbio; } +int APS5_VABI SSL_version_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).version; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 6547b2aac1..10945d54ba 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -37,6 +37,7 @@ extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); +int APS5_VABI SSL_version_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -85,9 +86,20 @@ static void TestGetWbio() { Require(SSL_get_wbio_nid_postfix(&ssl) == &write); } +static void TestVersion() { + TestSsl ssl {}; + RequireThrows([] { SSL_version_nid_postfix(nullptr); }); + Require(SSL_version_nid_postfix(&ssl) == 0); + ssl.version = 0x0303; + Require(SSL_version_nid_postfix(&ssl) == 0x0303); + ssl.version = 0xFEFF; + Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); + TestVersion(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index de123b6c3f..7df58632f8 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 337 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 336 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From b2bdb563f0a66bb2734faeefc3e6797a8ab6d08f Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:47 +0200 Subject: [PATCH 052/612] feat(libSceLibreSsl): implement SSL_get_verify_result --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 0c994dbc94..1e22a2326f 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1258,11 +1258,6 @@ int APS5_VABI SSL_get_shutdown_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_verify_result_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index a980c12199..89c984e2d6 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -87,4 +87,8 @@ int APS5_VABI SSL_version_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).version; } +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).verifyResult; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 10945d54ba..2d1a43528c 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -38,6 +38,7 @@ void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -96,10 +97,23 @@ static void TestVersion() { Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); } +static void TestVerifyResult() { + TestSsl ssl {}; + RequireThrows([] { SSL_get_verify_result_nid_postfix(nullptr); }); + Require(SSL_get_verify_result_nid_postfix(&ssl) == 0); + ssl.verifyResult = 20; + Require(SSL_get_verify_result_nid_postfix(&ssl) == 20); + ssl.verifyResult = INT64_C(0x100000002); + Require(SSL_get_verify_result_nid_postfix(&ssl) == INT64_C(0x100000002)); + ssl.verifyResult = -1; + Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); TestVersion(); + TestVerifyResult(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 7df58632f8..0b67fca116 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 336 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 335 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 18f69c623a7195c2029542a6e83799b2ab4559e8 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:57 +0200 Subject: [PATCH 053/612] feat(libSceLibreSsl): implement SSL_get_shutdown --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 16 ++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 1e22a2326f..d397ae2da3 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1253,11 +1253,6 @@ int APS5_VABI SSL_get_session_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_shutdown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 89c984e2d6..cb226a9590 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -91,4 +91,8 @@ std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).verifyResult; } +int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSsl* ssl) { + return Internal(ssl, __func__).shutdown; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 2d1a43528c..2bca9bdbdb 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -39,6 +39,7 @@ void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); +int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -109,11 +110,26 @@ static void TestVerifyResult() { Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); } +static void TestGetShutdown() { + TestSslInternal internal {}; + TestSsl ssl {}; + RequireThrows([] { SSL_get_shutdown_nid_postfix(nullptr); }); + RequireThrows([&] { SSL_get_shutdown_nid_postfix(&ssl); }); + ssl.internal = &internal; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 0); + internal.rwstate = 3; + internal.shutdown = 1; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 1); + internal.shutdown = 3; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); TestVersion(); TestVerifyResult(); + TestGetShutdown(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 0b67fca116..bc9412ed98 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 335 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 334 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From dcefe885aa97350e39368f28cbf5b13852bcbf59 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:55:07 +0200 Subject: [PATCH 054/612] feat(libSceLibreSsl): implement SSL_want --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 16 ++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index d397ae2da3..24976655ac 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1338,11 +1338,6 @@ int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void) { return 0; } -int APS5_VABI SSL_want_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_write_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index cb226a9590..69780d7342 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -95,4 +95,8 @@ int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSsl* ssl) { return Internal(ssl, __func__).shutdown; } +int APS5_VABI SSL_want_nid_postfix(const LibreSsl* ssl) { + return Internal(ssl, __func__).rwstate; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 2bca9bdbdb..04abd2ad93 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -40,6 +40,7 @@ void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); +int APS5_VABI SSL_want_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -124,6 +125,20 @@ static void TestGetShutdown() { Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); } +static void TestWant() { + TestSslInternal internal {}; + TestSsl ssl {}; + RequireThrows([] { SSL_want_nid_postfix(nullptr); }); + RequireThrows([&] { SSL_want_nid_postfix(&ssl); }); + ssl.internal = &internal; + Require(SSL_want_nid_postfix(&ssl) == 0); + internal.rwstate = 1; + Require(SSL_want_nid_postfix(&ssl) == 1); + internal.shutdown = 2; + internal.rwstate = 3; + Require(SSL_want_nid_postfix(&ssl) == 3); +} + int main() { TestGetSslCtx(); TestGetRbio(); @@ -131,5 +146,6 @@ int main() { TestVersion(); TestVerifyResult(); TestGetShutdown(); + TestWant(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index bc9412ed98..3db96c9e94 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 334 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 333 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown, SSL_want](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 2473e1c7e3017a1f36b5ba2adf2cb7b79e4f711e Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 06:01:21 +0100 Subject: [PATCH 055/612] refactor(libSceLibreSsl): declare the SSL accessor exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 53 +++++++++++++++++--- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 40 +-------------- core/libs/tests/GuestLibreSslSsl.cpp | 53 +++++++++----------- 3 files changed, 71 insertions(+), 75 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index cfee7755af..64f1094d8b 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -51,6 +51,45 @@ struct Sha512Context { std::uint32_t num, mdLength; }; +struct LibreSslSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct LibreSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + int server; + void* s3; + void* d1; + void* param; + void* cipherList; + void* cert; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* session; + int verifyMode; + int error; + int errorCode; + void* ctx; + std::int64_t verifyResult; + int references; + int clientVersion; + unsigned int maxSendFragment; + char* tlsextHostname; + int tlsextStatusType; + void* initialCtx; + void* encReadCtx; + void* readHash; + LibreSslSslInternal* internal; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -302,7 +341,7 @@ int APS5_VABI SSL_free_nid_postfix(void); int APS5_VABI SSL_get0_alpn_selected_nid_postfix(void); int APS5_VABI SSL_get0_param_nid_postfix(void); int APS5_VABI SSL_get1_session_nid_postfix(void); -int APS5_VABI SSL_get_SSL_CTX_nid_postfix(void); +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_get_certificate_nid_postfix(void); int APS5_VABI SSL_get_current_cipher_nid_postfix(void); int APS5_VABI SSL_get_error_nid_postfix(void); @@ -313,12 +352,12 @@ int APS5_VABI SSL_get_fd_nid_postfix(void); int APS5_VABI SSL_get_peer_cert_chain_nid_postfix(void); int APS5_VABI SSL_get_peer_certificate_nid_postfix(void); int APS5_VABI SSL_get_privatekey_nid_postfix(void); -int APS5_VABI SSL_get_rbio_nid_postfix(void); +void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_get_servername_nid_postfix(void); int APS5_VABI SSL_get_session_nid_postfix(void); -int APS5_VABI SSL_get_shutdown_nid_postfix(void); -int APS5_VABI SSL_get_verify_result_nid_postfix(void); -int APS5_VABI SSL_get_wbio_nid_postfix(void); +int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSsl* ssl); +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSsl* ssl); +void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_library_init_nid_postfix(void); int APS5_VABI SSL_load_error_strings_nid_postfix(void); int APS5_VABI SSL_new_nid_postfix(void); @@ -336,8 +375,8 @@ int APS5_VABI SSL_set_verify_nid_postfix(void); int APS5_VABI SSL_shutdown_nid_postfix(void); int APS5_VABI SSL_use_PrivateKey_ASN1_nid_postfix(void); int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void); -int APS5_VABI SSL_version_nid_postfix(void); -int APS5_VABI SSL_want_nid_postfix(void); +int APS5_VABI SSL_version_nid_postfix(const LibreSsl* ssl); +int APS5_VABI SSL_want_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_write_nid_postfix(void); std::uint64_t APS5_VABI SSLeay_nid_postfix(void); int APS5_VABI SSLv23_client_method_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 69780d7342..74c3e5aa99 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -1,48 +1,10 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslSslInternal { - unsigned char beforeRwstate[156]; - int rwstate; - unsigned char beforeShutdown[8]; - int shutdown; - unsigned char rest[260]; -}; - -struct LibreSsl { - int version; - const void* method; - void* rbio; - void* wbio; - void* bbio; - int server; - void* s3; - void* d1; - void* param; - void* cipherList; - void* cert; - unsigned int sidCtxLength; - unsigned char sidCtx[32]; - void* session; - int verifyMode; - int error; - int errorCode; - void* ctx; - std::int64_t verifyResult; - int references; - int clientVersion; - unsigned int maxSendFragment; - char* tlsextHostname; - int tlsextStatusType; - void* initialCtx; - void* encReadCtx; - void* readHash; - LibreSslSslInternal* internal; -}; - static_assert(sizeof(LibreSslSslInternal) == 432); static_assert(offsetof(LibreSslSslInternal, rwstate) == 156); static_assert(offsetof(LibreSslSslInternal, shutdown) == 168); diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 04abd2ad93..e2549fab2f 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -33,14 +34,8 @@ static_assert(sizeof(TestSslInternal) == 432 && offsetof(TestSslInternal, rwstat static_assert(sizeof(TestSsl) == 232 && offsetof(TestSsl, session) == 128 && offsetof(TestSsl, ctx) == 152); static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, internal) == 224); -extern "C" { -void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); -void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); -void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); -int APS5_VABI SSL_version_nid_postfix(const TestSsl*); -std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); -int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); -int APS5_VABI SSL_want_nid_postfix(const TestSsl*); +static const LibreSsl* Ssl(const TestSsl* ssl) { + return reinterpret_cast(ssl); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -59,10 +54,10 @@ static void TestGetSslCtx() { int session = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_SSL_CTX_nid_postfix(nullptr); }); - Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == nullptr); + Require(SSL_get_SSL_CTX_nid_postfix(Ssl(&ssl)) == nullptr); ssl.ctx = &context; ssl.session = &session; - Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); + Require(SSL_get_SSL_CTX_nid_postfix(Ssl(&ssl)) == &context); } static void TestGetRbio() { @@ -70,10 +65,10 @@ static void TestGetRbio() { int write = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_rbio_nid_postfix(nullptr); }); - Require(SSL_get_rbio_nid_postfix(&ssl) == nullptr); + Require(SSL_get_rbio_nid_postfix(Ssl(&ssl)) == nullptr); ssl.rbio = &read; ssl.wbio = &write; - Require(SSL_get_rbio_nid_postfix(&ssl) == &read); + Require(SSL_get_rbio_nid_postfix(Ssl(&ssl)) == &read); } static void TestGetWbio() { @@ -82,61 +77,61 @@ static void TestGetWbio() { int buffer = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_wbio_nid_postfix(nullptr); }); - Require(SSL_get_wbio_nid_postfix(&ssl) == nullptr); + Require(SSL_get_wbio_nid_postfix(Ssl(&ssl)) == nullptr); ssl.rbio = &read; ssl.wbio = &write; ssl.bbio = &buffer; - Require(SSL_get_wbio_nid_postfix(&ssl) == &write); + Require(SSL_get_wbio_nid_postfix(Ssl(&ssl)) == &write); } static void TestVersion() { TestSsl ssl {}; RequireThrows([] { SSL_version_nid_postfix(nullptr); }); - Require(SSL_version_nid_postfix(&ssl) == 0); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0); ssl.version = 0x0303; - Require(SSL_version_nid_postfix(&ssl) == 0x0303); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0x0303); ssl.version = 0xFEFF; - Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0xFEFF); } static void TestVerifyResult() { TestSsl ssl {}; RequireThrows([] { SSL_get_verify_result_nid_postfix(nullptr); }); - Require(SSL_get_verify_result_nid_postfix(&ssl) == 0); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == 0); ssl.verifyResult = 20; - Require(SSL_get_verify_result_nid_postfix(&ssl) == 20); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == 20); ssl.verifyResult = INT64_C(0x100000002); - Require(SSL_get_verify_result_nid_postfix(&ssl) == INT64_C(0x100000002)); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == INT64_C(0x100000002)); ssl.verifyResult = -1; - Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == -1); } static void TestGetShutdown() { TestSslInternal internal {}; TestSsl ssl {}; RequireThrows([] { SSL_get_shutdown_nid_postfix(nullptr); }); - RequireThrows([&] { SSL_get_shutdown_nid_postfix(&ssl); }); + RequireThrows([&] { SSL_get_shutdown_nid_postfix(Ssl(&ssl)); }); ssl.internal = &internal; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 0); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 0); internal.rwstate = 3; internal.shutdown = 1; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 1); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 1); internal.shutdown = 3; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 3); } static void TestWant() { TestSslInternal internal {}; TestSsl ssl {}; RequireThrows([] { SSL_want_nid_postfix(nullptr); }); - RequireThrows([&] { SSL_want_nid_postfix(&ssl); }); + RequireThrows([&] { SSL_want_nid_postfix(Ssl(&ssl)); }); ssl.internal = &internal; - Require(SSL_want_nid_postfix(&ssl) == 0); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 0); internal.rwstate = 1; - Require(SSL_want_nid_postfix(&ssl) == 1); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 1); internal.shutdown = 2; internal.rwstate = 3; - Require(SSL_want_nid_postfix(&ssl) == 3); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 3); } int main() { From d98e8b6e4a7559511583db406f8582019a722c18 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:00:27 +0200 Subject: [PATCH 056/612] feat(libSceLibreSsl): implement SSL_CTX_get_verify_mode --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 ++++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 53 ++++++++++++++++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 60 +++++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 3 +- 5 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/SslCtx.cpp create mode 100644 core/libs/tests/GuestLibreSslSslCtx.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 0eae0a5d98..c10885c0e4 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SslCtx.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -88,6 +88,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_ocsp PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_ssl_ctx_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSslCtx.cpp) + target_include_directories(guest_libressl_ssl_ctx_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_ctx_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_ctx_tests) + add_test(NAME guest_libressl_ssl_ctx COMMAND guest_libressl_ssl_ctx_tests) + set_tests_properties(guest_libressl_ssl_ctx PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl_ctx PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index cee8b3e31f..25f4c848ac 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1008,11 +1008,6 @@ int APS5_VABI SSL_CTX_get_ex_new_index_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_load_verify_locations_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp new file mode 100644 index 0000000000..f4566a99a9 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct LibreSslCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + int references; + void* extraCerts; + int verifyMode; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* param; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + LibreSslCtxInternal* internal; +}; + +static_assert(sizeof(LibreSslCtxInternal) == 464); +static_assert(offsetof(LibreSslCtxInternal, quietShutdown) == 292); +static_assert(sizeof(LibreSslCtx) == 120); +static_assert(offsetof(LibreSslCtx, certStore) == 16); +static_assert(offsetof(LibreSslCtx, verifyMode) == 48); +static_assert(offsetof(LibreSslCtx, defaultPasswdCallback) == 96); +static_assert(offsetof(LibreSslCtx, defaultPasswdCallbackUserdata) == 104); +static_assert(offsetof(LibreSslCtx, internal) == 112); + +namespace { + +LibreSslCtx& Context(LibreSslCtx* ctx, const char* function) { + if (!ctx) throw std::invalid_argument(std::string(function) + ": null context"); + return *ctx; +} + +} + +extern "C" { + +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx) { + return Context(ctx, __func__).verifyMode; +} + +} diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp new file mode 100644 index 0000000000..acbcf82c94 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -0,0 +1,60 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +struct TestCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct TestCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + unsigned char beforeVerifyMode[16]; + int verifyMode; + unsigned char beforeCallback[44]; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + TestCtxInternal* internal; +}; + +static_assert(sizeof(TestCtxInternal) == 464 && offsetof(TestCtxInternal, quietShutdown) == 292); +static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && offsetof(TestCtx, defaultPasswdCallback) == 96); + +extern "C" { +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL_CTX check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetVerifyMode() { + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_get_verify_mode_nid_postfix(nullptr); }); + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0); + ctx.verifyMode = 0x03; + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0x03); + ctx.verifyMode = -1; + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); +} + +int main() { + TestGetVerifyMode(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 1f8ebb39eb..833c8e43d7 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 340 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 339 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,6 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. +- [SSL_CTX_get_verify_mode](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach it, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 851e1ee4ebbdb817676dbb4769136b56f207a8a5 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:22 +0200 Subject: [PATCH 057/612] feat(libSceLibreSsl): implement SSL_CTX_get_cert_store --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 25f4c848ac..2cc8243526 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -993,11 +993,6 @@ int APS5_VABI SSL_CTX_free_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_get_cert_store_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index f4566a99a9..446eff72de 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -50,4 +50,8 @@ int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx) { return Context(ctx, __func__).verifyMode; } +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx) { + return Context(ctx, __func__).certStore; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index acbcf82c94..df6c8d1c3d 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -31,6 +31,7 @@ static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && o extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -54,7 +55,20 @@ static void TestGetVerifyMode() { Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); } +static void TestGetCertStore() { + int store = 0; + int ciphers = 0; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_get_cert_store_nid_postfix(nullptr); }); + Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == nullptr); + ctx.cipherList = &ciphers; + ctx.certStore = &store; + ctx.sessionTimeout = -1; + Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); +} + int main() { TestGetVerifyMode(); + TestGetCertStore(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 833c8e43d7..25853e2406 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 339 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 338 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_CTX_get_verify_mode](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach it, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 97a8311b0b59645e479de386b458ade7489c3629 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:31 +0200 Subject: [PATCH 058/612] feat(libSceLibreSsl): implement SSL_CTX_set_quiet_shutdown --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 6 ++++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 18 ++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 2cc8243526..aff283e87a 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1088,11 +1088,6 @@ int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 446eff72de..2a86c75d25 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -54,4 +54,10 @@ void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx) { return Context(ctx, __func__).certStore; } +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode) { + LibreSslCtx& context = Context(ctx, __func__); + if (!context.internal) throw std::invalid_argument(std::string(__func__) + ": null internal state"); + context.internal->quietShutdown = mode; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index df6c8d1c3d..38f3bb5fad 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -32,6 +32,7 @@ static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && o extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -67,8 +68,25 @@ static void TestGetCertStore() { Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); } +static void TestSetQuietShutdown() { + TestCtxInternal internal {}; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_set_quiet_shutdown_nid_postfix(nullptr, 1); }); + RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); }); + ctx.internal = &internal; + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); + Require(internal.quietShutdown == 1); + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, INT_MIN); + Require(internal.quietShutdown == INT_MIN); + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 0); + Require(internal.quietShutdown == 0); + for (unsigned char byte : internal.beforeQuietShutdown) Require(byte == 0); + for (unsigned char byte : internal.rest) Require(byte == 0); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); + TestSetQuietShutdown(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 25853e2406..ea317efaed 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 338 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 337 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From f40a0a8c81aa48f0ddd5e9dab417f0776bcc5610 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:41 +0200 Subject: [PATCH 059/612] feat(libSceLibreSsl): implement SSL_CTX_set_default_passwd_cb --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index aff283e87a..cb76e44842 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1058,11 +1058,6 @@ int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 2a86c75d25..573dbf8978 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -60,4 +60,8 @@ void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode context.internal->quietShutdown = mode; } +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* callback) { + Context(ctx, __func__).defaultPasswdCallback = callback; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index 38f3bb5fad..613aed964a 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -33,6 +33,7 @@ extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -84,9 +85,20 @@ static void TestSetQuietShutdown() { for (unsigned char byte : internal.rest) Require(byte == 0); } +static void TestSetPasswordCallback() { + int callback = 0; + TestCtx ctx {}; + RequireThrows([&] { SSL_CTX_set_default_passwd_cb_nid_postfix(nullptr, &callback); }); + SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, &callback); + Require(ctx.defaultPasswdCallback == &callback && ctx.defaultPasswdCallbackUserdata == nullptr); + SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, nullptr); + Require(ctx.defaultPasswdCallback == nullptr); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); TestSetQuietShutdown(); + TestSetPasswordCallback(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index ea317efaed..b286e5f6fe 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 337 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 336 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From b4a7e848611a6539fdcb25ab49219bd168d0c46a Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:51 +0200 Subject: [PATCH 060/612] feat(libSceLibreSsl): implement SSL_CTX_set_default_passwd_cb_userdata --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index cb76e44842..849e6cad01 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1058,11 +1058,6 @@ int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 573dbf8978..b70a0d9f12 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -64,4 +64,8 @@ void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* Context(ctx, __func__).defaultPasswdCallback = callback; } +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* ctx, void* userdata) { + Context(ctx, __func__).defaultPasswdCallbackUserdata = userdata; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index 613aed964a..fe76a282de 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -34,6 +34,7 @@ int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(TestCtx*, void*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -95,10 +96,21 @@ static void TestSetPasswordCallback() { Require(ctx.defaultPasswdCallback == nullptr); } +static void TestSetPasswordUserdata() { + int userdata = 0; + TestCtx ctx {}; + RequireThrows([&] { SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(nullptr, &userdata); }); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, &userdata); + Require(ctx.defaultPasswdCallbackUserdata == &userdata && ctx.defaultPasswdCallback == nullptr && ctx.internal == nullptr); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, nullptr); + Require(ctx.defaultPasswdCallbackUserdata == nullptr); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); TestSetQuietShutdown(); TestSetPasswordCallback(); + TestSetPasswordUserdata(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index b286e5f6fe..987d0f4c93 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 336 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 335 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -12,7 +12,7 @@ - [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb, SSL_CTX_set_default_passwd_cb_userdata](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From e96986f7f6941d109add651280c49a41b30613f8 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 05:59:27 +0100 Subject: [PATCH 061/612] refactor(libSceLibreSsl): declare the SSL_CTX accessor exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 32 +++++++++++++++--- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 23 +------------ core/libs/tests/GuestLibreSslSslCtx.cpp | 35 +++++++++----------- 3 files changed, 44 insertions(+), 46 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index cfee7755af..6b48f2aff1 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -51,6 +51,28 @@ struct Sha512Context { std::uint32_t num, mdLength; }; +struct LibreSslCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct LibreSslCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + int references; + void* extraCerts; + int verifyMode; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* param; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + LibreSslCtxInternal* internal; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -261,10 +283,10 @@ int APS5_VABI SSL_CTX_callback_ctrl_nid_postfix(void); int APS5_VABI SSL_CTX_check_private_key_nid_postfix(void); int APS5_VABI SSL_CTX_ctrl_nid_postfix(void); int APS5_VABI SSL_CTX_free_nid_postfix(void); -int APS5_VABI SSL_CTX_get_cert_store_nid_postfix(void); +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx); int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void); int APS5_VABI SSL_CTX_get_ex_new_index_nid_postfix(void); -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void); +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx); int APS5_VABI SSL_CTX_load_verify_locations_nid_postfix(void); int APS5_VABI SSL_CTX_load_verify_mem_nid_postfix(void); int APS5_VABI SSL_CTX_new_nid_postfix(void); @@ -276,13 +298,13 @@ int APS5_VABI SSL_CTX_set_cert_store_nid_postfix(void); int APS5_VABI SSL_CTX_set_cert_verify_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_cipher_list_nid_postfix(void); int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void); +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* callback); +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* ctx, void* userdata); int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void); int APS5_VABI SSL_CTX_set_info_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_msg_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(void); +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode); int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void); int APS5_VABI SSL_CTX_set_verify_nid_postfix(void); int APS5_VABI SSL_CTX_use_PrivateKey_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index b70a0d9f12..99eb4fa5a1 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -1,31 +1,10 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslCtxInternal { - unsigned char beforeQuietShutdown[292]; - int quietShutdown; - unsigned char rest[168]; -}; - -struct LibreSslCtx { - const void* method; - void* cipherList; - void* certStore; - std::int64_t sessionTimeout; - int references; - void* extraCerts; - int verifyMode; - unsigned int sidCtxLength; - unsigned char sidCtx[32]; - void* param; - void* defaultPasswdCallback; - void* defaultPasswdCallbackUserdata; - LibreSslCtxInternal* internal; -}; - static_assert(sizeof(LibreSslCtxInternal) == 464); static_assert(offsetof(LibreSslCtxInternal, quietShutdown) == 292); static_assert(sizeof(LibreSslCtx) == 120); diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index fe76a282de..3e77a25e02 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -29,12 +30,8 @@ struct TestCtx { static_assert(sizeof(TestCtxInternal) == 464 && offsetof(TestCtxInternal, quietShutdown) == 292); static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && offsetof(TestCtx, defaultPasswdCallback) == 96); -extern "C" { -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); -void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); -void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); -void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); -void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(TestCtx*, void*); +static LibreSslCtx* Context(TestCtx* ctx) { + return reinterpret_cast(ctx); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -51,11 +48,11 @@ template static void RequireThrows(TAction action, std::source static void TestGetVerifyMode() { TestCtx ctx {}; RequireThrows([] { SSL_CTX_get_verify_mode_nid_postfix(nullptr); }); - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == 0); ctx.verifyMode = 0x03; - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0x03); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == 0x03); ctx.verifyMode = -1; - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == -1); } static void TestGetCertStore() { @@ -63,24 +60,24 @@ static void TestGetCertStore() { int ciphers = 0; TestCtx ctx {}; RequireThrows([] { SSL_CTX_get_cert_store_nid_postfix(nullptr); }); - Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == nullptr); + Require(SSL_CTX_get_cert_store_nid_postfix(Context(&ctx)) == nullptr); ctx.cipherList = &ciphers; ctx.certStore = &store; ctx.sessionTimeout = -1; - Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); + Require(SSL_CTX_get_cert_store_nid_postfix(Context(&ctx)) == &store); } static void TestSetQuietShutdown() { TestCtxInternal internal {}; TestCtx ctx {}; RequireThrows([] { SSL_CTX_set_quiet_shutdown_nid_postfix(nullptr, 1); }); - RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); }); + RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 1); }); ctx.internal = &internal; - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 1); Require(internal.quietShutdown == 1); - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, INT_MIN); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), INT_MIN); Require(internal.quietShutdown == INT_MIN); - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 0); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 0); Require(internal.quietShutdown == 0); for (unsigned char byte : internal.beforeQuietShutdown) Require(byte == 0); for (unsigned char byte : internal.rest) Require(byte == 0); @@ -90,9 +87,9 @@ static void TestSetPasswordCallback() { int callback = 0; TestCtx ctx {}; RequireThrows([&] { SSL_CTX_set_default_passwd_cb_nid_postfix(nullptr, &callback); }); - SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, &callback); + SSL_CTX_set_default_passwd_cb_nid_postfix(Context(&ctx), &callback); Require(ctx.defaultPasswdCallback == &callback && ctx.defaultPasswdCallbackUserdata == nullptr); - SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, nullptr); + SSL_CTX_set_default_passwd_cb_nid_postfix(Context(&ctx), nullptr); Require(ctx.defaultPasswdCallback == nullptr); } @@ -100,9 +97,9 @@ static void TestSetPasswordUserdata() { int userdata = 0; TestCtx ctx {}; RequireThrows([&] { SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(nullptr, &userdata); }); - SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, &userdata); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(Context(&ctx), &userdata); Require(ctx.defaultPasswdCallbackUserdata == &userdata && ctx.defaultPasswdCallback == nullptr && ctx.internal == nullptr); - SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, nullptr); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(Context(&ctx), nullptr); Require(ctx.defaultPasswdCallbackUserdata == nullptr); } From 6facbf67438be8934b2cd544c56aa6c103e488b3 Mon Sep 17 00:00:00 2001 From: Daniel <62883984+lopdan@users.noreply.github.com> Date: Mon, 26 Oct 2026 16:10:09 +0100 Subject: [PATCH 062/612] style: fix CMakeLists indentation after merge --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 70 ++++++++++----------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 499fce4318..97ffe1a16e 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -107,43 +107,43 @@ endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/OcspBasicResponse.cpp) - if(BUILD_TESTING) - add_test_executable(guest_libressl_ocsp_basic_response_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslOcspBasicResponse.cpp) - target_include_directories(guest_libressl_ocsp_basic_response_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_libressl_ocsp_basic_response_tests PRIVATE libSceLibreSsl libc) - configure_windows_unwind(guest_libressl_ocsp_basic_response_tests) - add_test(NAME guest_libressl_ocsp_basic_response COMMAND guest_libressl_ocsp_basic_response_tests) - set_tests_properties(guest_libressl_ocsp_basic_response PROPERTIES TIMEOUT 30) - if(WIN32) - set_tests_properties(guest_libressl_ocsp_basic_response PROPERTIES ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") - endif() - endif() +if(BUILD_TESTING) + add_test_executable(guest_libressl_ocsp_basic_response_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslOcspBasicResponse.cpp) + target_include_directories(guest_libressl_ocsp_basic_response_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ocsp_basic_response_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_ocsp_basic_response_tests) + add_test(NAME guest_libressl_ocsp_basic_response COMMAND guest_libressl_ocsp_basic_response_tests) + set_tests_properties(guest_libressl_ocsp_basic_response PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ocsp_basic_response PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() - target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Alert.cpp) +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Alert.cpp) - if(BUILD_TESTING) - add_test_executable(guest_libressl_alert_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslAlert.cpp) - target_include_directories(guest_libressl_alert_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_libressl_alert_tests PRIVATE libSceLibreSsl libc) - configure_windows_unwind(guest_libressl_alert_tests) - add_test(NAME guest_libressl_alert COMMAND guest_libressl_alert_tests) - set_tests_properties(guest_libressl_alert PROPERTIES TIMEOUT 30) - if(WIN32) - set_tests_properties(guest_libressl_alert PROPERTIES ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") - endif() - endif() +if(BUILD_TESTING) + add_test_executable(guest_libressl_alert_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslAlert.cpp) + target_include_directories(guest_libressl_alert_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_alert_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_alert_tests) + add_test(NAME guest_libressl_alert COMMAND guest_libressl_alert_tests) + set_tests_properties(guest_libressl_alert PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_alert PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() - if(BUILD_TESTING) - add_test_executable(guest_libressl_version_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslVersion.cpp) - target_include_directories(guest_libressl_version_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_libressl_version_tests PRIVATE libSceLibreSsl libc) - configure_windows_unwind(guest_libressl_version_tests) - add_test(NAME guest_libressl_version COMMAND guest_libressl_version_tests) - set_tests_properties(guest_libressl_version PROPERTIES TIMEOUT 30) - if(WIN32) - set_tests_properties(guest_libressl_version PROPERTIES ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") +if(BUILD_TESTING) + add_test_executable(guest_libressl_version_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslVersion.cpp) + target_include_directories(guest_libressl_version_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_version_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_version_tests) + add_test(NAME guest_libressl_version COMMAND guest_libressl_version_tests) + set_tests_properties(guest_libressl_version PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_version PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") endif() endif() From 277d74f32228ef270c795abfb3744fdf32d5a7a7 Mon Sep 17 00:00:00 2001 From: Daniel <62883984+lopdan@users.noreply.github.com> Date: Mon, 26 Oct 2026 16:28:13 +0100 Subject: [PATCH 063/612] docs(libSceLibreSsl): update pending-export count to 338 --- docs/dev/debt/libSceLibreSsl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index dd13510653..96d5f989f5 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 340 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 338 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. From 9c80c8881fd6c6b5835d34518cec75c30372c32e Mon Sep 17 00:00:00 2001 From: Ahmad Mujahid Date: Mon, 26 Oct 2026 22:25:54 +0400 Subject: [PATCH 064/612] docs(debt): drop the libSceLibreSsl pending export count --- docs/dev/debt/libSceLibreSsl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 7f750b9f45..2042ff1394 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 331 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - the unimplemented named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. From d64657c5b0d29fe1732f8eb6da371374dedb48bd Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 21 Oct 2026 17:51:02 +0100 Subject: [PATCH 065/612] feat(agc): replay a capture several times in one process The first replay pass compiles every shader and pipeline the frames use, so its frame times mostly measure compilation. agc_frame_replay --repeat replays the frames n times: before each later pass it waits for the driver to go idle, clears the captured ranges, restores the snapshot, registers the video outputs the snapshot had again and makes the EOP flip reservations that were pending at the snapshot again (a token still reserved from the previous pass is kept). Frame times come from passes 2 to n, each pass is listed under passes, and agc_driver_frame_replay also replays its capture with --repeat 3. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceAgcDriver/tests/FrameReplay.cpp | 25 ++- .../prx/libSceAgcDriver/tools/FrameReplay.cpp | 145 ++++++++++++++---- docs/dev/BUILD.md | 4 +- 3 files changed, 138 insertions(+), 36 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp index 9b557243eb..d23bdebfb7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp @@ -188,15 +188,26 @@ void testCaptureAndReplay(const std::filesystem::path& tool) { const auto firstDump = capture / "first.bin"; const auto secondDump = capture / "second.bin"; - auto command = "\"" + tool.string() + "\" \"" + capture.string() + "\" --strict --dump " + std::to_string(at(layout.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + firstDump.string() + - "\" --dump " + std::to_string(at(late.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + secondDump.string() + "\""; + const auto replay = [&](const std::string& options, const std::filesystem::path& output) { + std::filesystem::remove(firstDump); + std::filesystem::remove(secondDump); + auto command = "\"" + tool.string() + "\" \"" + capture.string() + "\" --strict " + options + " --dump " + std::to_string(at(layout.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + + firstDump.string() + "\" --dump " + std::to_string(at(late.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + secondDump.string() + "\" > \"" + output.string() + "\""; #ifdef _WIN32 - command = "\"" + command + "\""; + command = "\"" + command + "\""; #endif - const auto code = std::system(command.c_str()); - check(code == 0, "agc_frame_replay exited with " + std::to_string(code)); - check(readFile(firstDump) == first, "the replay did not reproduce the first copy"); - check(readFile(secondDump) == second, "the replay did not reproduce the second copy"); + const auto code = std::system(command.c_str()); + check(code == 0, "agc_frame_replay " + options + " exited with " + std::to_string(code)); + check(readFile(firstDump) == first, "the replay " + options + " did not reproduce the first copy"); + check(readFile(secondDump) == second, "the replay " + options + " did not reproduce the second copy"); + const auto json = readFile(output); + return std::string(reinterpret_cast(json.data()), json.size()); + }; + replay("", capture / "replay.json"); + const auto repeated = replay("--repeat 3", capture / "repeat.json"); + std::size_t passes = 0; + for (auto found = repeated.find("\"frames\":2,\"p50\""); found != std::string::npos; found = repeated.find("\"frames\":2,\"p50\"", found + 1)) ++passes; + check(passes == 3, "the repeated replay did not replay both frames in each of three passes: " + repeated); } void testConcurrentCopies(const std::filesystem::path& tool) { diff --git a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp index 0ddce118db..815dd9a3c8 100644 --- a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -70,6 +71,7 @@ struct Options { std::chrono::milliseconds drainTimeout{10000}; bool release = true; bool synthesize = true; + std::size_t repeat = 1; }; class Frames { @@ -246,6 +248,21 @@ void mapHostRanges(const FrameCaptureReader& reader, Skipped& skipped) { #endif } +void restoreSnapshot(const FrameCaptureReader& reader, const Skipped& skipped) { + std::vector snapshot; + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Snapshot && !skipped.Overlaps(chunk)) snapshot.push_back(&chunk); + } + const auto workers = std::max(1, std::thread::hardware_concurrency()); + for (std::size_t first = 0; first < snapshot.size(); first += workers) { + std::vector> pending; + for (auto index = first; index < std::min(snapshot.size(), first + workers); ++index) { + pending.push_back(std::async(std::launch::async, [&reader, chunk = snapshot[index]] { restore(reader, *chunk); })); + } + for (auto& future : pending) future.get(); + } +} + Skipped restoreMemory(const FrameCaptureReader& reader) { std::uintptr_t base = 0; std::size_t size = 0; @@ -280,21 +297,18 @@ Skipped restoreMemory(const FrameCaptureReader& reader) { mutation.Add(pointer, bytes, (chunk.access & FrameRangeAccess::Readable) != 0, (chunk.access & FrameRangeAccess::Writable) != 0, gpu); } } - std::vector snapshot; - for (const auto& chunk : reader.Chunks()) { - if (chunk.kind == FrameMemoryKind::Snapshot && !skipped.Overlaps(chunk)) snapshot.push_back(&chunk); - } - const auto workers = std::max(1, std::thread::hardware_concurrency()); - for (std::size_t first = 0; first < snapshot.size(); first += workers) { - std::vector> pending; - for (auto index = first; index < std::min(snapshot.size(), first + workers); ++index) { - pending.push_back(std::async(std::launch::async, [&reader, chunk = snapshot[index]] { restore(reader, *chunk); })); - } - for (auto& future : pending) future.get(); - } + restoreSnapshot(reader, skipped); return skipped; } +void resetMemory(const FrameCaptureReader& reader, const Skipped& skipped) { + AgcDriverWaitIdle_nid_postfix(); + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Range && !skipped.Overlaps(chunk)) std::memset(reinterpret_cast(static_cast(chunk.address)), 0, static_cast(chunk.bytes)); + } + restoreSnapshot(reader, skipped); +} + std::pair progress() { std::uint64_t accepted = 0; std::uint64_t completed = 0; @@ -321,9 +335,11 @@ struct Replay { std::atomic earlyReleases{0}; std::atomic lateReleases{0}; std::atomic syntheticReleases{0}; + std::atomic keptReservations{0}; std::atomic sequenced{0}; std::uint64_t unfinished = 0; std::size_t threads = 0; + std::size_t pass = 0; bool recording = false; Clock::time_point startedAt{}; Clock::time_point endedAt{}; @@ -418,7 +434,15 @@ struct Replay { return; case FrameCallKind::ReserveEopFlip: { const auto token = static_cast(call.values.at(4)); - if (AgcDriverReserveEopFlipAt_nid_postfix(flipInfo(call), token) != token) throw std::runtime_error("frame replay: EOP flip token " + std::to_string(token) + " was not reserved"); + std::uint32_t reserved = 0; + try { + reserved = AgcDriverReserveEopFlipAt_nid_postfix(flipInfo(call), token); + } catch (const std::exception&) { + if (pass == 0) throw; + ++keptReservations; + return; + } + if (reserved != token) throw std::runtime_error("frame replay: EOP flip token " + std::to_string(token) + " was not reserved"); return; } case FrameCallKind::Suspend: @@ -533,6 +557,8 @@ struct Replay { std::size_t started = 0; std::exception_ptr failure; std::atomic finished{false}; + sequenced.store(0); + stalled.store(false); std::thread breaker([&] { if (!options.release) return; auto lastCompleted = progress().second; @@ -626,7 +652,7 @@ struct Replay { int usage() { std::fprintf(stderr, "usage: agc_frame_replay [--no-pace] [--no-release] [--release-after-ms ] [--strict]\n" - " [--dump
::]...\n" + " [--repeat ] [--dump
::]...\n" " replays the driver calls and guest memory that APS5_FRAME_CAPTURE= recorded, through this\n" " build's AGC driver with no title running, and prints one JSON line with the frame times\n" " --no-pace issues calls without waiting for the in-flight depth the title had\n" @@ -634,6 +660,8 @@ int usage() { " --release-after-ms milliseconds without a completion before awaited dwords are released (3)\n" " --no-synthesize releases a wait only with a value the capture recorded, never with its reference value\n" " --strict fails on a pacing timeout or a failed registration inside the captured frames\n" + " --repeat replays the frames n times in this process, restoring the snapshot before each pass;\n" + " with n > 1 the frame times come from passes 2 to n, after the first warmed the caches\n" " --dump writes guest memory to a file after the replay (hexadecimal or decimal)\n" " --frames writes each recorded and replayed display buffer as reference-.bmp and replay-.bmp\n"); return 2; @@ -714,6 +742,20 @@ FrameCheck compareFrames(const FrameCaptureReader& reader, const Options& option return check; } +struct Pass { + std::size_t firstFrame = 0; + std::size_t frames = 0; + double seconds = 0.0; + std::uint64_t paceTimeouts = 0; + std::uint64_t synthesized = 0; +}; + +double percentile(std::vector values, double fraction) { + if (values.empty()) return 0.0; + std::sort(values.begin(), values.end()); + return values[std::min(values.size() - 1, static_cast(fraction * static_cast(values.size())))]; +} + int run(const Options& options) { const auto loadStart = Clock::now(); const FrameCaptureReader reader(options.directory); @@ -728,12 +770,45 @@ int run(const Options& options) { const auto& calls = reader.Calls(); const auto frameStart = std::find_if(calls.begin(), calls.end(), [](const FrameCall& call) { return call.kind == FrameCallKind::FrameStart; }); for (auto call = calls.begin(); call != frameStart; ++call) replay.execute(*call); - replay.recording = true; - replay.startedAt = Clock::now(); - if (frameStart != calls.end()) replay.runWindow(std::span(calls).subspan(static_cast(frameStart - calls.begin()) + 1)); - if (frameStart == calls.end()) AgcDriverWaitIdle_nid_postfix(); - replay.endedAt = Clock::now(); - if (const auto failure = replay.frames.Failure()) std::rethrow_exception(failure); + std::set initialOutputs; + for (const auto& [handle, output] : replay.outputs) initialOutputs.insert(handle); + std::vector passes; + for (std::size_t pass = 0; pass < options.repeat && (pass == 0 || replay.unfinished == 0); ++pass) { + if (pass > 0) { + resetMemory(reader, replay.skipped); + replay.pass = pass; + replay.recording = false; + for (auto output = replay.outputs.begin(); output != replay.outputs.end();) { + if (initialOutputs.contains(output->first)) { + ++output; + continue; + } + AgcDriverUnregisterVideoOutput_nid_postfix(output->first, output->second); + output = replay.outputs.erase(output); + } + for (const auto handle : initialOutputs) { + if (replay.outputs.contains(handle)) continue; + auto output = std::make_shared(replay.frames); + AgcDriverRegisterVideoOutput_nid_postfix(handle, output); + replay.outputs.emplace(handle, std::move(output)); + } + for (auto call = calls.begin(); call != frameStart; ++call) { + if (call->kind == FrameCallKind::ReserveEopFlip) replay.execute(*call); + } + } + Pass measured{replay.frames.Times().size(), 0, 0.0, replay.paceTimeouts.load(), replay.syntheticReleases.load()}; + replay.recording = true; + replay.startedAt = Clock::now(); + if (frameStart != calls.end()) replay.runWindow(std::span(calls).subspan(static_cast(frameStart - calls.begin()) + 1)); + else AgcDriverWaitIdle_nid_postfix(); + replay.endedAt = Clock::now(); + if (const auto failure = replay.frames.Failure()) std::rethrow_exception(failure); + measured.frames = replay.frames.Times().size() - measured.firstFrame; + measured.seconds = std::chrono::duration(replay.endedAt - replay.startedAt).count(); + measured.paceTimeouts = replay.paceTimeouts.load() - measured.paceTimeouts; + measured.synthesized = replay.syntheticReleases.load() - measured.synthesized; + passes.push_back(measured); + } FrameCheck check; if (replay.unfinished == 0) { for (const auto& dump : options.dumps) writeDump(dump); @@ -742,15 +817,30 @@ int run(const Options& options) { std::fprintf(stderr, "[frame-replay] no dumps written: the GPU work did not finish\n"); } const auto ready = replay.frames.Times(); + const std::size_t firstMeasured = passes.size() > 1 ? 1 : 0; std::vector frameMs; - for (std::size_t index = 1; index < ready.size(); ++index) frameMs.push_back(std::chrono::duration(ready[index] - ready[index - 1]).count()); std::string frameList; - for (const auto value : frameMs) frameList += (frameList.empty() ? "" : ",") + std::to_string(static_cast(value)); - std::sort(frameMs.begin(), frameMs.end()); - const auto percentile = [&](double fraction) { return frameMs.empty() ? 0.0 : frameMs[std::min(frameMs.size() - 1, static_cast(fraction * static_cast(frameMs.size())))]; }; - const auto seconds = std::chrono::duration(replay.endedAt - replay.startedAt).count(); - std::printf("{\"frames\":%zu,\"replay_s\":%.3f,\"fps\":%.3f,\"frame_ms\":{\"p50\":%.3f,\"p90\":%.3f,\"max\":%.3f,\"each\":[%s]},\"submits\":%llu,\"delta_mib\":%.1f,\"pace_timeouts\":%llu,\"pace_wait_s\":%.3f,\"stale_registrations\":%llu,\"failed_registrations\":%llu,\"skipped_mib\":%.1f,\"threads\":%zu,\"released_waits\":%llu,\"of_them_from_earlier_writes\":%llu,\"of_them_synthesized\":%llu,\"unfinished\":%llu,\"frames_compared\":%zu,\"pixels_identical\":%.6f,\"max_channel_diff\":%d,\"load_s\":%.3f}\n", - ready.size(), seconds, seconds > 0 ? static_cast(ready.size()) / seconds : 0.0, percentile(0.5), percentile(0.9), frameMs.empty() ? 0.0 : frameMs.back(), frameList.c_str(), + std::string passList; + double seconds = 0.0; + std::size_t frames = 0; + for (std::size_t index = 0; index < passes.size(); ++index) { + const auto& measured = passes[index]; + std::vector passMs; + for (auto frame = measured.firstFrame + 1; frame < measured.firstFrame + measured.frames; ++frame) passMs.push_back(std::chrono::duration(ready[frame] - ready[frame - 1]).count()); + char entry[192]; + std::snprintf(entry, sizeof(entry), "%s{\"replay_s\":%.3f,\"frames\":%zu,\"p50\":%.3f,\"pace_timeouts\":%llu,\"synthesized\":%llu}", passList.empty() ? "" : ",", measured.seconds, measured.frames, + percentile(passMs, 0.5), static_cast(measured.paceTimeouts), static_cast(measured.synthesized)); + passList += entry; + if (index < firstMeasured) continue; + for (const auto value : passMs) frameList += (frameList.empty() ? "" : ",") + std::to_string(static_cast(value)); + frameMs.insert(frameMs.end(), passMs.begin(), passMs.end()); + seconds += measured.seconds; + frames += measured.frames; + } + const auto slowest = frameMs.empty() ? 0.0 : *std::max_element(frameMs.begin(), frameMs.end()); + std::printf("{\"frames\":%zu,\"replay_s\":%.3f,\"fps\":%.3f,\"frame_ms\":{\"p50\":%.3f,\"p90\":%.3f,\"max\":%.3f,\"each\":[%s]},\"passes\":[%s],\"kept_reservations\":%llu,\"submits\":%llu,\"delta_mib\":%.1f,\"pace_timeouts\":%llu,\"pace_wait_s\":%.3f,\"stale_registrations\":%llu,\"failed_registrations\":%llu,\"skipped_mib\":%.1f,\"threads\":%zu,\"released_waits\":%llu,\"of_them_from_earlier_writes\":%llu,\"of_them_synthesized\":%llu,\"unfinished\":%llu,\"frames_compared\":%zu,\"pixels_identical\":%.6f,\"max_channel_diff\":%d,\"load_s\":%.3f}\n", + frames, seconds, seconds > 0 ? static_cast(frames) / seconds : 0.0, percentile(frameMs, 0.5), percentile(frameMs, 0.9), slowest, frameList.c_str(), passList.c_str(), + static_cast(replay.keptReservations.load()), static_cast(replay.submits.load()), static_cast(replay.deltaBytes.load()) / (1024.0 * 1024.0), static_cast(replay.paceTimeouts.load()), replay.paceWaitedSeconds(), static_cast(replay.staleRegistrations.load()), static_cast(replay.failedRegistrations.load()), static_cast(replay.skipped.bytes) / (1024.0 * 1024.0), replay.threads, static_cast(replay.earlyReleases.load()), static_cast(replay.lateReleases.load()), @@ -771,6 +861,7 @@ int main(int argc, char** argv) { else if (argument == "--no-synthesize") options.synthesize = false; else if (argument == "--release-after-ms" && index + 1 < argc) options.releaseAfter = std::chrono::milliseconds(std::strtoll(argv[++index], nullptr, 10)); else if (argument == "--strict") options.strict = true; + else if (argument == "--repeat" && index + 1 < argc) options.repeat = std::max(1, std::strtoull(argv[++index], nullptr, 10)); else if (argument == "--dump" && index + 1 < argc) options.dumps.push_back(parseDump(argv[++index])); else if (argument == "--frames" && index + 1 < argc) options.frames = argv[++index]; else if (!argument.starts_with("-") && options.directory.empty()) options.directory = argument; diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md index 5c9b4ac5a4..31eabb83dd 100644 --- a/docs/dev/BUILD.md +++ b/docs/dev/BUILD.md @@ -228,14 +228,14 @@ On Windows use `agc_pm4_replay.exe` and prepend `build/core/libs/libs/unpatched` On Windows and Linux, set `APS5_FRAME_CAPTURE` to a directory to record a few frames of a title at the AGC driver boundary, then replay them with `agc_frame_replay` through any build of the driver with no title running. From the start of the process the driver keeps every shader registration and ABI resolution, video output registration and EOP flip reservation. At the frame boundary picked by `APS5_FRAME_CAPTURE_AT_FLIP` (default 1), or by the appearance of the file `APS5_FRAME_CAPTURE_TRIGGER` names (a number in it replaces the frame count), it waits for the driver to go idle and writes every committed, readable allocation of the guest allocation registry, the title's image included, as zstd chunks without their all-zero 64 KiB blocks. A frame ends at a CPU flip or at a submission that flips (a flip packet, or the EOP release of a reserved flip token); the reservation itself only records its token. For the next `APS5_FRAME_CAPTURE_FRAMES` (10) frames it records each submission (queue, command buffer address and size), flip and suspend point with the thread that made it and the accepted and completed serials, each preceded by the guest pages the write tracker saw the CPU write since the previous call that handed work to the GPU (`GuestMemory::CollectWrittenBlocks`, per 4 KiB where the tracker kept page stamps), read once the submissions already accepted have completed (a recorded submission, flip or suspend point reaches the driver before the recorder takes its next call, so that wait covers every call recorded before the delta); an allocation the registry gained after the snapshot is recorded there as a new range with its nonzero 64 KiB blocks. A submission ends a frame only when it flips itself, not when another thread's submission flipped while it was being submitted. Shader registrations and ABI resolutions only collect the blocks of the shader header and the arrays it points to, and the pages of the title's image only when their content changed. At the end it stores each display buffer the title presented, decoded, as a reference image. Without the guest write watch (`APS5_NO_WRITE_WATCH`, or no write watch on the host) the capture throws when it starts, since it could not record CPU stores. A capture is `calls.bin`, `memory.bin` and `capture.json` (commit, switches, sizes, warnings), and the recorder stops with a warning when its volume has less than 4 GiB free. A 20-frame capture in the Demon's Souls Nexus is 5.3 GiB of snapshot and 11.5 GiB of deltas before compression, 3.9 GiB on disk. -`agc_frame_replay [--no-pace] [--no-release] [--release-after-ms ] [--no-synthesize] [--strict] [--dump
::]... [--frames ]` maps the captured ranges at their addresses and registers them with their access (a range the replaying process already maps writable, such as the driver's fixed memory, is only refilled, and a non-GPU range it uses itself is skipped and counted), repeats the registrations, then replays the calls of each captured thread on a thread of its own, started in captured order. Before a call that hands work to the GPU it waits until no more submissions are in flight than when the title made it (`--no-pace` turns this off). Titles release GPU waits from threads that never call the driver, so when the driver completes nothing for 3 ms the replay reads the waits its queues are blocked on (`AgcDriverAwaitedWaits`) and writes the next recorded CPU value of the awaited dword that satisfies the compare, else the last earlier one, else a value derived from the reference (`--no-synthesize` turns this last step off). It prints one JSON line with the frame count and times, the submissions, the pacing wait, the released waits and how many were derived, the submissions that never completed and, for captures with reference images, the share of identical pixels and the largest channel difference of the replayed display buffers (`--frames` writes both images). Effects that depend on timing (temporal antialiasing, particles) make pixels differ, so expect a visual match rather than identical frames. Replay times follow the GPU clock state: lock the clocks (`nvidia-smi -lgc`, `-lmc`) when comparing two drivers. +`agc_frame_replay [--no-pace] [--no-release] [--release-after-ms ] [--no-synthesize] [--strict] [--repeat ] [--dump
::]... [--frames ]` maps the captured ranges at their addresses and registers them with their access (a range the replaying process already maps writable, such as the driver's fixed memory, is only refilled, and a non-GPU range it uses itself is skipped and counted), repeats the registrations, then replays the calls of each captured thread on a thread of its own, started in captured order. Before a call that hands work to the GPU it waits until no more submissions are in flight than when the title made it (`--no-pace` turns this off). Titles release GPU waits from threads that never call the driver, so when the driver completes nothing for 3 ms the replay reads the waits its queues are blocked on (`AgcDriverAwaitedWaits`) and writes the next recorded CPU value of the awaited dword that satisfies the compare, else the last earlier one, else a value derived from the reference (`--no-synthesize` turns this last step off). It prints one JSON line with the frame count and times, the submissions, the pacing wait, the released waits and how many were derived, the submissions that never completed and, for captures with reference images, the share of identical pixels and the largest channel difference of the replayed display buffers (`--frames` writes both images). Effects that depend on timing (temporal antialiasing, particles) make pixels differ, so expect a visual match rather than identical frames. The first pass compiles every shader and pipeline the frames use, so `--repeat ` replays the frames n times in one process: before each later pass it waits for the driver to go idle, clears the captured ranges, restores the snapshot and makes the reservations that were pending at the snapshot again (a token still reserved from the previous pass is kept), and the frame times then come from passes 2 to n, with each pass listed under `passes`. Later passes replay the same frames, so driver caches keyed on values that change every frame in the title (ring addresses in user data, for example) can hit entries of an earlier pass; compare such changes on the first pass with warm disk caches instead. Replay times follow the GPU clock state: lock the clocks (`nvidia-smi -lgc`, `-lmc`) when comparing two drivers. ```sh cmake --build build --target agc_frame_replay build/core/libs/prx/libSceAgcDriver/agc_frame_replay capture ``` -On Windows use `agc_frame_replay.exe` and prepend `build/core/libs/libs/unpatched` to the process `PATH`. With `BUILD_TESTING=ON`, `agc_frame_capture` checks the file format (round trip, zero-block elision, truncated and corrupt files), and `agc_driver_frame_capture_eop` reserves and submits six EOP flips with `APS5_FRAME_CAPTURE_FRAMES=3` and checks that the capture starts after the first flipping submission and holds three frames of one reservation and one submission each, then that a flipping submission on another thread leaves this thread's flip count unchanged, and `agc_driver_frame_replay` captures two frames of CPU writes followed by DMA copies, the second from a block committed during the capture, replays them in `agc_frame_replay` and compares the copies. +On Windows use `agc_frame_replay.exe` and prepend `build/core/libs/libs/unpatched` to the process `PATH`. With `BUILD_TESTING=ON`, `agc_frame_capture` checks the file format (round trip, zero-block elision, truncated and corrupt files), and `agc_driver_frame_capture_eop` reserves and submits six EOP flips with `APS5_FRAME_CAPTURE_FRAMES=3` and checks that the capture starts after the first flipping submission and holds three frames of one reservation and one submission each, then that a flipping submission on another thread leaves this thread's flip count unchanged, and `agc_driver_frame_replay` captures two frames of CPU writes followed by DMA copies, the second from a block committed during the capture, replays them in `agc_frame_replay`, once and with `--repeat 3`, and compares the copies. ## Pipeline statistics From 8a17ba39da5279b4a278cd5c9e4aa7e3363cd19c Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 21 Oct 2026 17:51:19 +0100 Subject: [PATCH 066/612] fix(agc): keep draining a replay until no submission completes for ten minutes The drain gave up after 10 s without a completion even while the driver was still compiling or preparing the work, which reported unfinished submissions on full titles and left a process that could not exit. A blocked wait on one queue does not mean the replay is stuck either: another queue can still be preparing the work that releases it. The drain now gives up only after ten minutes without a completion and reports the waits the queues are blocked on every ten seconds and when it gives up. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceAgcDriver/tools/FrameReplay.cpp | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp index 815dd9a3c8..c7a83a3c81 100644 --- a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp @@ -69,6 +69,7 @@ struct Options { std::chrono::milliseconds memoryPaceTimeout{10000}; std::chrono::milliseconds releaseAfter{3}; std::chrono::milliseconds drainTimeout{10000}; + std::chrono::milliseconds busyDrainTimeout{600000}; bool release = true; bool synthesize = true; std::size_t repeat = 1; @@ -350,9 +351,24 @@ struct Replay { void report() const { const auto [accepted, completed] = progress(); - std::fprintf(stderr, "[frame-replay] %llu submits, %zu frames ready, %.1f s since the first frame, pacing waited %.1f s (%llu timeouts), serials %llu/%llu\n", + std::fprintf(stderr, "[frame-replay] %llu submits, %zu frames ready, %.1f s since the first frame, pacing waited %.1f s (%llu timeouts), serials %llu/%llu, blocked on%s\n", static_cast(submits.load()), frames.Times().size(), std::chrono::duration(Clock::now() - startedAt).count(), paceWaitedSeconds(), - static_cast(paceTimeouts.load()), static_cast(completed), static_cast(accepted)); + static_cast(paceTimeouts.load()), static_cast(completed), static_cast(accepted), describeBlocked().c_str()); + } + + static std::string describeBlocked() { + std::array awaited{}; + const auto count = AgcDriverAwaitedWaits_nid_postfix(awaited.data(), awaited.size()); + std::string text; + for (std::size_t slot = 0; slot < count; ++slot) { + const auto& wait = awaited[slot]; + if (wait.address == 0) continue; + char entry[128]; + std::snprintf(entry, sizeof(entry), " 0x%llx (function %u, reference 0x%llx, mask 0x%llx, now 0x%llx)", static_cast(wait.address), static_cast(wait.function), + static_cast(wait.reference), static_cast(wait.mask), static_cast(load(wait.address, wait.bytes == 8 ? 8 : 4))); + text += entry; + } + return text.empty() ? " nothing" : text; } static bool paced(FrameCallKind kind) { @@ -614,16 +630,23 @@ struct Replay { void drain() { auto lastCompleted = progress().second; auto lastProgress = Clock::now(); + auto lastReport = lastProgress; while (true) { const auto [accepted, completed] = progress(); unfinished = accepted - completed; if (unfinished == 0) return; + const auto now = Clock::now(); if (completed != lastCompleted) { lastCompleted = completed; - lastProgress = Clock::now(); - } else if (Clock::now() - lastProgress >= options.drainTimeout) { - std::fprintf(stderr, "[frame-replay] %llu submissions did not complete after the last captured call (serials %llu/%llu)\n", static_cast(unfinished), - static_cast(completed), static_cast(accepted)); + lastProgress = now; + } else if (now - lastReport >= options.drainTimeout) { + lastReport = now; + std::fprintf(stderr, "[frame-replay] draining: %llu submissions pending for %.0f s (serials %llu/%llu), blocked on%s\n", static_cast(unfinished), + std::chrono::duration(now - lastProgress).count(), static_cast(completed), static_cast(accepted), describeBlocked().c_str()); + } + if (now - lastProgress >= options.busyDrainTimeout) { + std::fprintf(stderr, "[frame-replay] %llu submissions did not complete after the last captured call (serials %llu/%llu), blocked on%s\n", static_cast(unfinished), + static_cast(completed), static_cast(accepted), describeBlocked().c_str()); return; } std::this_thread::sleep_for(std::chrono::milliseconds(5)); From f691bc9f415e524be1ffb508db323b97e62e4394 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 21 Oct 2026 12:55:06 +0100 Subject: [PATCH 067/612] fix(agc): replay frames with the title's pipeline cache and journal already applied A replay's device has no window, so it never loaded the title's Vulkan pipeline cache file and compiled every pipeline the captured frames used again, minutes for the first Demon's Souls frames. It also replayed the pipeline journal in the background during the measured frames, which the title did at boot. APS5_HEADLESS_PIPELINE_CACHE makes a device without a window load the file, and agc_frame_replay sets it unless --cold-pipelines is given. The driver exports AgcDriverWaitForPipelineJournal, and the replay calls it after the registrations and reports the wait as journal_s. Co-Authored-By: Claude Opus 5.5 --- .../libSceAgcDriver/Execution/include/Driver.hpp | 1 + .../Execution/include/Driver/Driver.hpp | 1 + .../Execution/src/Driver/EntryPoints.cpp | 4 ++++ .../Execution/src/Driver/Lifecycle.cpp | 10 ++++++++++ .../libSceAgcDriver/Execution/src/VulkanDevice.cpp | 3 ++- .../libs/prx/libSceAgcDriver/tools/FrameReplay.cpp | 14 +++++++++++--- docs/dev/BUILD.md | 2 +- docs/dev/ENVIRONMENT.md | 1 + 8 files changed, 31 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp index 03e68cd1b7..7c89e376ad 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp @@ -32,6 +32,7 @@ extern "C" void AgcDriverRequestScreenshot_nid_postfix(); extern "C" void AgcDriverSuspendPoint_nid_postfix(); extern "C" void AgcDriverSerialProgress_nid_postfix(std::uint64_t* accepted, std::uint64_t* completed); extern "C" std::size_t AgcDriverAwaitedWaits_nid_postfix(AgcDriver::AwaitedWait* waits, std::size_t capacity); +extern "C" void AgcDriverWaitForPipelineJournal_nid_postfix(); extern "C" std::uint64_t AgcDriverDispatchesAdoptedAhead_nid_postfix(); extern "C" std::uint64_t AgcDriverDispatchesRejectedAhead_nid_postfix(); extern "C" void AgcDriverSetAheadAdoptHook_nid_postfix(void (*hook)()); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index 7d89a2589f..602d3022ef 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -73,6 +73,7 @@ public: std::pair SerialProgress(); std::uint64_t FlipSubmissions() const; std::vector AwaitedWaits(); + void WaitForPipelineJournal(); bool WaitIdleFor(std::chrono::milliseconds timeout); void RegisterVideoOutput(std::uint32_t handle, const std::shared_ptr& output); void UnregisterVideoOutput(std::uint32_t handle, const std::shared_ptr& output); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp index fbe526d30a..b02459557b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp @@ -141,6 +141,10 @@ extern "C" std::size_t AgcDriverAwaitedWaits_nid_postfix(AgcDriver::AwaitedWait* return count; } +extern "C" void AgcDriverWaitForPipelineJournal_nid_postfix() { + AgcDriver::DriverDetail::Driver::Get().WaitForPipelineJournal(); +} + extern "C" void AgcDriverSerialProgress_nid_postfix(std::uint64_t* accepted, std::uint64_t* completed) { const auto [submitted, done] = AgcDriver::DriverDetail::Driver::Get().SerialProgress(); *accepted = submitted; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp index e12649fac4..9b4c42f4c8 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp @@ -278,6 +278,16 @@ std::vector Driver::AwaitedWaits() { return result; } +void Driver::WaitForPipelineJournal() { + std::shared_ptr localDevice = device.Load(); + if (localDevice == nullptr) { + std::lock_guard gpuLock(GuestMemory::GpuMutex()); + if (device == nullptr) device = std::make_shared(); + localDevice = device; + } + localDevice->WaitForPipelineJournalReplay(); +} + std::pair Driver::SerialProgress() { std::lock_guard lock(mutex); return {accepted, completed}; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 89305ccae5..78f7a079d9 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -2180,7 +2180,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : Graphics::PrepareImportWatch(graphicsContext()); state->bufferPool = std::make_shared(graphicsContext()); state->emptyBuffer = std::make_unique(graphicsContext(), Graphics::EmptyBufferBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); - state->pipelineCache = std::make_unique(graphicsContext(), state->properties, window != nullptr || compileOnly); + static const bool headlessPipelineCache = std::getenv("APS5_HEADLESS_PIPELINE_CACHE") != nullptr; + state->pipelineCache = std::make_unique(graphicsContext(), state->properties, window != nullptr || compileOnly || headlessPipelineCache); state->detiler = std::make_unique(graphicsContext()); state->textureCache = std::make_unique(graphicsContext()); state->colorTransfer = std::make_unique(graphicsContext()); diff --git a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp index c7a83a3c81..2235f258d6 100644 --- a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp @@ -73,6 +73,7 @@ struct Options { bool release = true; bool synthesize = true; std::size_t repeat = 1; + bool coldPipelines = false; }; class Frames { @@ -675,7 +676,7 @@ struct Replay { int usage() { std::fprintf(stderr, "usage: agc_frame_replay [--no-pace] [--no-release] [--release-after-ms ] [--strict]\n" - " [--repeat ] [--dump
::]...\n" + " [--repeat ] [--cold-pipelines] [--dump
::]...\n" " replays the driver calls and guest memory that APS5_FRAME_CAPTURE= recorded, through this\n" " build's AGC driver with no title running, and prints one JSON line with the frame times\n" " --no-pace issues calls without waiting for the in-flight depth the title had\n" @@ -685,6 +686,7 @@ int usage() { " --strict fails on a pacing timeout or a failed registration inside the captured frames\n" " --repeat replays the frames n times in this process, restoring the snapshot before each pass;\n" " with n > 1 the frame times come from passes 2 to n, after the first warmed the caches\n" + " --cold-pipelines starts with an empty Vulkan pipeline cache instead of the title's pipeline cache file\n" " --dump writes guest memory to a file after the replay (hexadecimal or decimal)\n" " --frames writes each recorded and replayed display buffer as reference-.bmp and replay-.bmp\n"); return 2; @@ -793,6 +795,9 @@ int run(const Options& options) { const auto& calls = reader.Calls(); const auto frameStart = std::find_if(calls.begin(), calls.end(), [](const FrameCall& call) { return call.kind == FrameCallKind::FrameStart; }); for (auto call = calls.begin(); call != frameStart; ++call) replay.execute(*call); + const auto journalStart = Clock::now(); + AgcDriverWaitForPipelineJournal_nid_postfix(); + const auto journalSeconds = std::chrono::duration(Clock::now() - journalStart).count(); std::set initialOutputs; for (const auto& [handle, output] : replay.outputs) initialOutputs.insert(handle); std::vector passes; @@ -861,14 +866,14 @@ int run(const Options& options) { frames += measured.frames; } const auto slowest = frameMs.empty() ? 0.0 : *std::max_element(frameMs.begin(), frameMs.end()); - std::printf("{\"frames\":%zu,\"replay_s\":%.3f,\"fps\":%.3f,\"frame_ms\":{\"p50\":%.3f,\"p90\":%.3f,\"max\":%.3f,\"each\":[%s]},\"passes\":[%s],\"kept_reservations\":%llu,\"submits\":%llu,\"delta_mib\":%.1f,\"pace_timeouts\":%llu,\"pace_wait_s\":%.3f,\"stale_registrations\":%llu,\"failed_registrations\":%llu,\"skipped_mib\":%.1f,\"threads\":%zu,\"released_waits\":%llu,\"of_them_from_earlier_writes\":%llu,\"of_them_synthesized\":%llu,\"unfinished\":%llu,\"frames_compared\":%zu,\"pixels_identical\":%.6f,\"max_channel_diff\":%d,\"load_s\":%.3f}\n", + std::printf("{\"frames\":%zu,\"replay_s\":%.3f,\"fps\":%.3f,\"frame_ms\":{\"p50\":%.3f,\"p90\":%.3f,\"max\":%.3f,\"each\":[%s]},\"passes\":[%s],\"kept_reservations\":%llu,\"submits\":%llu,\"delta_mib\":%.1f,\"pace_timeouts\":%llu,\"pace_wait_s\":%.3f,\"stale_registrations\":%llu,\"failed_registrations\":%llu,\"skipped_mib\":%.1f,\"threads\":%zu,\"released_waits\":%llu,\"of_them_from_earlier_writes\":%llu,\"of_them_synthesized\":%llu,\"unfinished\":%llu,\"frames_compared\":%zu,\"pixels_identical\":%.6f,\"max_channel_diff\":%d,\"load_s\":%.3f,\"journal_s\":%.3f}\n", frames, seconds, seconds > 0 ? static_cast(frames) / seconds : 0.0, percentile(frameMs, 0.5), percentile(frameMs, 0.9), slowest, frameList.c_str(), passList.c_str(), static_cast(replay.keptReservations.load()), static_cast(replay.submits.load()), static_cast(replay.deltaBytes.load()) / (1024.0 * 1024.0), static_cast(replay.paceTimeouts.load()), replay.paceWaitedSeconds(), static_cast(replay.staleRegistrations.load()), static_cast(replay.failedRegistrations.load()), static_cast(replay.skipped.bytes) / (1024.0 * 1024.0), replay.threads, static_cast(replay.earlyReleases.load()), static_cast(replay.lateReleases.load()), static_cast(replay.syntheticReleases.load()), - static_cast(replay.unfinished), check.compared, check.identical, check.maxDifference, std::chrono::duration(loaded - loadStart).count()); + static_cast(replay.unfinished), check.compared, check.identical, check.maxDifference, std::chrono::duration(loaded - loadStart).count(), journalSeconds); std::fflush(stdout); return replay.unfinished == 0 ? 0 : 3; } @@ -885,6 +890,7 @@ int main(int argc, char** argv) { else if (argument == "--release-after-ms" && index + 1 < argc) options.releaseAfter = std::chrono::milliseconds(std::strtoll(argv[++index], nullptr, 10)); else if (argument == "--strict") options.strict = true; else if (argument == "--repeat" && index + 1 < argc) options.repeat = std::max(1, std::strtoull(argv[++index], nullptr, 10)); + else if (argument == "--cold-pipelines") options.coldPipelines = true; else if (argument == "--dump" && index + 1 < argc) options.dumps.push_back(parseDump(argv[++index])); else if (argument == "--frames" && index + 1 < argc) options.frames = argv[++index]; else if (!argument.starts_with("-") && options.directory.empty()) options.directory = argument; @@ -893,8 +899,10 @@ int main(int argc, char** argv) { if (options.directory.empty()) return usage(); #ifdef _WIN32 _putenv_s("APS5_FRAME_CAPTURE", ""); + if (!options.coldPipelines) _putenv_s("APS5_HEADLESS_PIPELINE_CACHE", "1"); #else unsetenv("APS5_FRAME_CAPTURE"); + if (!options.coldPipelines) setenv("APS5_HEADLESS_PIPELINE_CACHE", "1", 1); #endif int code = 2; try { diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md index 31eabb83dd..5b9e00b8a6 100644 --- a/docs/dev/BUILD.md +++ b/docs/dev/BUILD.md @@ -228,7 +228,7 @@ On Windows use `agc_pm4_replay.exe` and prepend `build/core/libs/libs/unpatched` On Windows and Linux, set `APS5_FRAME_CAPTURE` to a directory to record a few frames of a title at the AGC driver boundary, then replay them with `agc_frame_replay` through any build of the driver with no title running. From the start of the process the driver keeps every shader registration and ABI resolution, video output registration and EOP flip reservation. At the frame boundary picked by `APS5_FRAME_CAPTURE_AT_FLIP` (default 1), or by the appearance of the file `APS5_FRAME_CAPTURE_TRIGGER` names (a number in it replaces the frame count), it waits for the driver to go idle and writes every committed, readable allocation of the guest allocation registry, the title's image included, as zstd chunks without their all-zero 64 KiB blocks. A frame ends at a CPU flip or at a submission that flips (a flip packet, or the EOP release of a reserved flip token); the reservation itself only records its token. For the next `APS5_FRAME_CAPTURE_FRAMES` (10) frames it records each submission (queue, command buffer address and size), flip and suspend point with the thread that made it and the accepted and completed serials, each preceded by the guest pages the write tracker saw the CPU write since the previous call that handed work to the GPU (`GuestMemory::CollectWrittenBlocks`, per 4 KiB where the tracker kept page stamps), read once the submissions already accepted have completed (a recorded submission, flip or suspend point reaches the driver before the recorder takes its next call, so that wait covers every call recorded before the delta); an allocation the registry gained after the snapshot is recorded there as a new range with its nonzero 64 KiB blocks. A submission ends a frame only when it flips itself, not when another thread's submission flipped while it was being submitted. Shader registrations and ABI resolutions only collect the blocks of the shader header and the arrays it points to, and the pages of the title's image only when their content changed. At the end it stores each display buffer the title presented, decoded, as a reference image. Without the guest write watch (`APS5_NO_WRITE_WATCH`, or no write watch on the host) the capture throws when it starts, since it could not record CPU stores. A capture is `calls.bin`, `memory.bin` and `capture.json` (commit, switches, sizes, warnings), and the recorder stops with a warning when its volume has less than 4 GiB free. A 20-frame capture in the Demon's Souls Nexus is 5.3 GiB of snapshot and 11.5 GiB of deltas before compression, 3.9 GiB on disk. -`agc_frame_replay [--no-pace] [--no-release] [--release-after-ms ] [--no-synthesize] [--strict] [--repeat ] [--dump
::]... [--frames ]` maps the captured ranges at their addresses and registers them with their access (a range the replaying process already maps writable, such as the driver's fixed memory, is only refilled, and a non-GPU range it uses itself is skipped and counted), repeats the registrations, then replays the calls of each captured thread on a thread of its own, started in captured order. Before a call that hands work to the GPU it waits until no more submissions are in flight than when the title made it (`--no-pace` turns this off). Titles release GPU waits from threads that never call the driver, so when the driver completes nothing for 3 ms the replay reads the waits its queues are blocked on (`AgcDriverAwaitedWaits`) and writes the next recorded CPU value of the awaited dword that satisfies the compare, else the last earlier one, else a value derived from the reference (`--no-synthesize` turns this last step off). It prints one JSON line with the frame count and times, the submissions, the pacing wait, the released waits and how many were derived, the submissions that never completed and, for captures with reference images, the share of identical pixels and the largest channel difference of the replayed display buffers (`--frames` writes both images). Effects that depend on timing (temporal antialiasing, particles) make pixels differ, so expect a visual match rather than identical frames. The first pass compiles every shader and pipeline the frames use, so `--repeat ` replays the frames n times in one process: before each later pass it waits for the driver to go idle, clears the captured ranges, restores the snapshot and makes the reservations that were pending at the snapshot again (a token still reserved from the previous pass is kept), and the frame times then come from passes 2 to n, with each pass listed under `passes`. Later passes replay the same frames, so driver caches keyed on values that change every frame in the title (ring addresses in user data, for example) can hit entries of an earlier pass; compare such changes on the first pass with warm disk caches instead. Replay times follow the GPU clock state: lock the clocks (`nvidia-smi -lgc`, `-lmc`) when comparing two drivers. +`agc_frame_replay [--no-pace] [--no-release] [--release-after-ms ] [--no-synthesize] [--strict] [--repeat ] [--cold-pipelines] [--dump
::]... [--frames ]` maps the captured ranges at their addresses and registers them with their access (a range the replaying process already maps writable, such as the driver's fixed memory, is only refilled, and a non-GPU range it uses itself is skipped and counted), repeats the registrations, then replays the calls of each captured thread on a thread of its own, started in captured order. Before a call that hands work to the GPU it waits until no more submissions are in flight than when the title made it (`--no-pace` turns this off). Titles release GPU waits from threads that never call the driver, so when the driver completes nothing for 3 ms the replay reads the waits its queues are blocked on (`AgcDriverAwaitedWaits`) and writes the next recorded CPU value of the awaited dword that satisfies the compare, else the last earlier one, else a value derived from the reference (`--no-synthesize` turns this last step off). It prints one JSON line with the frame count and times, the submissions, the pacing wait, the released waits and how many were derived, the submissions that never completed and, for captures with reference images, the share of identical pixels and the largest channel difference of the replayed display buffers (`--frames` writes both images). Effects that depend on timing (temporal antialiasing, particles) make pixels differ, so expect a visual match rather than identical frames. The replay's device has no window, but it loads the title's Vulkan pipeline cache file (`vk-pipelines-*.bin` in the shader cache directory) as the windowed device does, so pipelines the title already compiled are not compiled again (`--cold-pipelines` starts with an empty one; the driver switch is `APS5_HEADLESS_PIPELINE_CACHE`). The title replayed its pipeline journal at boot, long before the captured frames, so after the registrations the replay waits for the driver's journal replay to finish before the first pass (`journal_s` in the JSON line). The first pass still compiles every shader and pipeline the frames use that neither cache holds, so `--repeat ` replays the frames n times in one process: before each later pass it waits for the driver to go idle, clears the captured ranges, restores the snapshot and makes the reservations that were pending at the snapshot again (a token still reserved from the previous pass is kept), and the frame times then come from passes 2 to n, with each pass listed under `passes`. Later passes replay the same frames, so driver caches keyed on values that change every frame in the title (ring addresses in user data, for example) can hit entries of an earlier pass; compare such changes on the first pass with warm disk caches instead. Replay times follow the GPU clock state: lock the clocks (`nvidia-smi -lgc`, `-lmc`) when comparing two drivers. ```sh cmake --build build --target agc_frame_replay diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index 13dd311610..79b0ee715a 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -172,6 +172,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_GPU_WAIT_TIMEOUT_MS` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp` | 1000 | milliseconds a GPU wait may run before a warning is printed | | `APS5_GROUP_CAPTURE_ALL_QUEUES` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp` | | resolves group-ahead capture on queue 0 as well | | `APS5_HANDOFF_SPIN_US` | integer | `core/libs/prx/libSceAgcDriver/Execution/include/HandoffSpin.hpp` | | microseconds a thread spins for a handed-off draw's work or for the GPU mutex before blocking, clamped to 0..10000; unset spins 150 us when the process has more than 8 usable processors and not at all otherwise | +| `APS5_HEADLESS_PIPELINE_CACHE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | loads the pipeline cache file on a device without a window too; `agc_frame_replay` sets it unless `--cold-pipelines` is given | | `APS5_HEAP_MIRROR_MIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | 24576 | budget in MiB for the heap mirrors held for registered allocations | | `APS5_HOOK_FLUSH_CPU_BLOCKS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | set to 0 flushes pending images whose every 64 KiB block the CPU wrote, instead of skipping them | | `APS5_HOOK_FULL_SYNC` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | makes the flush hook's recorded-store wait a full Sync under the GpuMutex | From 96603d9851fcf8dba8b464f461f21baa56814e90 Mon Sep 17 00:00:00 2001 From: BunnyFl Date: Tue, 27 Oct 2026 13:43:11 +0100 Subject: [PATCH 068/612] feat(libSceHttp2): bind cookie boxes to templates and requests sceHttp2SetCookieBox and sceHttp2GetCookieBox follow an emulator run of the firmware 13.42 module: templates and requests take a live box or 0, other ids and unknown boxes return the invalid-id error, a new request copies its template's box, and the getter checks a null output first. A request with a bound box sends the box's matching cookies and stores every Set-Cookie of the response in the box. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceHttp2/Cookies.cpp | 46 ++++++++++++++++----- core/libs/prx/libSceHttp2/Cookies.hpp | 6 +++ core/libs/prx/libSceHttp2/Export.cpp | 17 ++++++-- core/libs/prx/libSceHttp2/Transport.cpp | 7 ++++ core/libs/prx/libSceHttp2/Transport.hpp | 1 + core/libs/prx/libSceHttp2/include/Http2.hpp | 2 +- core/libs/tests/GuestHttp2.cpp | 32 +++++++++++++- core/libs/tests/GuestHttp2Transport.cpp | 26 +++++++++++- docs/dev/debt/libSceHttp2.md | 5 ++- 9 files changed, 122 insertions(+), 20 deletions(-) diff --git a/core/libs/prx/libSceHttp2/Cookies.cpp b/core/libs/prx/libSceHttp2/Cookies.cpp index 9e767f997a..74d23770d5 100644 --- a/core/libs/prx/libSceHttp2/Cookies.cpp +++ b/core/libs/prx/libSceHttp2/Cookies.cpp @@ -325,6 +325,40 @@ CookieBox* FindBox(int cookieBoxId) { return &g_cookieBoxes[cookieBoxId]; } +std::vector Matches(CookieBox& box, const Url& url) { + RemoveExpired(box, Now()); + std::vector matches; + for (const Cookie& stored : box.cookies) { + if (DomainMatches(url.host, stored.domain) && PathMatches(url.path, stored.path) && (!stored.secure || url.secure)) matches.push_back(&stored); + } + std::stable_sort(matches.begin(), matches.end(), [](const Cookie* left, const Cookie* right) { return left->path.size() > right->path.size(); }); + for (auto run = matches.begin(); run != matches.end();) { + const auto end = std::find_if(run, matches.end(), [&](const Cookie* match) { return match->path.size() != (*run)->path.size(); }); + std::rotate(run, run + 1, end); + run = end; + } + return matches; +} + +} + +std::optional Http2CookieHeader(int cookieBoxId, std::string_view url) { + std::lock_guard lock(g_cookiesMutex); + CookieBox* box = FindBox(cookieBoxId); + Url parsed{}; + if (!box || ParseUrl(url, parsed) != 0) return std::nullopt; + const auto matches = Matches(*box, parsed); + if (matches.empty()) return std::nullopt; + std::string header; + for (const Cookie* match : matches) AppendCookie(header, *match); + return header; +} + +void Http2StoreResponseCookie(int cookieBoxId, std::string_view url, std::string_view cookie) { + std::lock_guard lock(g_cookiesMutex); + CookieBox* box = FindBox(cookieBoxId); + Url parsed{}; + if (box && ParseUrl(url, parsed) == 0) static_cast(StoreCookie(*box, parsed, cookie, Now())); } void Http2ForgetCookies(int cookieBoxId) { @@ -358,19 +392,9 @@ int APS5_VABI sceHttp2GetCookie(int cookieBoxId, const char* url, char* cookie, Url parsed{}; if (const int result = ParseUrl(url, parsed); result == ERROR_REJECTED) return ERROR_NOT_FOUND; else if (result != 0) return result; - RemoveExpired(*box, Now()); - std::vector matches; - for (const Cookie& stored : box->cookies) { - if (DomainMatches(parsed.host, stored.domain) && PathMatches(parsed.path, stored.path) && (!stored.secure || parsed.secure)) matches.push_back(&stored); - } + const auto matches = Matches(*box, parsed); if (matches.empty()) return ERROR_NOT_FOUND; if (!cookie && !required) return ERROR_INVALID_VALUE; - std::stable_sort(matches.begin(), matches.end(), [](const Cookie* left, const Cookie* right) { return left->path.size() > right->path.size(); }); - for (auto run = matches.begin(); run != matches.end();) { - const auto end = std::find_if(run, matches.end(), [&](const Cookie* match) { return match->path.size() != (*run)->path.size(); }); - std::rotate(run, run + 1, end); - run = end; - } std::string header; for (const Cookie* match : matches) AppendCookie(header, *match); const std::size_t size = header.size() + HEADER_SLACK; diff --git a/core/libs/prx/libSceHttp2/Cookies.hpp b/core/libs/prx/libSceHttp2/Cookies.hpp index 44a8587c3c..2c68abab36 100644 --- a/core/libs/prx/libSceHttp2/Cookies.hpp +++ b/core/libs/prx/libSceHttp2/Cookies.hpp @@ -1,5 +1,11 @@ #pragma once +#include +#include +#include + bool Http2CookieBoxExists(int cookieBoxId); void Http2ForgetCookies(int cookieBoxId); void Http2FlushCookies(int cookieBoxId); +std::optional Http2CookieHeader(int cookieBoxId, std::string_view url); +void Http2StoreResponseCookie(int cookieBoxId, std::string_view url, std::string_view cookie); diff --git a/core/libs/prx/libSceHttp2/Export.cpp b/core/libs/prx/libSceHttp2/Export.cpp index fb618851d0..d409d581c7 100644 --- a/core/libs/prx/libSceHttp2/Export.cpp +++ b/core/libs/prx/libSceHttp2/Export.cpp @@ -373,7 +373,11 @@ int APS5_VABI sceHttp2DeleteCookieBox(int cookie_box_id) { } int APS5_VABI sceHttp2SetCookieBox(int id, int cookie_box_id) { - return Update(id, __func__, [&](auto&) { if (cookie_box_id != 0) NotImplemented_nid_no_patch("sceHttp2SetCookieBox"); }); + return Update(id, __func__, [&](auto& settings) { + if (cookie_box_id != 0 && !FindHandle(cookie_box_id, Http2Kind::CookieBox)) return ERROR_INVALID_ID; + settings.cookieBox = cookie_box_id; + return 0; + }, true); } int APS5_VABI sceHttp2SetRequestNoContentLength(int id) { @@ -444,9 +448,14 @@ int APS5_VABI sceHttp2GetAutoRedirect(int id, int* enable) { }); } -int APS5_VABI sceHttp2GetCookieBox(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceHttp2GetCookieBox(int id, int* cookieBoxId) { + if (!cookieBoxId) return ERROR_NULL_POINTER; + *cookieBoxId = 0; + return Read(id, [&](const auto& settings) { + if (settings.cookieBox == 0) return ERROR_NOT_FOUND; + *cookieBoxId = settings.cookieBox; + return 0; + }); } int APS5_VABI sceHttp2RedirectCacheFlush(int lib_http2_ctx_id) { diff --git a/core/libs/prx/libSceHttp2/Transport.cpp b/core/libs/prx/libSceHttp2/Transport.cpp index ab7389458f..481bcec7ce 100644 --- a/core/libs/prx/libSceHttp2/Transport.cpp +++ b/core/libs/prx/libSceHttp2/Transport.cpp @@ -8,6 +8,7 @@ #endif #include "Transport.hpp" +#include "Cookies.hpp" #include "prx/libSceNet/include/Net.hpp" #include "prx/libSceSsl/include/Ssl.hpp" #include "prx/libc/include/General.hpp" @@ -283,6 +284,8 @@ struct Request::State { ~State() { Close(); } + std::string Address() const { return "https://" + url.authority + url.path; } + void Close() { if (session) nghttp2_session_del(std::exchange(session, nullptr)); if (ssl >= 0) sceSslDeleteConnection(std::exchange(ssl, -1)); @@ -407,6 +410,7 @@ struct Request::State { state.contentLength = Number(text); state.hasLength = true; } + if (key == "set-cookie" && state.settings.cookieBox != 0) Http2StoreResponseCookie(state.settings.cookieBox, state.Address(), text); }); } @@ -482,6 +486,9 @@ struct Request::State { if (!settings.userAgent.empty() && std::none_of(settings.headers.begin(), settings.headers.end(), [](const auto& header) { return header.first == "user-agent"; })) fields.emplace_back("user-agent", settings.userAgent); fields.insert(fields.end(), settings.headers.begin(), settings.headers.end()); + if (settings.cookieBox != 0) { + if (auto cookie = Http2CookieHeader(settings.cookieBox, Address())) fields.emplace_back("cookie", std::move(*cookie)); + } if (!settings.noContentLength) fields.emplace_back("content-length", std::to_string(settings.contentLength)); std::vector headers; for (auto& [name, value] : fields) headers.push_back({reinterpret_cast(name.data()), diff --git a/core/libs/prx/libSceHttp2/Transport.hpp b/core/libs/prx/libSceHttp2/Transport.hpp index 116790ad2b..3c48f7e9ba 100644 --- a/core/libs/prx/libSceHttp2/Transport.hpp +++ b/core/libs/prx/libSceHttp2/Transport.hpp @@ -27,6 +27,7 @@ struct Settings { void* authInfoArgument = nullptr; void* preSendCallback = nullptr; void* preSendArgument = nullptr; + int cookieBox = 0; }; struct Capacity { diff --git a/core/libs/prx/libSceHttp2/include/Http2.hpp b/core/libs/prx/libSceHttp2/include/Http2.hpp index c9157d4713..79956557f9 100644 --- a/core/libs/prx/libSceHttp2/include/Http2.hpp +++ b/core/libs/prx/libSceHttp2/include/Http2.hpp @@ -80,7 +80,7 @@ int APS5_VABI sceHttp2CookieImport(void); int APS5_VABI sceHttp2GetAuthEnabled(int id, int* isEnable); int APS5_VABI sceHttp2GetAutoRedirect(int id, int* enable); int APS5_VABI sceHttp2GetCookie(int cookieBoxId, const char* url, char* cookie, std::size_t* required, std::size_t prepared); -int APS5_VABI sceHttp2GetCookieBox(void); +int APS5_VABI sceHttp2GetCookieBox(int id, int* cookieBoxId); int APS5_VABI sceHttp2GetCookieStats(int cookieBoxId, Http2CookieStats* stats); int APS5_VABI sceHttp2RedirectCacheFlush(int libHttp2CtxId); int APS5_VABI sceHttp2RemoveRequestHeader(int id, const char* name); diff --git a/core/libs/tests/GuestHttp2.cpp b/core/libs/tests/GuestHttp2.cpp index 4023dcaea1..32e92e707b 100644 --- a/core/libs/tests/GuestHttp2.cpp +++ b/core/libs/tests/GuestHttp2.cpp @@ -9,6 +9,8 @@ static void Require(bool value) { if (!value) std::abort(); } static constexpr int InvalidId = static_cast(0x817B1100); +static constexpr int NotFound = static_cast(0x817B1025); +static constexpr int NullPointer = static_cast(0x817B1225); template static bool Throws(TCall call) { @@ -28,7 +30,35 @@ int main() { const int request = sceHttp2CreateRequestWithURL(tmpl, "GET", "https://localhost/", 0); Require(box > 0 && context > 0 && tmpl > 0 && request > 0); Require(sceHttp2SetCookieBox(tmpl, 0) == 0); - Require(Throws([&] { sceHttp2SetCookieBox(tmpl, box); })); + int bound = -7; + Require(sceHttp2GetCookieBox(tmpl, &bound) == NotFound && bound == 0); + Require(sceHttp2GetCookieBox(tmpl, nullptr) == NullPointer && sceHttp2GetCookieBox(context, nullptr) == NullPointer); + Require(sceHttp2SetCookieBox(tmpl, box) == 0); + bound = -7; + Require(sceHttp2GetCookieBox(tmpl, &bound) == 0 && bound == box); + bound = -7; + Require(sceHttp2GetCookieBox(request, &bound) == NotFound && bound == 0); + const int copied = sceHttp2CreateRequestWithURL(tmpl, "GET", "https://localhost/", 0); + bound = -7; + Require(sceHttp2GetCookieBox(copied, &bound) == 0 && bound == box); + Require(sceHttp2SetCookieBox(tmpl, 0) == 0); + bound = -7; + Require(sceHttp2GetCookieBox(tmpl, &bound) == NotFound && bound == 0); + bound = -7; + Require(sceHttp2GetCookieBox(copied, &bound) == 0 && bound == box); + for (const int id : {context, 0, -1, 0x7fffffff}) { + Require(sceHttp2SetCookieBox(id, box) == InvalidId && sceHttp2SetCookieBox(id, 0) == InvalidId); + bound = -7; + Require(sceHttp2GetCookieBox(id, &bound) == InvalidId && bound == 0); + } + const int deletedBox = sceHttp2CreateCookieBox(0x10000); + Require(deletedBox > 0 && sceHttp2DeleteCookieBox(deletedBox) == 0); + for (const int value : {-1, 0x7fffffff, context, tmpl, deletedBox}) Require(sceHttp2SetCookieBox(request, value) == InvalidId); + const int heldBox = sceHttp2CreateCookieBox(0x10000); + Require(heldBox > 0 && sceHttp2SetCookieBox(copied, heldBox) == 0 && sceHttp2DeleteCookieBox(heldBox) == 0); + bound = -7; + Require(sceHttp2GetCookieBox(copied, &bound) == 0 && bound == heldBox); + Require(sceHttp2SetCookieBox(copied, 0) == 0 && sceHttp2DeleteRequest(copied) == 0); Require(sceHttp2CookieFlush(box) == 0); Require(sceHttp2SetResolveRetry(tmpl, 0) == 0); Require(Throws([&] { sceHttp2SetResolveRetry(tmpl, 3); })); diff --git a/core/libs/tests/GuestHttp2Transport.cpp b/core/libs/tests/GuestHttp2Transport.cpp index b785722d08..eb651d6e5f 100644 --- a/core/libs/tests/GuestHttp2Transport.cpp +++ b/core/libs/tests/GuestHttp2Transport.cpp @@ -48,7 +48,7 @@ bool Throws(TCall call) { return false; } -enum class Mode { Normal, Large, NoLength, Head, NotFound, Redirect, Auth, Reset, Truncate, StallHandshake, StallHeaders, StallBody, NoAlpn, Informational, StallUpload, EarlyFinal, EarlyReset, EarlyEmpty, EarlyLarge, EarlyResetIncomplete }; +enum class Mode { Normal, Large, NoLength, Head, NotFound, Redirect, Auth, Reset, Truncate, StallHandshake, StallHeaders, StallBody, NoAlpn, Informational, StallUpload, Cookie, EarlyFinal, EarlyReset, EarlyEmpty, EarlyLarge, EarlyResetIncomplete }; const char* Protocols[] = {"h2", nullptr}; class Server { @@ -197,6 +197,7 @@ private: std::vector> fields{{":status", status}, {"x-loopback", "yes"}}; if (mode != Mode::NoLength) fields.emplace_back("content-length", length); if (mode == Mode::Redirect) fields.emplace_back("location", "/another"); + if (mode == Mode::Cookie) fields.emplace_back("set-cookie", "session=fresh; Path=/"); std::vector response; for (auto& [name, value] : fields) response.push_back({reinterpret_cast(name.data()), reinterpret_cast(value.data()), name.size(), value.size(), 0}); nghttp2_data_provider2 provider{}; @@ -298,6 +299,28 @@ struct Client { ~Client() { if (context > 0) Require(sceHttp2Term(context) == 0, "client term"); } }; +void CookieBox(int ssl) { + std::fprintf(stderr, "HTTP2 cookie box scenario\n"); + Server server(Mode::Cookie); + Client client(ssl); + const std::string url = server.Url("127.0.0.1"); + const int box = sceHttp2CreateCookieBox(0x10000); + Require(box > 0 && sceHttp2AddCookie(box, url.c_str(), "stored=old", 10) == 0, "stored cookie"); + Require(sceHttp2SetCookieBox(client.tmpl, box) == 0, "bind cookie box"); + client.Create(url); + Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "send with a cookie box"); + std::array buffer; + while (sceHttp2ReadData(client.request, buffer.data(), buffer.size()) > 0) {} + server.Join(); + const auto& headers = server.Headers(); + Require(headers.count("cookie") == 1 && headers.find("cookie")->second == "stored=old", "box cookie sent"); + std::array cookie{}; + std::size_t required = 0; + Require(sceHttp2GetCookie(box, url.c_str(), cookie.data(), &required, cookie.size()) == 0, "response cookie lookup"); + Require(std::string_view(cookie.data()) == "session=fresh; stored=old", "response cookie stored in the box"); + Require(sceHttp2DeleteCookieBox(box) == 0, "delete cookie box"); +} + void Success(int ssl, Mode mode, bool disableCa = false) { std::fprintf(stderr, "HTTP2 success scenario %d\n", static_cast(mode)); Server server(mode); @@ -537,6 +560,7 @@ int main(int argc, char** argv) { Require(argc == 1, "test arguments"); for (const auto mode : {Mode::Normal, Mode::Large, Mode::NoLength, Mode::Head, Mode::NotFound, Mode::Redirect, Mode::Auth, Mode::Informational}) Success(ssl, mode); for (const auto mode : {Mode::NoAlpn, Mode::Reset, Mode::Truncate, Mode::Redirect, Mode::Auth}) Failure(ssl, mode); + CookieBox(ssl); Failure(ssl, Mode::Normal, true); for (const auto mode : {Mode::StallHandshake, Mode::StallHeaders, Mode::StallBody, Mode::StallUpload}) { Deadline(ssl, mode, false); diff --git a/docs/dev/debt/libSceHttp2.md b/docs/dev/debt/libSceHttp2.md index f89a6dac77..5337093bc7 100644 --- a/docs/dev/debt/libSceHttp2.md +++ b/docs/dev/debt/libSceHttp2.md @@ -7,11 +7,12 @@ ### Unknown function info - [sceHttp2DeleteCookieBox](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - imported by PPSA35943's Unity player. The `(int cookieBoxId)` signature follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http2.cs#L215-L217). A box `sceHttp2CreateCookieBox` returned is discarded; an unknown or already deleted id returns the library's invalid-id error (0x817B1100), as [prosper](https://github.com/mattias800/prosper/blob/b1bec57186a0/prosper/src/hle/net/hle_http2.cpp#L512-L523) does; not measured on a console -- [libSceHttp2 cookie boxes](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - `sceHttp2CreateCookieBox` takes a machine-sized pool size and requires at least 64 KiB. On 13.42 the pool is rounded up to 16 KiB (wrapping to 0 for `SIZE_MAX`) and mapped with `sceKernelMapNamedFlexibleMemory`, whose error is returned unchanged; the host maps it the same way under an assumed name, but does not reproduce native cookie-heap bookkeeping, identifier ranges or retained request references. Its delete signature and invalid-ID error are confirmed. `sceHttp2SetRequestNoContentLength` takes only the request id. `sceHttp2CookieFlush` empties a cookie box and answers 0 for any other id. Nonzero cookie-box binding by `sceHttp2SetCookieBox` throws +- [libSceHttp2 cookie boxes](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - `sceHttp2CreateCookieBox` takes a machine-sized pool size and requires at least 64 KiB. On 13.42 the pool is rounded up to 16 KiB (wrapping to 0 for `SIZE_MAX`) and mapped with `sceKernelMapNamedFlexibleMemory`, whose error is returned unchanged; the host maps it the same way under an assumed name, but does not reproduce native cookie-heap bookkeeping, identifier ranges or retained request references. Its delete signature and invalid-ID error are confirmed. `sceHttp2SetRequestNoContentLength` takes only the request id. `sceHttp2CookieFlush` empties a cookie box and answers 0 for any other id. +- [sceHttp2SetCookieBox and sceHttp2GetCookieBox](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - follow an emulator run of the 13.42 module: templates and requests accept a live box or 0 (unbind), other ids and unknown or deleted boxes return 0x817B1100, a new request copies its template's box, a deleted box stays reported by the requests holding it, and `sceHttp2GetCookieBox(id, int*)` returns 0x817B1225 for a null output before writing 0 and checking the id, then 0x817B1025 when no box is bound. Not measured (the run cannot reach the network): a request with a bound box sends the box's matching cookies as one extra `cookie` field next to any added by `sceHttp2AddRequestHeader`, and every `Set-Cookie` of the response is stored in the box with the rules of `sceHttp2AddCookie`, with rejected cookies dropped silently - [libSceHttp2 cookie store](../../../core/libs/prx/libSceHttp2/Cookies.cpp) (libSceHttp2) - behavior follows a run of `GuestHttp2Cookies.cpp`'s sequence against 13.42's `libSceHttp2.sprx` ([#10534](https://github.com/boykopovar/AnyPS5/pull/10534)). Not reproduced: the `0x817B1076` stack check below 0x1FD0 bytes of stack, and the pool size is not enforced. Assumed, not measured on libSceHttp2: the default total limit of 3000 with the 10 oldest dropped past it, and the NUL, space, NUL written after the header (both libSceHttp's), which of no match and null `cookie`/`required` is checked first, public-suffix rules for `Domain`, and dates beyond the tested ones. Equal-length paths come out with the first stored last, a rule inferred from the runs -- [libSceHttp2 pending exports](../../../core/libs/prx/libSceHttp2/Export.cpp) - 5 named callable exports were identified in the PS5 3.20 export table and cross-checked against firmware 13.60. Signatures and behavior remain unverified; declarations use the unknown-signature convention and throw through `NotImplemented_nid_no_patch`. Evidence: [3.20 export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceHttp2.c). +- [libSceHttp2 pending exports](../../../core/libs/prx/libSceHttp2/Export.cpp) - 4 named callable exports were identified in the PS5 3.20 export table and cross-checked against firmware 13.60. Signatures and behavior remain unverified; declarations use the unknown-signature convention and throw through `NotImplemented_nid_no_patch`. Evidence: [3.20 export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceHttp2.c). ### Functional - [libSceHttp2 transport](../../../core/libs/prx/libSceHttp2/Transport.cpp) - only synchronous HTTPS requests with version 3 and ALPN `h2` are supported. HTTP/1.x, cleartext HTTP/2, negotiation fallback, connection reuse, multiplexing across requests, CONNECT, response trailers, automatic redirects and authentication challenges throw. Version values, method strings, header overwrite/append modes, status/read/content-length signatures and timeout units follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http2.cs); wire framing and negotiation follow [RFC 9113 sections 3, 5, 6 and 8](https://www.rfc-editor.org/rfc/rfc9113.html). [prosper](https://github.com/mattias800/prosper/blob/b1bec57186a0/prosper/src/hle/net/hle_http2.cpp) instead records the method argument as an integer; the string signature follows SharpProspero. These are public compatibility contracts, not console measurements. From bbef95bb6827e55fd2abe6c9305bbf6da9f51c47 Mon Sep 17 00:00:00 2001 From: BunnyFl <166477956+BunnyFl@users.noreply.github.com> Date: Tue, 27 Oct 2026 15:59:12 +0100 Subject: [PATCH 069/612] fix(libSceHttp2): keep cookie box lookups off the handles mutex The transport read and stored a bound box's cookies under the request's state mutex, and the box lookup took the handles mutex, while deleting a request holds the handles mutex and waits for that state mutex. The cookie module now records live boxes itself, so the lookup no longer needs the handles mutex. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceHttp2/Cookies.cpp | 9 +++++-- core/libs/prx/libSceHttp2/Cookies.hpp | 2 +- core/libs/prx/libSceHttp2/Export.cpp | 15 ++++++------ core/libs/tests/GuestHttp2Transport.cpp | 32 ++++++++++++++++++++++++- 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceHttp2/Cookies.cpp b/core/libs/prx/libSceHttp2/Cookies.cpp index 74d23770d5..1b8a4443b6 100644 --- a/core/libs/prx/libSceHttp2/Cookies.cpp +++ b/core/libs/prx/libSceHttp2/Cookies.cpp @@ -321,8 +321,8 @@ void AppendCookie(std::string& header, const Cookie& cookie) { } CookieBox* FindBox(int cookieBoxId) { - if (!Http2CookieBoxExists(cookieBoxId)) return nullptr; - return &g_cookieBoxes[cookieBoxId]; + const auto box = g_cookieBoxes.find(cookieBoxId); + return box != g_cookieBoxes.end() ? &box->second : nullptr; } std::vector Matches(CookieBox& box, const Url& url) { @@ -361,6 +361,11 @@ void Http2StoreResponseCookie(int cookieBoxId, std::string_view url, std::string if (box && ParseUrl(url, parsed) == 0) static_cast(StoreCookie(*box, parsed, cookie, Now())); } +void Http2AddCookieBox(int cookieBoxId) { + std::lock_guard lock(g_cookiesMutex); + g_cookieBoxes.try_emplace(cookieBoxId); +} + void Http2ForgetCookies(int cookieBoxId) { std::lock_guard lock(g_cookiesMutex); g_cookieBoxes.erase(cookieBoxId); diff --git a/core/libs/prx/libSceHttp2/Cookies.hpp b/core/libs/prx/libSceHttp2/Cookies.hpp index 2c68abab36..4983d98f01 100644 --- a/core/libs/prx/libSceHttp2/Cookies.hpp +++ b/core/libs/prx/libSceHttp2/Cookies.hpp @@ -4,7 +4,7 @@ #include #include -bool Http2CookieBoxExists(int cookieBoxId); +void Http2AddCookieBox(int cookieBoxId); void Http2ForgetCookies(int cookieBoxId); void Http2FlushCookies(int cookieBoxId); std::optional Http2CookieHeader(int cookieBoxId, std::string_view url); diff --git a/core/libs/prx/libSceHttp2/Export.cpp b/core/libs/prx/libSceHttp2/Export.cpp index d409d581c7..837af5b7fe 100644 --- a/core/libs/prx/libSceHttp2/Export.cpp +++ b/core/libs/prx/libSceHttp2/Export.cpp @@ -66,11 +66,6 @@ static void ReleaseOwnedHandles(int owner) { } } -bool Http2CookieBoxExists(int cookieBoxId) { - std::lock_guard lock(g_handlesMutex); - return FindHandle(cookieBoxId, Http2Kind::CookieBox) != nullptr; -} - static bool HasSettings(const Http2Handle& handle) { return handle.kind == Http2Kind::Template || handle.kind == Http2Kind::Request; } @@ -356,9 +351,15 @@ int APS5_VABI sceHttp2CreateCookieBox(std::size_t poolSize) { void* address = nullptr; if (const int result = sceKernelMapNamedFlexibleMemory(&address, mappedSize, 3, 0, "SceHttp2CookieBox"); result != 0) return result; std::shared_ptr memory(address, [mappedSize](void* mapped) { sceKernelMunmap(reinterpret_cast(mapped), mappedSize); }); - std::lock_guard lock(g_handlesMutex); const int id = g_nextHandle.fetch_add(1, std::memory_order_relaxed); - g_handles.emplace(id, Http2Handle{Http2Kind::CookieBox, 0, poolSize, std::move(memory)}); + Http2AddCookieBox(id); + try { + std::lock_guard lock(g_handlesMutex); + g_handles.emplace(id, Http2Handle{Http2Kind::CookieBox, 0, poolSize, std::move(memory)}); + } catch (...) { + Http2ForgetCookies(id); + throw; + } return id; } diff --git a/core/libs/tests/GuestHttp2Transport.cpp b/core/libs/tests/GuestHttp2Transport.cpp index eb651d6e5f..c8e45868e2 100644 --- a/core/libs/tests/GuestHttp2Transport.cpp +++ b/core/libs/tests/GuestHttp2Transport.cpp @@ -48,7 +48,7 @@ bool Throws(TCall call) { return false; } -enum class Mode { Normal, Large, NoLength, Head, NotFound, Redirect, Auth, Reset, Truncate, StallHandshake, StallHeaders, StallBody, NoAlpn, Informational, StallUpload, Cookie, EarlyFinal, EarlyReset, EarlyEmpty, EarlyLarge, EarlyResetIncomplete }; +enum class Mode { Normal, Large, NoLength, Head, NotFound, Redirect, Auth, Reset, Truncate, StallHandshake, StallHeaders, StallBody, NoAlpn, Informational, StallUpload, Cookie, CookieFlood, EarlyFinal, EarlyReset, EarlyEmpty, EarlyLarge, EarlyResetIncomplete }; const char* Protocols[] = {"h2", nullptr}; class Server { @@ -198,6 +198,9 @@ private: if (mode != Mode::NoLength) fields.emplace_back("content-length", length); if (mode == Mode::Redirect) fields.emplace_back("location", "/another"); if (mode == Mode::Cookie) fields.emplace_back("set-cookie", "session=fresh; Path=/"); + if (mode == Mode::CookieFlood) { + for (int i = 0; i < 1500; ++i) fields.emplace_back("set-cookie", "f" + std::to_string(i) + "=v; Path=/"); + } std::vector response; for (auto& [name, value] : fields) response.push_back({reinterpret_cast(name.data()), reinterpret_cast(value.data()), name.size(), value.size(), 0}); nghttp2_data_provider2 provider{}; @@ -321,6 +324,32 @@ void CookieBox(int ssl) { Require(sceHttp2DeleteCookieBox(box) == 0, "delete cookie box"); } +void CookieBoxDelete(int ssl) { + std::fprintf(stderr, "HTTP2 cookie box delete scenario\n"); + Server server(Mode::CookieFlood); + Client client(ssl); + const std::string url = server.Url("127.0.0.1"); + const int box = sceHttp2CreateCookieBox(0x10000); + Require(box > 0 && sceHttp2SetCookieBox(client.tmpl, box) == 0, "bind cookie box"); + Require(sceHttp2SetCookieMaxNumPerDomain(box, 2000) == 0, "room for every response cookie"); + client.Create(url); + auto pending = std::async(std::launch::async, [&] { + try { static_cast(sceHttp2SendRequest(client.request, nullptr, 0)); } catch (const std::exception&) {} + }); + std::size_t required = 0; + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (sceHttp2GetCookie(box, url.c_str(), nullptr, &required, 0) != 0) { + Require(std::chrono::steady_clock::now() < deadline, "first response cookie stored"); + std::this_thread::yield(); + } + auto deleting = std::async(std::launch::async, [&] { return sceHttp2DeleteRequest(client.request); }); + Require(deleting.wait_for(std::chrono::seconds(2)) == std::future_status::ready, "delete returns while the transport stores response cookies"); + Require(deleting.get() == 0, "delete a request whose transport stores response cookies"); + Require(pending.wait_for(std::chrono::seconds(2)) == std::future_status::ready, "send ends after the delete"); + server.Join(); + Require(sceHttp2DeleteCookieBox(box) == 0, "delete cookie box"); +} + void Success(int ssl, Mode mode, bool disableCa = false) { std::fprintf(stderr, "HTTP2 success scenario %d\n", static_cast(mode)); Server server(mode); @@ -561,6 +590,7 @@ int main(int argc, char** argv) { for (const auto mode : {Mode::Normal, Mode::Large, Mode::NoLength, Mode::Head, Mode::NotFound, Mode::Redirect, Mode::Auth, Mode::Informational}) Success(ssl, mode); for (const auto mode : {Mode::NoAlpn, Mode::Reset, Mode::Truncate, Mode::Redirect, Mode::Auth}) Failure(ssl, mode); CookieBox(ssl); + CookieBoxDelete(ssl); Failure(ssl, Mode::Normal, true); for (const auto mode : {Mode::StallHandshake, Mode::StallHeaders, Mode::StallBody, Mode::StallUpload}) { Deadline(ssl, mode, false); From 5584210c8f902653e869ede9bbdb781ece423660 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 27 Oct 2026 17:30:50 +0000 Subject: [PATCH 070/612] fix(agc): replay review fixes: inherited pipeline cache, shell quoting, reservation errors --cold-pipelines now clears an inherited APS5_HEADLESS_PIPELINE_CACHE on both platforms, the replay test builds its command with #11157's quoting so the redirect and --repeat work under sh and cmd, and a reservation that fails in a repeated pass is reported and fails the replay instead of being dropped. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 22 ++++- .../Execution/include/Driver/Driver.hpp | 2 +- .../Execution/include/VideoOutput.hpp | 5 + .../Execution/src/Driver/Presentation.cpp | 11 ++- .../prx/libSceAgcDriver/tests/FrameReplay.cpp | 72 ++++++++++++-- .../tests/FrameReplayState.cpp | 93 ++++++++++++++++++- .../prx/libSceAgcDriver/tools/FrameReplay.cpp | 7 +- docs/dev/ENVIRONMENT.md | 2 +- 8 files changed, 192 insertions(+), 22 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 401e0a2b9d..a1715c5d1d 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -3594,11 +3594,11 @@ if(BUILD_TESTING) endif() add_test_executable(agc_driver_frame_replay_state_tests tests/FrameReplayState.cpp Execution/src/FrameCapture.cpp) - target_include_directories(agc_driver_frame_replay_state_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_include_directories(agc_driver_frame_replay_state_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) target_link_libraries(agc_driver_frame_replay_state_tests PRIVATE libSceAgcDriver libkernel libc libzstd_static) set_target_properties(agc_driver_frame_replay_state_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_driver_frame_replay_state_tests) - set(agcFrameReplayModes order tokens blocking) + set(agcFrameReplayModes order tokens blocking reservations reservation_failure) if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux") list(APPEND agcFrameReplayModes zero shader shader_host) endif() @@ -3635,12 +3635,28 @@ if(BUILD_TESTING) add_dependencies(agc_driver_frame_replay_tests agc_frame_replay) add_test(NAME agc_driver_frame_replay COMMAND agc_driver_frame_replay_tests $) set_tests_properties(agc_driver_frame_replay PROPERTIES - ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2" + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame replay capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2" TIMEOUT 120) if(WIN32) set_tests_properties(agc_driver_frame_replay PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_driver_frame_replay_cold_pipelines COMMAND agc_driver_frame_replay_tests $ pipelines) + set_tests_properties(agc_driver_frame_replay_cold_pipelines PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=0;APS5_HEADLESS_PIPELINE_CACHE=1;ANYPS5_SHADER_CACHE_DIR=${CMAKE_CURRENT_BINARY_DIR}/frame replay pipeline cache;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame replay cold capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2" + TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_driver_frame_replay_cold_pipelines PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_driver_frame_replay_reservation_error COMMAND agc_driver_frame_replay_tests $ reservation_error) + set_tests_properties(agc_driver_frame_replay_reservation_error PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame replay reservation error" + TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_driver_frame_replay_reservation_error PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_driver_frame_replay_concurrent COMMAND agc_driver_frame_replay_tests $ concurrent) set_tests_properties(agc_driver_frame_replay_concurrent PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-concurrent-capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=8" diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index 602d3022ef..978b67587a 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -442,7 +442,7 @@ private: std::map queues; std::map> outputs; struct EopFlip { - std::uint32_t handle; + FlipInfo info; std::shared_ptr request; }; std::map eopFlips; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp index c642371e8e..95696a705f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp @@ -32,6 +32,11 @@ struct FlipInfo { std::int64_t argument; }; +class EopFlipAlreadyReserved final : public std::runtime_error { +public: + EopFlipAlreadyReserved() : std::runtime_error("EOP flip token is already reserved for this flip") {} +}; + class IFlipRequest { public: virtual ~IFlipRequest() = default; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp index d607e1ae53..efbef80f39 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp @@ -24,12 +24,17 @@ std::uint32_t Driver::ReserveEopFlipAt(const FlipInfo& info, std::uint32_t token std::uint32_t Driver::reserveEopFlip(const FlipInfo& info, std::uint32_t token) { rethrowFailure(); checkStopping(); - require(token >= EopFlipTokenBase && token <= EopFlipTokenLimit && !eopFlips.contains(token), "EOP flip token is not free"); + require(token >= EopFlipTokenBase && token <= EopFlipTokenLimit, "EOP flip token is out of range"); const auto output = outputs.find(info.handle); require(output != outputs.end(), "EOP flip references an unregistered video output"); + if (const auto found = eopFlips.find(token); found != eopFlips.end()) { + const auto& reserved = found->second.info; + require(reserved.handle == info.handle && reserved.index == info.index && reserved.mode == info.mode && reserved.argument == info.argument, "EOP flip token is reserved for a different flip"); + throw EopFlipAlreadyReserved{}; + } auto request = output->second->TryReserve(info); if (!request) return 0; - eopFlips.emplace(token, EopFlip{info.handle, std::move(request)}); + eopFlips.emplace(token, EopFlip{info, std::move(request)}); nextEopToken = std::max(nextEopToken, token + 1); return token; } @@ -53,7 +58,7 @@ void Driver::UnregisterVideoOutput(std::uint32_t handle, const std::shared_ptrsecond == output, "video output registration mismatch"); - std::erase_if(eopFlips, [handle](const auto& entry) { return entry.second.handle == handle; }); + std::erase_if(eopFlips, [handle](const auto& entry) { return entry.second.info.handle == handle; }); outputs.erase(it); } diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp index d23bdebfb7..58f420fb60 100644 --- a/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp @@ -134,7 +134,7 @@ bool equal(const std::byte* bytes, const std::vector& expected) { return std::memcmp(bytes, expected.data(), expected.size()) == 0; } -void testCaptureAndReplay(const std::filesystem::path& tool) { +void testCaptureAndReplay(const std::filesystem::path& tool, bool pipelines) { const char* directory = std::getenv("APS5_FRAME_CAPTURE"); check(directory != nullptr && *directory != '\0', "APS5_FRAME_CAPTURE is not set"); const std::filesystem::path capture(directory); @@ -186,13 +186,13 @@ void testCaptureAndReplay(const std::filesystem::path& tool) { check(recorded(late.base), "the block committed during the capture is not in its ranges"); check(reader.Header().find("\"late_ranges\":1") != std::string::npos, "the capture header does not count one range committed during the capture: " + reader.Header()); - const auto firstDump = capture / "first.bin"; - const auto secondDump = capture / "second.bin"; + const auto firstDump = capture / "first dump.bin"; + const auto secondDump = capture / "second dump.bin"; const auto replay = [&](const std::string& options, const std::filesystem::path& output) { std::filesystem::remove(firstDump); std::filesystem::remove(secondDump); auto command = "\"" + tool.string() + "\" \"" + capture.string() + "\" --strict " + options + " --dump " + std::to_string(at(layout.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + - firstDump.string() + "\" --dump " + std::to_string(at(late.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + secondDump.string() + "\" > \"" + output.string() + "\""; + firstDump.string() + "\" --dump " + std::to_string(at(late.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + secondDump.string() + "\" > \"" + output.string() + "\" 2> \"" + output.string() + ".log\""; #ifdef _WIN32 command = "\"" + command + "\""; #endif @@ -203,8 +203,20 @@ void testCaptureAndReplay(const std::filesystem::path& tool) { const auto json = readFile(output); return std::string(reinterpret_cast(json.data()), json.size()); }; - replay("", capture / "replay.json"); - const auto repeated = replay("--repeat 3", capture / "repeat.json"); + replay("", capture / "replay output.json"); + if (pipelines) { + check(std::getenv("APS5_HEADLESS_PIPELINE_CACHE") != nullptr, "the pipeline cache switch was not inherited"); + const auto warm = capture / "warm output.json"; + replay("", warm); + const auto warmLog = readFile(warm.string() + ".log"); + check(std::string(reinterpret_cast(warmLog.data()), warmLog.size()).find("[pipeline-cache] loaded ") != std::string::npos, "the warm replay did not load the seeded pipeline cache"); + const auto cold = capture / "cold output.json"; + replay("--cold-pipelines", cold); + const auto coldLog = readFile(cold.string() + ".log"); + check(std::string(reinterpret_cast(coldLog.data()), coldLog.size()).find("[pipeline-cache] loaded ") == std::string::npos, "--cold-pipelines loaded a cache with APS5_HEADLESS_PIPELINE_CACHE inherited"); + return; + } + const auto repeated = replay("--repeat 3", capture / "repeat output.json"); std::size_t passes = 0; for (auto found = repeated.find("\"frames\":2,\"p50\""); found != std::string::npos; found = repeated.find("\"frames\":2,\"p50\"", found + 1)) ++passes; check(passes == 3, "the repeated replay did not replay both frames in each of three passes: " + repeated); @@ -272,16 +284,56 @@ void testConcurrentCopies(const std::filesystem::path& tool) { } } +void testReservationError(const std::filesystem::path& tool) { + const char* directory = std::getenv("APS5_FRAME_CAPTURE"); + check(directory != nullptr && *directory != '\0', "APS5_FRAME_CAPTURE is not set"); + const std::filesystem::path capture(directory); + std::uintptr_t base = 0; + std::size_t size = 0; + GuestArena::GuestArenaRange_nid_postfix(&base, &size); + AgcDriver::FrameCaptureWriter writer(capture); + AgcDriver::FrameCall output; + output.kind = FrameCallKind::RegisterVideoOutput; + output.values = {Handle}; + writer.WriteCall(output); + AgcDriver::FrameCall reservation; + reservation.kind = FrameCallKind::ReserveEopFlip; + reservation.values = {Handle, static_cast(-1), 1, 0, AgcDriver::EopFlipTokenBase}; + writer.WriteCall(reservation); + AgcDriver::FrameCall start; + start.kind = FrameCallKind::FrameStart; + writer.WriteCall(start); + output.kind = FrameCallKind::UnregisterVideoOutput; + writer.WriteCall(output); + output.kind = FrameCallKind::RegisterVideoOutput; + writer.WriteCall(output); + reservation.values[3] = 1; + writer.WriteCall(reservation); + writer.Finish("{\"arena_base\":" + std::to_string(base) + "}"); + const auto result = capture / "reservation error.json"; + const auto log = capture / "reservation error.log"; + auto command = "\"" + tool.string() + "\" \"" + capture.string() + "\" --repeat 2 --strict > \"" + result.string() + "\" 2> \"" + log.string() + "\""; +#ifdef _WIN32 + command = "\"" + command + "\""; +#endif + check(std::system(command.c_str()) != 0, "the replay accepted an incompatible reservation on the second pass"); + const auto errors = readFile(log); + check(std::string(reinterpret_cast(errors.data()), errors.size()).find("EOP flip token is reserved for a different flip") != std::string::npos, "the repeated replay did not report the incompatible reservation"); + const auto report = readFile(result); + check(std::string(reinterpret_cast(report.data()), report.size()).find("\"p50\"") == std::string::npos, "a failed repeated reservation produced a successful replay report"); +} + } int main(int argc, char** argv) { - if (argc != 2 && !(argc == 3 && std::string(argv[2]) == "concurrent")) { - std::fprintf(stderr, "usage: agc_driver_frame_replay_tests [concurrent]\n"); + if (argc != 2 && !(argc == 3 && (std::string(argv[2]) == "concurrent" || std::string(argv[2]) == "pipelines" || std::string(argv[2]) == "reservation_error"))) { + std::fprintf(stderr, "usage: agc_driver_frame_replay_tests [concurrent|pipelines|reservation_error]\n"); return 2; } try { - if (argc == 3) testConcurrentCopies(argv[1]); - else testCaptureAndReplay(argv[1]); + if (argc == 3 && std::string(argv[2]) == "concurrent") testConcurrentCopies(argv[1]); + else if (argc == 3 && std::string(argv[2]) == "reservation_error") testReservationError(argv[1]); + else testCaptureAndReplay(argv[1], argc == 3); } catch (const std::exception& error) { std::fprintf(stderr, "FAILED: %s\n", error.what()); return 1; diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp index 29c816ebcd..d210fcfad5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp @@ -2,8 +2,10 @@ #include "../tools/FrameReplay.cpp" #undef main #include "prx/libSceAgcDriver/Execution/include/FrameRecorder.hpp" +#include "prx/libSceAgcDriver/Execution/include/Presentation.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" #include +#include namespace { @@ -182,6 +184,94 @@ void testEopTokens() { AgcDriverUnregisterVideoOutput_nid_postfix(7, output); } +class ReservationOutput final : public AgcDriver::IVideoOutput { +public: + explicit ReservationOutput(Frames& frames) : frames(frames) {} + std::shared_ptr Reserve(const AgcDriver::FlipInfo&) override { + ++reservations; + if (failure) std::rethrow_exception(failure); + return available ? std::make_shared(frames) : nullptr; + } + void Fail(std::exception_ptr error) noexcept override { frames.Failed(error); } + std::exception_ptr failure; + bool available = true; + std::size_t reservations = 0; + +private: + Frames& frames; +}; + +FrameCall reservationCall(std::uint32_t token) { + FrameCall call; + call.kind = FrameCallKind::ReserveEopFlip; + call.values = {7, static_cast(-1), 1, 99, token}; + return call; +} + +template +void expectReservationFailure(Replay& replay, const FrameCall& call) { + const auto kept = replay.keptReservations.load(); + bool failed = false; + try { replay.runWindow(std::span(&call, 1)); } + catch (const TException&) { failed = true; } + check(failed, "the replay accepted a failed EOP reservation"); + check(replay.keptReservations.load() == kept, "a failed EOP reservation was counted as kept"); +} + +void testRepeatedReservations(const std::filesystem::path& directory, bool driverFailure) { + FrameCaptureWriter writer(directory); + writer.Finish("{}"); + const FrameCaptureReader reader(directory); + Options options; + options.pace = false; + options.release = false; + Replay replay{options, reader}; + auto output = std::make_shared(replay.frames); + AgcDriverRegisterVideoOutput_nid_postfix(7, output); + AgcDriverRegisterVideoOutput_nid_postfix(8, output); + const auto call = reservationCall(AgcDriver::EopFlipTokenBase); + replay.execute(call); + expectReservationFailure(replay, call); + replay.pass = 1; + if (driverFailure) { + AgcDriverReportFailure_nid_postfix(std::make_exception_ptr(std::runtime_error("reservation driver failure"))); + expectReservationFailure(replay, call); + bool failed = false; + try { AgcDriverShutdown_nid_postfix(); } + catch (const std::runtime_error& error) { failed = std::string_view(error.what()) == "reservation driver failure"; } + check(failed, "the injected driver failure was not propagated at shutdown"); + return; + } + replay.runWindow(std::span(&call, 1)); + check(replay.keptReservations.load() == 1 && output->reservations == 1, "a compatible pending reservation was not kept without allocating again"); + for (std::size_t field = 0; field < 4; ++field) { + auto different = call; + ++different.values[field]; + expectReservationFailure(replay, different); + } + auto missing = call; + missing.values[0] = 9; + expectReservationFailure(replay, missing); + expectReservationFailure(replay, reservationCall(AgcDriver::EopFlipTokenBase - 1)); + expectReservationFailure(replay, reservationCall(AgcDriver::EopFlipTokenLimit + 1)); + const auto fresh = reservationCall(AgcDriver::EopFlipTokenBase + 1); + output->failure = std::make_exception_ptr(std::bad_alloc{}); + expectReservationFailure(replay, fresh); + output->failure = std::make_exception_ptr(std::runtime_error("reservation output failure")); + expectReservationFailure(replay, fresh); + output->failure = nullptr; + output->available = false; + expectReservationFailure(replay, fresh); + output->available = true; + AgcDriverUnregisterVideoOutput_nid_postfix(7, output); + auto replacement = std::make_shared(replay.frames); + AgcDriverRegisterVideoOutput_nid_postfix(7, replacement); + replay.runWindow(std::span(&call, 1)); + check(replay.keptReservations.load() == 1 && replacement->reservations == 1, "a closed output's token was kept after reopening"); + AgcDriverUnregisterVideoOutput_nid_postfix(7, replacement); + AgcDriverUnregisterVideoOutput_nid_postfix(8, output); +} + class WaitingOutput final : public AgcDriver::IVideoOutput { public: WaitingOutput(Frames& frames, const std::uint32_t* label) : frames(frames), label(label) {} @@ -302,10 +392,11 @@ int main(int argc, char** argv) { else if (mode == "host_neighbors") testHostNeighbors(directory); else if (mode == "unwatched") testUnwatched(); else if (mode == "tokens") testEopTokens(); + else if (mode == "reservations" || mode == "reservation_failure") testRepeatedReservations(directory, mode == "reservation_failure"); else if (mode == "blocking") testBlockingSubmission(directory); else if (mode == "shader" || mode == "shader_host") testShaderCode(directory, mode == "shader_host"); else throw std::runtime_error("unknown test"); - AgcDriverShutdown_nid_postfix(); + if (mode != "reservation_failure") AgcDriverShutdown_nid_postfix(); std::puts("frame replay state test passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp index 2235f258d6..1e40481245 100644 --- a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp @@ -454,7 +454,7 @@ struct Replay { std::uint32_t reserved = 0; try { reserved = AgcDriverReserveEopFlipAt_nid_postfix(flipInfo(call), token); - } catch (const std::exception&) { + } catch (const AgcDriver::EopFlipAlreadyReserved&) { if (pass == 0) throw; ++keptReservations; return; @@ -899,10 +899,11 @@ int main(int argc, char** argv) { if (options.directory.empty()) return usage(); #ifdef _WIN32 _putenv_s("APS5_FRAME_CAPTURE", ""); - if (!options.coldPipelines) _putenv_s("APS5_HEADLESS_PIPELINE_CACHE", "1"); + _putenv_s("APS5_HEADLESS_PIPELINE_CACHE", options.coldPipelines ? "" : "1"); #else unsetenv("APS5_FRAME_CAPTURE"); - if (!options.coldPipelines) setenv("APS5_HEADLESS_PIPELINE_CACHE", "1", 1); + if (options.coldPipelines) unsetenv("APS5_HEADLESS_PIPELINE_CACHE"); + else setenv("APS5_HEADLESS_PIPELINE_CACHE", "1", 1); #endif int code = 2; try { diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index 79b0ee715a..36683d55af 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -172,7 +172,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_GPU_WAIT_TIMEOUT_MS` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp` | 1000 | milliseconds a GPU wait may run before a warning is printed | | `APS5_GROUP_CAPTURE_ALL_QUEUES` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp` | | resolves group-ahead capture on queue 0 as well | | `APS5_HANDOFF_SPIN_US` | integer | `core/libs/prx/libSceAgcDriver/Execution/include/HandoffSpin.hpp` | | microseconds a thread spins for a handed-off draw's work or for the GPU mutex before blocking, clamped to 0..10000; unset spins 150 us when the process has more than 8 usable processors and not at all otherwise | -| `APS5_HEADLESS_PIPELINE_CACHE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | loads the pipeline cache file on a device without a window too; `agc_frame_replay` sets it unless `--cold-pipelines` is given | +| `APS5_HEADLESS_PIPELINE_CACHE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | loads the pipeline cache file on a device without a window too; `agc_frame_replay` sets it by default and clears it with `--cold-pipelines`, including inherited values | | `APS5_HEAP_MIRROR_MIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | 24576 | budget in MiB for the heap mirrors held for registered allocations | | `APS5_HOOK_FLUSH_CPU_BLOCKS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | set to 0 flushes pending images whose every 64 KiB block the CPU wrote, instead of skipping them | | `APS5_HOOK_FULL_SYNC` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | makes the flush hook's recorded-store wait a full Sync under the GpuMutex | From 0c8232d533984214645be8420ab21a5545ef9c1c Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 21 Oct 2026 17:54:04 +0100 Subject: [PATCH 071/612] feat(agc): trace the serials the driver accepts and completes under APS5_TRACE_GPU With APS5_TRACE_GPU the driver now prints the serial it gives each submission, CPU flip and suspend point when it accepts it, with the queue, size and command address, and each completion as it is marked, including queue 0 completions deferred to the draw pipeline. A replay that stops completing can then be followed from the call that made a submission to the serial that never completed. Co-Authored-By: Claude Opus 5.5 --- .../Execution/src/Driver/Queues/QueueWorker.cpp | 4 ++++ .../Execution/src/Driver/Queues/Submission.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp index b6a5b5689c..24717b22bf 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp @@ -50,6 +50,8 @@ FrameTiming* Driver::includeTimingSubmission(const Submission& submission, bool } void Driver::markCompleted(std::uint64_t serial) { + static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr; + if (traceGpu) std::fprintf(stderr, "[gpu] %.1f completed serial=%llu (in order up to %llu)\n", TraceMs(), static_cast(serial), static_cast(completed)); completedOutOfOrder.insert(serial); while (!completedOutOfOrder.empty() && *completedOutOfOrder.begin() == completed + 1) { completed = *completedOutOfOrder.begin(); @@ -70,6 +72,8 @@ void Driver::deferCompletion(const Submission& submission) { queue0Uncommitted.push_back(received); } completionDeferred() = true; + static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr; + if (traceGpu) std::fprintf(stderr, "[gpu] %.1f deferred completion serial=%llu to the draw pipeline\n", TraceMs(), static_cast(submission.serial)); DrawPipeline::Queue0().Enqueue([this, serial = submission.serial, received, labelWrites = submission.labelWrites, progress = submission.progress] { completeSubmission(serial, received, labelWrites, progress); }, {}); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp index eeaea3e126..1d6388fe3c 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp @@ -445,6 +445,8 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue, bool cpuFlip) { } submission.shaders = shaders; submission.serial = accepted + 1; + if (trace) std::fprintf(stderr, "[gpu] %.1f accepted serial=%llu queue=0x%x dwords=%zu at %p%s\n", TraceMs(), static_cast(submission.serial), queue, submission.commands.size(), + static_cast(descriptor.addr), cpuFlip ? " (cpu flip)" : ""); submission.received = ++eventSerial; const auto now = std::chrono::steady_clock::now(); @@ -501,6 +503,8 @@ void Driver::SuspendPoint() { Submission boundary{}; boundary.serial = accepted + 1; boundary.suspend = true; + static const bool trace = std::getenv("APS5_TRACE_GPU") != nullptr; + if (trace) std::fprintf(stderr, "[gpu] %.1f accepted serial=%llu suspend point\n", TraceMs(), static_cast(boundary.serial)); boundary.queue = 0; boundary.receivedAt = receivedAt; From c793788954d38ec2c70ec8a09820ca16e077c019 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 21 Oct 2026 17:55:32 +0100 Subject: [PATCH 072/612] feat(agc): list a capture's shaders and submissions and trace a replay's serials Finding why a replay of a title stops, or which shaders a frame uses, meant running the whole capture. agc_frame_replay gains three modes: - --shaders replays nothing and maps no guest memory: it reads the snapshot, applies the captured writes in call order and prints one JSON line per shader registration with its code address, size, type, code hash and how many earlier registrations had the same code; --shader-code also writes each distinct code to a file - --submits lists the first n submissions and flips of the captured frames with their queue and serials, the packets of each and the fields of its waits and releases - --trace-serials prints each submission, flip and suspend point with its call ordinal and command address as it is issued, to match the serials APS5_TRACE_GPU prints agc_driver_frame_replay_shader and agc_driver_frame_replay_shader_host check that the listing reads the code the capture holds at the registration. Co-Authored-By: Claude Opus 5.5 --- .../tests/FrameReplayState.cpp | 15 + .../prx/libSceAgcDriver/tools/FrameReplay.cpp | 277 +++++++++++++++++- docs/dev/BUILD.md | 2 +- 3 files changed, 287 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp index d210fcfad5..e94b91c580 100644 --- a/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp @@ -377,6 +377,21 @@ void testShaderCode(const std::filesystem::path& directory, bool host) { } } check(registered && captured == expected, "the complete separate shader code was not captured before registration"); + CapturedMemory memory(reader); + std::map> groups; + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Delta) groups[chunk.group].push_back(&chunk); + } + std::optional> listed; + for (const auto& call : reader.Calls()) { + if (call.kind == FrameCallKind::RegisterShader) { + listed = memory.Read(address(code), CodeBytes); + break; + } + if (call.kind != FrameCallKind::Memory) continue; + if (const auto found = groups.find(call.values.at(0)); found != groups.end()) memory.Apply(found->second); + } + check(listed && *listed == expected, "the offline shader listing read other code than the capture holds at the registration"); } } diff --git a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp index 1e40481245..04e9ca9c92 100644 --- a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp @@ -73,6 +73,10 @@ struct Options { bool release = true; bool synthesize = true; std::size_t repeat = 1; + bool shaders = false; + std::filesystem::path shaderCode; + std::size_t submits = 0; + bool traceSerials = false; bool coldPipelines = false; }; @@ -441,13 +445,15 @@ struct Replay { return; case FrameCallKind::Submit: { const Packet packet{reinterpret_cast(static_cast(call.address)), static_cast(call.size), static_cast(call.flags), {}}; - if (call.queue == 0) sceAgcDriverSubmitDcb(&packet); - else sceAgcDriverSubmitAcb(call.queue, &packet); + traced(call, [&] { + if (call.queue == 0) sceAgcDriverSubmitDcb(&packet); + else sceAgcDriverSubmitAcb(call.queue, &packet); + }); if (++submits % 25 == 0) report(); return; } case FrameCallKind::Flip: - AgcDriverSubmitFlip_nid_postfix(flipInfo(call)); + traced(call, [&] { AgcDriverSubmitFlip_nid_postfix(flipInfo(call)); }); return; case FrameCallKind::ReserveEopFlip: { const auto token = static_cast(call.values.at(4)); @@ -463,7 +469,7 @@ struct Replay { return; } case FrameCallKind::Suspend: - AgcDriverSuspendPoint_nid_postfix(); + traced(call, [] { AgcDriverSuspendPoint_nid_postfix(); }); return; case FrameCallKind::Memory: { std::lock_guard lock(memoryMutex); @@ -654,6 +660,15 @@ struct Replay { } } + template + void traced(const FrameCall& call, TFunction&& function) { + if (options.traceSerials) { + std::fprintf(stderr, "[frame-replay] call %llu (%s, thread %llu, queue 0x%x, %llu dwords at 0x%llx)\n", static_cast(call.ordinal), std::string(AgcDriver::FrameCallName(call.kind)).c_str(), + static_cast(call.thread), call.queue, static_cast(call.size), static_cast(call.address)); + } + function(); + } + template void registration(TFunction&& function) { try { @@ -687,6 +702,15 @@ int usage() { " --repeat replays the frames n times in this process, restoring the snapshot before each pass;\n" " with n > 1 the frame times come from passes 2 to n, after the first warmed the caches\n" " --cold-pipelines starts with an empty Vulkan pipeline cache instead of the title's pipeline cache file\n" + " --shaders replays nothing and maps no guest memory; lists each shader registration with its code\n" + " address, size and code hash as the snapshot and the captured writes before it hold them,\n" + " and how many registrations share that code\n" + " --shader-code as --shaders, and writes each distinct code as /type-.bin\n" + " --trace-serials prints each submission, flip and suspend point with its call ordinal and command\n" + " address as it is issued; with APS5_TRACE_GPU the driver prints the serial it accepts\n" + " for each address\n" + " --submits replays nothing; lists the first n submissions and flips of the captured frames with\n" + " their queue and serials, the packets of each and the fields of its waits and releases\n" " --dump writes guest memory to a file after the replay (hexadecimal or decimal)\n" " --frames writes each recorded and replayed display buffer as reference-.bmp and replay-.bmp\n"); return 2; @@ -767,6 +791,236 @@ FrameCheck compareFrames(const FrameCaptureReader& reader, const Options& option return check; } +bool readable(std::uint64_t address, std::uint64_t bytes) { +#ifdef _WIN32 + for (auto at = address; at < address + bytes;) { + MEMORY_BASIC_INFORMATION info{}; + if (VirtualQuery(reinterpret_cast(static_cast(at)), &info, sizeof(info)) == 0 || info.State != MEM_COMMIT) return false; + const auto access = info.Protect & 0xffu; + if ((info.Protect & PAGE_GUARD) != 0 || access == PAGE_NOACCESS || access == PAGE_EXECUTE) return false; + at = reinterpret_cast(info.BaseAddress) + info.RegionSize; + } + return true; +#else + static_cast(address); + static_cast(bytes); + return false; +#endif +} + +std::uint64_t contentHash(const std::byte* bytes, std::size_t size) { + std::uint64_t hash = 0xcbf29ce484222325ull; + for (std::size_t index = 0; index < size; ++index) { + hash ^= std::to_integer(bytes[index]); + hash *= 0x100000001b3ull; + } + return hash; +} + +class CapturedMemory { +public: + explicit CapturedMemory(const FrameCaptureReader& reader) : reader(reader) { + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Snapshot) snapshots.push_back(&chunk); + else if (chunk.kind == FrameMemoryKind::Range) ranges.push_back(&chunk); + } + const auto byAddress = [](const FrameMemoryChunk* a, const FrameMemoryChunk* b) { return a->address < b->address; }; + std::sort(snapshots.begin(), snapshots.end(), byAddress); + std::sort(ranges.begin(), ranges.end(), byAddress); + } + + std::optional> Read(std::uint64_t address, std::uint64_t bytes) { + std::vector result(static_cast(bytes)); + for (auto at = address; at < address + bytes;) { + const auto* range = covering(ranges, at); + if (range == nullptr) return std::nullopt; + const auto* snapshot = covering(snapshots, at); + auto end = std::min(address + bytes, range->address + range->bytes); + if (snapshot != nullptr) { + end = std::min(end, snapshot->address + snapshot->bytes); + const auto& data = load(*snapshot); + std::memcpy(result.data() + (at - address), data.data() + (at - snapshot->address), static_cast(end - at)); + } else { + const auto next = std::upper_bound(snapshots.begin(), snapshots.end(), at, [](std::uint64_t value, const FrameMemoryChunk* chunk) { return value < chunk->address; }); + if (next != snapshots.end()) end = std::min(end, (*next)->address); + } + at = end; + } + for (const auto* delta : deltas) { + const auto begin = std::max(address, delta->address); + const auto end = std::min(address + bytes, delta->address + delta->bytes); + if (begin >= end) continue; + const auto data = reader.Read(*delta); + std::memcpy(result.data() + (begin - address), data.data() + (begin - delta->address), static_cast(end - begin)); + } + return result; + } + + void Apply(std::span chunks) { + deltas.insert(deltas.end(), chunks.begin(), chunks.end()); + } + +private: + static const FrameMemoryChunk* covering(const std::vector& chunks, std::uint64_t address) { + const auto next = std::upper_bound(chunks.begin(), chunks.end(), address, [](std::uint64_t value, const FrameMemoryChunk* chunk) { return value < chunk->address; }); + if (next == chunks.begin()) return nullptr; + const auto* chunk = *std::prev(next); + return address < chunk->address + chunk->bytes ? chunk : nullptr; + } + + const std::vector& load(const FrameMemoryChunk& chunk) { + if (cached != &chunk) { + data = reader.Read(chunk); + cached = &chunk; + } + return data; + } + + const FrameCaptureReader& reader; + std::vector snapshots; + std::vector ranges; + std::vector deltas; + const FrameMemoryChunk* cached = nullptr; + std::vector data; +}; + +int listShaders(const Options& options) { + const FrameCaptureReader reader(options.directory); + CapturedMemory memory(reader); + if (!options.shaderCode.empty()) std::filesystem::create_directories(options.shaderCode); + std::map> groups; + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Delta) groups[chunk.group].push_back(&chunk); + } + const auto& calls = reader.Calls(); + const auto frameStart = std::find_if(calls.begin(), calls.end(), [](const FrameCall& call) { return call.kind == FrameCallKind::FrameStart; }); + std::map copies; + std::size_t listed = 0, inWindow = 0, unreadable = 0; + for (auto call = calls.begin(); call != calls.end(); ++call) { + if (call->kind == FrameCallKind::Memory) { + if (const auto found = groups.find(call->values.at(0)); found != groups.end()) memory.Apply(found->second); + continue; + } + if (call->kind != FrameCallKind::RegisterShader) continue; + const auto header = memory.Read(call->address, sizeof(Shader)); + if (!header) { + ++unreadable; + continue; + } + Shader fields{}; + std::memcpy(&fields, header->data(), sizeof(Shader)); + const auto code = static_cast(reinterpret_cast(const_cast(fields.code))); + const auto bytes = fields.shader_size != 0 ? memory.Read(code, fields.shader_size) : std::nullopt; + if (!bytes) { + ++unreadable; + continue; + } + const auto hash = contentHash(bytes->data(), bytes->size()); + const auto copy = ++copies[hash]; + if (copy == 1 && !options.shaderCode.empty()) { + char name[64]; + std::snprintf(name, sizeof(name), "type%u-%016llx.bin", static_cast(fields.type), static_cast(hash)); + std::ofstream file(options.shaderCode / name, std::ios::binary | std::ios::trunc); + file.write(reinterpret_cast(bytes->data()), static_cast(bytes->size())); + } + const bool window = frameStart != calls.end() && call > frameStart; + ++listed; + inWindow += window ? 1 : 0; + std::printf("{\"ordinal\":%llu,\"in_window\":%s,\"header\":%llu,\"code\":%llu,\"bytes\":%u,\"type\":%u,\"hash\":\"%016llx\",\"copy\":%zu}\n", static_cast(call->ordinal), window ? "true" : "false", + static_cast(call->address), static_cast(code), fields.shader_size, static_cast(fields.type), static_cast(hash), copy); + } + std::printf("{\"registrations\":%zu,\"in_window\":%zu,\"distinct_code\":%zu,\"unreadable\":%zu}\n", listed, inWindow, copies.size(), unreadable); + std::fflush(stdout); + return 0; +} + +std::string_view packetName(std::uint32_t opcode) { + switch (opcode) { + case 0x10: return "NOP"; + case 0x15: return "DISPATCH_DIRECT"; + case 0x16: return "DISPATCH_INDIRECT"; + case 0x27: return "DRAW_INDEX_2"; + case 0x2d: return "DRAW_INDEX_AUTO"; + case 0x35: return "DRAW_INDEX_OFFSET_2"; + case 0x37: return "WRITE_DATA"; + case 0x3c: return "WAIT_REG_MEM"; + case 0x3f: return "INDIRECT_BUFFER"; + case 0x46: return "EVENT_WRITE"; + case 0x49: return "RELEASE_MEM"; + case 0x50: return "DMA_DATA"; + case 0x58: return "ACQUIRE_MEM"; + case 0x69: return "SET_CONTEXT_REG"; + case 0x76: return "SET_SH_REG"; + case 0x79: return "SET_UCONFIG_REG"; + default: return {}; + } +} + +void describeSubmission(const FrameCall& call) { + const auto* words = reinterpret_cast(static_cast(call.address)); + if (!readable(call.address, call.size * sizeof(std::uint32_t))) { + std::printf(" (command buffer not readable)\n"); + return; + } + std::map counts; + for (std::size_t at = 0; at < call.size;) { + const auto header = words[at]; + const auto type = header >> 30u; + const auto length = type == 3u ? static_cast((header >> 16u) & 0x3fffu) + 2u : 1u; + if (type == 3u) { + const auto opcode = (header >> 8u) & 0xffu; + const auto name = packetName(opcode); + char label[24]; + std::snprintf(label, sizeof(label), "0x%02x", opcode); + const std::string key = name.empty() ? std::string(label) : std::string(name); + const auto word = [&](std::size_t index) { return at + index < call.size ? words[at + index] : 0u; }; + if (opcode == 0x3c) { + std::printf(" @%zu WAIT_REG_MEM function %u space %u address 0x%llx reference 0x%x mask 0x%x\n", at, word(1) & 7u, (word(1) >> 4u) & 3u, + static_cast(word(2)) | (static_cast(word(3)) << 32u), word(4), word(5)); + } else if (opcode == 0x49 || opcode == 0x37 || opcode == 0x46) { + std::printf(" @%zu %s", at, key.c_str()); + for (std::size_t index = 1; index < length && index < 8; ++index) std::printf(" %08x", word(index)); + std::printf("\n"); + } + ++counts[key]; + } + at += length; + } + std::printf(" packets:"); + for (const auto& [name, count] : counts) std::printf(" %s x%zu", name.c_str(), count); + std::printf("\n"); +} + +int listSubmits(const Options& options) { + const FrameCaptureReader reader(options.directory); + const auto skipped = restoreMemory(reader); + std::map> groups; + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Delta) groups[chunk.group].push_back(&chunk); + } + const auto& calls = reader.Calls(); + const auto frameStart = std::find_if(calls.begin(), calls.end(), [](const FrameCall& call) { return call.kind == FrameCallKind::FrameStart; }); + std::size_t listed = 0; + for (auto call = frameStart; call != calls.end() && listed < options.submits; ++call) { + if (call->kind == FrameCallKind::Memory) { + if (const auto found = groups.find(call->values.at(0)); found != groups.end()) { + for (const auto* chunk : found->second) { + if (!skipped.Overlaps(*chunk)) restore(reader, *chunk); + } + } + continue; + } + if (call->kind != FrameCallKind::Submit && call->kind != FrameCallKind::Flip && call->kind != FrameCallKind::ReserveEopFlip) continue; + ++listed; + std::printf("#%zu ordinal %llu %s thread %llu queue 0x%x serials %llu/%llu address 0x%llx dwords %llu\n", listed, static_cast(call->ordinal), std::string(AgcDriver::FrameCallName(call->kind)).c_str(), + static_cast(call->thread), call->queue, static_cast(call->completed), static_cast(call->accepted), + static_cast(call->address), static_cast(call->size)); + if (call->kind == FrameCallKind::Submit) describeSubmission(*call); + } + std::fflush(stdout); + return 0; +} + struct Pass { std::size_t firstFrame = 0; std::size_t frames = 0; @@ -890,6 +1144,16 @@ int main(int argc, char** argv) { else if (argument == "--release-after-ms" && index + 1 < argc) options.releaseAfter = std::chrono::milliseconds(std::strtoll(argv[++index], nullptr, 10)); else if (argument == "--strict") options.strict = true; else if (argument == "--repeat" && index + 1 < argc) options.repeat = std::max(1, std::strtoull(argv[++index], nullptr, 10)); + else if (argument == "--shaders") options.shaders = true; + else if (argument == "--shader-code" && index + 1 < argc) { + options.shaders = true; + options.shaderCode = argv[++index]; + } + else if (argument == "--submits" && index + 1 < argc) { + char* end = nullptr; + options.submits = std::strtoull(argv[++index], &end, 10); + if (options.submits == 0 || end == argv[index] || *end != '\0') return usage(); + } else if (argument == "--trace-serials") options.traceSerials = true; else if (argument == "--cold-pipelines") options.coldPipelines = true; else if (argument == "--dump" && index + 1 < argc) options.dumps.push_back(parseDump(argv[++index])); else if (argument == "--frames" && index + 1 < argc) options.frames = argv[++index]; @@ -907,8 +1171,9 @@ int main(int argc, char** argv) { #endif int code = 2; try { - code = run(options); - if (code == 0) { + const bool listing = options.shaders || options.submits != 0; + code = options.shaders ? listShaders(options) : options.submits != 0 ? listSubmits(options) : run(options); + if (code == 0 && !listing) { AgcDriverShutdown_nid_postfix(); return 0; } diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md index 5b9e00b8a6..bd57560d77 100644 --- a/docs/dev/BUILD.md +++ b/docs/dev/BUILD.md @@ -228,7 +228,7 @@ On Windows use `agc_pm4_replay.exe` and prepend `build/core/libs/libs/unpatched` On Windows and Linux, set `APS5_FRAME_CAPTURE` to a directory to record a few frames of a title at the AGC driver boundary, then replay them with `agc_frame_replay` through any build of the driver with no title running. From the start of the process the driver keeps every shader registration and ABI resolution, video output registration and EOP flip reservation. At the frame boundary picked by `APS5_FRAME_CAPTURE_AT_FLIP` (default 1), or by the appearance of the file `APS5_FRAME_CAPTURE_TRIGGER` names (a number in it replaces the frame count), it waits for the driver to go idle and writes every committed, readable allocation of the guest allocation registry, the title's image included, as zstd chunks without their all-zero 64 KiB blocks. A frame ends at a CPU flip or at a submission that flips (a flip packet, or the EOP release of a reserved flip token); the reservation itself only records its token. For the next `APS5_FRAME_CAPTURE_FRAMES` (10) frames it records each submission (queue, command buffer address and size), flip and suspend point with the thread that made it and the accepted and completed serials, each preceded by the guest pages the write tracker saw the CPU write since the previous call that handed work to the GPU (`GuestMemory::CollectWrittenBlocks`, per 4 KiB where the tracker kept page stamps), read once the submissions already accepted have completed (a recorded submission, flip or suspend point reaches the driver before the recorder takes its next call, so that wait covers every call recorded before the delta); an allocation the registry gained after the snapshot is recorded there as a new range with its nonzero 64 KiB blocks. A submission ends a frame only when it flips itself, not when another thread's submission flipped while it was being submitted. Shader registrations and ABI resolutions only collect the blocks of the shader header and the arrays it points to, and the pages of the title's image only when their content changed. At the end it stores each display buffer the title presented, decoded, as a reference image. Without the guest write watch (`APS5_NO_WRITE_WATCH`, or no write watch on the host) the capture throws when it starts, since it could not record CPU stores. A capture is `calls.bin`, `memory.bin` and `capture.json` (commit, switches, sizes, warnings), and the recorder stops with a warning when its volume has less than 4 GiB free. A 20-frame capture in the Demon's Souls Nexus is 5.3 GiB of snapshot and 11.5 GiB of deltas before compression, 3.9 GiB on disk. -`agc_frame_replay [--no-pace] [--no-release] [--release-after-ms ] [--no-synthesize] [--strict] [--repeat ] [--cold-pipelines] [--dump
::]... [--frames ]` maps the captured ranges at their addresses and registers them with their access (a range the replaying process already maps writable, such as the driver's fixed memory, is only refilled, and a non-GPU range it uses itself is skipped and counted), repeats the registrations, then replays the calls of each captured thread on a thread of its own, started in captured order. Before a call that hands work to the GPU it waits until no more submissions are in flight than when the title made it (`--no-pace` turns this off). Titles release GPU waits from threads that never call the driver, so when the driver completes nothing for 3 ms the replay reads the waits its queues are blocked on (`AgcDriverAwaitedWaits`) and writes the next recorded CPU value of the awaited dword that satisfies the compare, else the last earlier one, else a value derived from the reference (`--no-synthesize` turns this last step off). It prints one JSON line with the frame count and times, the submissions, the pacing wait, the released waits and how many were derived, the submissions that never completed and, for captures with reference images, the share of identical pixels and the largest channel difference of the replayed display buffers (`--frames` writes both images). Effects that depend on timing (temporal antialiasing, particles) make pixels differ, so expect a visual match rather than identical frames. The replay's device has no window, but it loads the title's Vulkan pipeline cache file (`vk-pipelines-*.bin` in the shader cache directory) as the windowed device does, so pipelines the title already compiled are not compiled again (`--cold-pipelines` starts with an empty one; the driver switch is `APS5_HEADLESS_PIPELINE_CACHE`). The title replayed its pipeline journal at boot, long before the captured frames, so after the registrations the replay waits for the driver's journal replay to finish before the first pass (`journal_s` in the JSON line). The first pass still compiles every shader and pipeline the frames use that neither cache holds, so `--repeat ` replays the frames n times in one process: before each later pass it waits for the driver to go idle, clears the captured ranges, restores the snapshot and makes the reservations that were pending at the snapshot again (a token still reserved from the previous pass is kept), and the frame times then come from passes 2 to n, with each pass listed under `passes`. Later passes replay the same frames, so driver caches keyed on values that change every frame in the title (ring addresses in user data, for example) can hit entries of an earlier pass; compare such changes on the first pass with warm disk caches instead. Replay times follow the GPU clock state: lock the clocks (`nvidia-smi -lgc`, `-lmc`) when comparing two drivers. +`agc_frame_replay [--no-pace] [--no-release] [--release-after-ms ] [--no-synthesize] [--strict] [--repeat ] [--cold-pipelines] [--shaders] [--shader-code ] [--submits ] [--trace-serials] [--dump
::]... [--frames ]` maps the captured ranges at their addresses and registers them with their access (a range the replaying process already maps writable, such as the driver's fixed memory, is only refilled, and a non-GPU range it uses itself is skipped and counted), repeats the registrations, then replays the calls of each captured thread on a thread of its own, started in captured order. Before a call that hands work to the GPU it waits until no more submissions are in flight than when the title made it (`--no-pace` turns this off). Titles release GPU waits from threads that never call the driver, so when the driver completes nothing for 3 ms the replay reads the waits its queues are blocked on (`AgcDriverAwaitedWaits`) and writes the next recorded CPU value of the awaited dword that satisfies the compare, else the last earlier one, else a value derived from the reference (`--no-synthesize` turns this last step off). It prints one JSON line with the frame count and times, the submissions, the pacing wait, the released waits and how many were derived, the submissions that never completed and, for captures with reference images, the share of identical pixels and the largest channel difference of the replayed display buffers (`--frames` writes both images). Effects that depend on timing (temporal antialiasing, particles) make pixels differ, so expect a visual match rather than identical frames. The replay's device has no window, but it loads the title's Vulkan pipeline cache file (`vk-pipelines-*.bin` in the shader cache directory) as the windowed device does, so pipelines the title already compiled are not compiled again (`--cold-pipelines` starts with an empty one; the driver switch is `APS5_HEADLESS_PIPELINE_CACHE`). The title replayed its pipeline journal at boot, long before the captured frames, so after the registrations the replay waits for the driver's journal replay to finish before the first pass (`journal_s` in the JSON line). The first pass still compiles every shader and pipeline the frames use that neither cache holds, so `--repeat ` replays the frames n times in one process: before each later pass it waits for the driver to go idle, clears the captured ranges, restores the snapshot and makes the reservations that were pending at the snapshot again (a token still reserved from the previous pass is kept), and the frame times then come from passes 2 to n, with each pass listed under `passes`. Later passes replay the same frames, so driver caches keyed on values that change every frame in the title (ring addresses in user data, for example) can hit entries of an earlier pass; compare such changes on the first pass with warm disk caches instead. `--shaders` replays nothing and maps no guest memory: it reads the snapshot, applies the captured writes in call order and prints one JSON line per shader registration (code address, size, type, code hash and how many earlier registrations had the same code), then a summary; `--shader-code ` also writes each distinct code as `/type-.bin`. `--submits ` replays nothing either and lists the first n submissions and flips of the captured frames with their queue and serials, the packets of each and the fields of its waits and releases. `--trace-serials` prints each submission, flip and suspend point with its call ordinal and command address as it is issued; with `APS5_TRACE_GPU` the driver prints the serial it accepts for each address and each completion, so a replay that stops completing can be followed to the call that made the submission. Replay times follow the GPU clock state: lock the clocks (`nvidia-smi -lgc`, `-lmc`) when comparing two drivers. ```sh cmake --build build --target agc_frame_replay From e6b0a9b4df15a082a2d07f9f466d9853018e86bc Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 27 Oct 2026 17:31:16 +0000 Subject: [PATCH 073/612] fix(agc): replay listing review fixes: non-Windows packet decoding, payload words, output errors --submits decodes the captured ranges on Linux and macOS as well, walks packets with Pm4::PacketWords and FillerPacket so type-0 payload dwords are not taken for headers, and the listing checks open, write, flush and close of its output file, naming the path and exiting nonzero on failure. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 18 ++- .../tests/FrameReplayState.cpp | 85 ++++++++++++++ .../tests/FrameReplayTool.cmake | 105 ++++++++++++++++++ .../prx/libSceAgcDriver/tools/FrameReplay.cpp | 87 ++++++++------- docs/dev/BUILD.md | 2 +- 5 files changed, 252 insertions(+), 45 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/FrameReplayTool.cmake diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index a1715c5d1d..4c17b837e9 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -3598,7 +3598,19 @@ if(BUILD_TESTING) target_link_libraries(agc_driver_frame_replay_state_tests PRIVATE libSceAgcDriver libkernel libc libzstd_static) set_target_properties(agc_driver_frame_replay_state_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_driver_frame_replay_state_tests) - set(agcFrameReplayModes order tokens blocking reservations reservation_failure) + add_dependencies(agc_driver_frame_replay_state_tests agc_frame_replay) + foreach(mode IN ITEMS submits shader_output) + add_test(NAME agc_driver_frame_replay_${mode} COMMAND ${CMAKE_COMMAND} + "-DTOOL=$" "-DWRITER=$" + "-DDIRECTORY=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-${mode}" "-DMODE=${mode}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/tests/FrameReplayTool.cmake") + set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + set(agcFrameReplayModes order tokens blocking reservations reservation_failure environment) if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux") list(APPEND agcFrameReplayModes zero shader shader_host) endif() @@ -3620,6 +3632,10 @@ if(BUILD_TESTING) set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES ENVIRONMENT "APS5_NO_WRITE_WATCH=1;APS5_FRAME_CAPTURE=${capture};APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=1") endif() + if(mode STREQUAL "environment") + set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES ENVIRONMENT + "APS5_HEADLESS_PIPELINE_CACHE=1;APS5_FRAME_CAPTURE=${capture}") + endif() if(WIN32) set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp index e94b91c580..0b08d26e24 100644 --- a/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp @@ -394,6 +394,86 @@ void testShaderCode(const std::filesystem::path& directory, bool host) { check(listed && *listed == expected, "the offline shader listing read other code than the capture holds at the registration"); } +void writeInspectionCapture(const std::filesystem::path& directory) { + constexpr std::uint64_t Commands = 0x1000000001ull; + constexpr std::uint64_t Header = 0x2000000000ull; + constexpr std::uint64_t Code = 0x2000010000ull; + const std::array commands{ + 0x00010000u, 0xc0053c00u, 0xc0064900u, 0x80000000u, 0xffff1000u, + 0xc0053c00u, 0x13u, 0x12345678u, 0x9abcdef0u, 0x10203040u, 0xf0f0f0f0u, 4u, + 0xc0064900u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, + 0xc0033700u, 0x500u, 0xdeadbeefu, 0x12u, 0x1337u, + 0x00030000u, 0xc0053c00u}; + std::array code{0, 1, 2, 3, 4, 5, 6, 7, 255, 128, 13, 10, 39, 36, 38, 0}; + Shader shader{}; + shader.code = reinterpret_cast(static_cast(Code)); + shader.shader_size = code.size(); + FrameCaptureWriter writer(directory); + writer.WriteRange(Commands, 6, FrameRangeKind::Host, ReadWrite, FrameRangeAccess::Readable); + writer.WriteRange(Commands + 6, sizeof(commands) - 6, FrameRangeKind::Host, ReadWrite, FrameRangeAccess::Readable); + writer.WriteRange(Header, sizeof(shader), FrameRangeKind::Host, ReadWrite, FrameRangeAccess::Readable); + writer.WriteRange(Code, code.size(), FrameRangeKind::Host, ReadWrite, FrameRangeAccess::Readable); + writer.WriteSnapshot(Header, std::as_bytes(std::span(&shader, 1))); + writer.WriteSnapshot(Code, std::as_bytes(std::span(code))); + writer.WriteDelta(1, 0, Commands, std::as_bytes(std::span(commands))); + const std::uint32_t reference = 0x55667788u; + writer.WriteDelta(2, 0, Commands + 9 * sizeof(std::uint32_t), std::as_bytes(std::span(&reference, 1))); + code[0] = 0x42; + writer.WriteDelta(3, 0, Code, std::as_bytes(std::span(code))); + FrameCall call; + const auto write = [&](FrameCallKind kind, std::uint64_t location, std::uint64_t size, std::vector values = {}) { + call.kind = kind; + ++call.ordinal; + call.address = location; + call.size = size; + call.values = std::move(values); + call.thread = 7; + call.queue = 0x42; + call.accepted = 12; + call.completed = 9; + writer.WriteCall(call); + }; + write(FrameCallKind::Submit, Commands, commands.size()); + write(FrameCallKind::Memory, 0, 0, {1}); + write(FrameCallKind::RegisterShader, Header, 0); + write(FrameCallKind::FrameStart, 0, 0); + write(FrameCallKind::Submit, Commands, commands.size()); + write(FrameCallKind::Memory, 0, 0, {2}); + write(FrameCallKind::Submit, Commands, commands.size()); + write(FrameCallKind::RegisterShader, Header, 0); + write(FrameCallKind::Memory, 0, 0, {3}); + write(FrameCallKind::RegisterShader, Header, 0); + write(FrameCallKind::Flip, 0, 0); + write(FrameCallKind::ReserveEopFlip, 0, 0); + write(FrameCallKind::Submit, 0x3000000000ull, 1); + write(FrameCallKind::Submit, Commands, std::numeric_limits::max()); + write(FrameCallKind::Submit, std::numeric_limits::max() - 1, 1); + write(FrameCallKind::Submit, Commands, 0); + writer.Finish("{}"); +} + +void testColdPipelines() { + const auto* inherited = std::getenv("APS5_HEADLESS_PIPELINE_CACHE"); + check(inherited != nullptr && std::string_view(inherited) == "1", "pipeline cache setting was not inherited"); + Options options; + options.coldPipelines = true; + configureEnvironment(options); + check(std::getenv("APS5_HEADLESS_PIPELINE_CACHE") == nullptr, "cold replay kept the inherited pipeline cache setting"); + check(std::getenv("APS5_FRAME_CAPTURE") == nullptr, "replay kept the capture setting"); + options.coldPipelines = false; + configureEnvironment(options); + const auto* warm = std::getenv("APS5_HEADLESS_PIPELINE_CACHE"); + check(warm != nullptr && std::string_view(warm) == "1", "warm replay did not enable the pipeline cache"); +#ifdef _WIN32 + _putenv_s("APS5_HEADLESS_PIPELINE_CACHE", "0"); +#else + setenv("APS5_HEADLESS_PIPELINE_CACHE", "0", 1); +#endif + options.coldPipelines = true; + configureEnvironment(options); + check(std::getenv("APS5_HEADLESS_PIPELINE_CACHE") == nullptr, "cold replay kept a present zero pipeline cache setting"); +} + } int main(int argc, char** argv) { @@ -401,6 +481,10 @@ int main(int argc, char** argv) { check(argc == 3, "expected a test name and capture directory"); const std::string mode = argv[1]; const std::filesystem::path directory = argv[2]; + if (mode == "inspect_capture") { + writeInspectionCapture(directory); + return 0; + } if (mode == "order") testMemoryOrder(directory); else if (mode == "zero") testReusedMemory(directory); else if (mode == "shared_page") testSharedPage(directory); @@ -409,6 +493,7 @@ int main(int argc, char** argv) { else if (mode == "tokens") testEopTokens(); else if (mode == "reservations" || mode == "reservation_failure") testRepeatedReservations(directory, mode == "reservation_failure"); else if (mode == "blocking") testBlockingSubmission(directory); + else if (mode == "environment") testColdPipelines(); else if (mode == "shader" || mode == "shader_host") testShaderCode(directory, mode == "shader_host"); else throw std::runtime_error("unknown test"); if (mode != "reservation_failure") AgcDriverShutdown_nid_postfix(); diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplayTool.cmake b/core/libs/prx/libSceAgcDriver/tests/FrameReplayTool.cmake new file mode 100644 index 0000000000..86da56139d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplayTool.cmake @@ -0,0 +1,105 @@ +set(capture "${DIRECTORY}/capture 'quoted' & $scene %APS5_HEADLESS_PIPELINE_CACHE% ! (1)") +execute_process(COMMAND "${WRITER}" inspect_capture "${capture}" + RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error) +if(NOT result STREQUAL "0") + message(FATAL_ERROR "capture writer failed: ${result}\n${output}\n${error}") +endif() +if(NOT EXISTS "${capture}/calls.bin") + message(FATAL_ERROR "capture path was not passed literally: ${capture}") +endif() + +function(runTool expected) + execute_process(COMMAND "${TOOL}" "${capture}" ${ARGN} + RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error) + string(REPLACE "\\" "/" error "${error}") + if(NOT result STREQUAL "${expected}") + message(FATAL_ERROR "agc_frame_replay ${ARGN}: expected ${expected}, got ${result}\n${output}\n${error}") + endif() + set(output "${output}" PARENT_SCOPE) + set(error "${error}" PARENT_SCOPE) +endfunction() + +function(requireContains text needle) + string(FIND "${text}" "${needle}" found) + if(found EQUAL -1) + message(FATAL_ERROR "missing '${needle}' in:\n${text}") + endif() +endfunction() + +if(MODE STREQUAL "submits") + runTool(0 --submits 1) + requireContains("${output}" "#1 ordinal 5 submit thread 7 queue 0x42 serials 9/12 address 0x1000000001 dwords 27") + requireContains("${output}" "@5 WAIT_REG_MEM function 3 space 1 address 0x9abcdef012345678 reference 0x10203040 mask 0xf0f0f0f0") + requireContains("${output}" "@12 RELEASE_MEM 00000001 00000002 00000003 00000004 00000005 00000006 00000007") + requireContains("${output}" "@20 WRITE_DATA 00000500 deadbeef 00000012 00001337") + requireContains("${output}" "@25 (truncated packet: 5 dwords, 2 available)") + requireContains("${output}" "packets: RELEASE_MEM x1 WAIT_REG_MEM x1 WRITE_DATA x1") + if(output MATCHES "#2|ordinal 1 |reference 0x55667788|NOP|@1 WAIT_REG_MEM") + message(FATAL_ERROR "listing included a later submission, prologue or payload packet:\n${output}") + endif() + runTool(0 --submits 2) + requireContains("${output}" "#2 ordinal 7 submit") + requireContains("${output}" "reference 0x55667788") + runTool(0 --submits 4) + requireContains("${output}" "#3 ordinal 11 flip") + requireContains("${output}" "#4 ordinal 12 reserve-eop-flip") + if(output MATCHES "#5") + message(FATAL_ERROR "listing exceeded --submits 4:\n${output}") + endif() + runTool(0 --submits 8) + requireContains("${output}" "#8 ordinal 16 submit") + string(REGEX MATCHALL "command buffer not readable" unreadable "${output}") + list(LENGTH unreadable count) + if(NOT count EQUAL 3) + message(FATAL_ERROR "missing unrecorded and overflowing buffer diagnostics:\n${output}") + endif() + foreach(value IN ITEMS 0 -1 +1 1junk 18446744073709551616) + runTool(2 --submits "${value}") + requireContains("${error}" "usage: agc_frame_replay") + endforeach() + runTool(2 --submits) +elseif(MODE STREQUAL "shader_output") + runTool(0 --shaders) + requireContains("${output}" "\"ordinal\":3,\"in_window\":false") + requireContains("${output}" "\"ordinal\":8,\"in_window\":true") + requireContains("${output}" "\"hash\":\"541dd60be6dc3e20\",\"copy\":2") + requireContains("${output}" "\"hash\":\"b6df526defd638c2\",\"copy\":1") + requireContains("${output}" "{\"registrations\":3,\"in_window\":2,\"distinct_code\":2,\"unreadable\":0}") + set(binaries "${DIRECTORY}/shader code 'quoted' & $scene (2)") + runTool(0 --cold-pipelines --shader-code "${binaries}") + file(GLOB files "${binaries}/*.bin") + list(LENGTH files count) + if(NOT count EQUAL 2) + message(FATAL_ERROR "expected two distinct shader binaries, got ${count}") + endif() + file(READ "${binaries}/type0-541dd60be6dc3e20.bin" first HEX) + file(READ "${binaries}/type0-b6df526defd638c2.bin" second HEX) + if(NOT first STREQUAL "0001020304050607ff800d0a27242600" OR NOT second STREQUAL "4201020304050607ff800d0a27242600") + message(FATAL_ERROR "shader binaries differ from the bytes at registration: ${first}, ${second}") + endif() + set(blocked "${DIRECTORY}/blocked output 'quoted' & $scene (3)") + set(blockedFile "${blocked}/type0-541dd60be6dc3e20.bin") + file(MAKE_DIRECTORY "${blockedFile}") + runTool(2 --shader-code "${blocked}") + requireContains("${error}" "cannot open ${blockedFile}") + if(output MATCHES "\"registrations\"") + message(FATAL_ERROR "failed shader output reported success JSON:\n${output}") + endif() + if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") + set(full "${DIRECTORY}/full output") + file(MAKE_DIRECTORY "${full}") + set(fullFile "${full}/type0-541dd60be6dc3e20.bin") + file(REMOVE "${fullFile}") + file(CREATE_LINK /dev/full "${fullFile}" SYMBOLIC RESULT linked) + if(NOT linked STREQUAL "0") + message(FATAL_ERROR "cannot link /dev/full: ${linked}") + endif() + runTool(2 --shader-code "${full}") + requireContains("${error}" "${fullFile}") + if(NOT error MATCHES "cannot (write|flush|close)" OR NOT output STREQUAL "") + message(FATAL_ERROR "late shader output failure was not reported:\n${output}\n${error}") + endif() + endif() +else() + message(FATAL_ERROR "unknown inspection mode: ${MODE}") +endif() diff --git a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp index 04e9ca9c92..713406ccae 100644 --- a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp @@ -3,6 +3,7 @@ #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/FrameCapture.hpp" #include "prx/libSceAgcDriver/Execution/include/FrameDumpIo.hpp" +#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" #include "prx/libSceAgcDriver/Submit/include/Acb.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" @@ -13,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -791,23 +794,6 @@ FrameCheck compareFrames(const FrameCaptureReader& reader, const Options& option return check; } -bool readable(std::uint64_t address, std::uint64_t bytes) { -#ifdef _WIN32 - for (auto at = address; at < address + bytes;) { - MEMORY_BASIC_INFORMATION info{}; - if (VirtualQuery(reinterpret_cast(static_cast(at)), &info, sizeof(info)) == 0 || info.State != MEM_COMMIT) return false; - const auto access = info.Protect & 0xffu; - if ((info.Protect & PAGE_GUARD) != 0 || access == PAGE_NOACCESS || access == PAGE_EXECUTE) return false; - at = reinterpret_cast(info.BaseAddress) + info.RegionSize; - } - return true; -#else - static_cast(address); - static_cast(bytes); - return false; -#endif -} - std::uint64_t contentHash(const std::byte* bytes, std::size_t size) { std::uint64_t hash = 0xcbf29ce484222325ull; for (std::size_t index = 0; index < size; ++index) { @@ -830,6 +816,7 @@ public: } std::optional> Read(std::uint64_t address, std::uint64_t bytes) { + if (bytes > std::numeric_limits::max() || bytes > std::numeric_limits::max() - address) return std::nullopt; std::vector result(static_cast(bytes)); for (auto at = address; at < address + bytes;) { const auto* range = covering(ranges, at); @@ -920,8 +907,15 @@ int listShaders(const Options& options) { if (copy == 1 && !options.shaderCode.empty()) { char name[64]; std::snprintf(name, sizeof(name), "type%u-%016llx.bin", static_cast(fields.type), static_cast(hash)); - std::ofstream file(options.shaderCode / name, std::ios::binary | std::ios::trunc); + const auto path = options.shaderCode / name; + std::ofstream file(path, std::ios::binary | std::ios::trunc); + if (!file.is_open()) throw std::runtime_error("frame replay: cannot open " + path.string()); file.write(reinterpret_cast(bytes->data()), static_cast(bytes->size())); + if (!file) throw std::runtime_error("frame replay: cannot write " + path.string()); + file.flush(); + if (!file) throw std::runtime_error("frame replay: cannot flush " + path.string()); + file.close(); + if (!file) throw std::runtime_error("frame replay: cannot close " + path.string()); } const bool window = frameStart != calls.end() && call > frameStart; ++listed; @@ -956,24 +950,30 @@ std::string_view packetName(std::uint32_t opcode) { } } -void describeSubmission(const FrameCall& call) { - const auto* words = reinterpret_cast(static_cast(call.address)); - if (!readable(call.address, call.size * sizeof(std::uint32_t))) { +void describeSubmission(const FrameCall& call, CapturedMemory& memory) { + const auto bytes = call.size <= std::numeric_limits::max() / sizeof(std::uint32_t) ? memory.Read(call.address, call.size * sizeof(std::uint32_t)) : std::nullopt; + if (!bytes) { std::printf(" (command buffer not readable)\n"); return; } + std::vector words(static_cast(call.size)); + if (!bytes->empty()) std::memcpy(words.data(), bytes->data(), bytes->size()); std::map counts; for (std::size_t at = 0; at < call.size;) { const auto header = words[at]; const auto type = header >> 30u; - const auto length = type == 3u ? static_cast((header >> 16u) & 0x3fffu) + 2u : 1u; - if (type == 3u) { + const auto length = AgcDriver::Pm4::PacketWords(header); + if (length > words.size() - at) { + std::printf(" @%zu (truncated packet: %zu dwords, %zu available)\n", at, length, words.size() - at); + break; + } + if (type == 3u && !AgcDriver::Pm4::FillerPacket(header)) { const auto opcode = (header >> 8u) & 0xffu; const auto name = packetName(opcode); char label[24]; std::snprintf(label, sizeof(label), "0x%02x", opcode); const std::string key = name.empty() ? std::string(label) : std::string(name); - const auto word = [&](std::size_t index) { return at + index < call.size ? words[at + index] : 0u; }; + const auto word = [&](std::size_t index) { return index < length ? words[at + index] : 0u; }; if (opcode == 0x3c) { std::printf(" @%zu WAIT_REG_MEM function %u space %u address 0x%llx reference 0x%x mask 0x%x\n", at, word(1) & 7u, (word(1) >> 4u) & 3u, static_cast(word(2)) | (static_cast(word(3)) << 32u), word(4), word(5)); @@ -993,7 +993,7 @@ void describeSubmission(const FrameCall& call) { int listSubmits(const Options& options) { const FrameCaptureReader reader(options.directory); - const auto skipped = restoreMemory(reader); + CapturedMemory memory(reader); std::map> groups; for (const auto& chunk : reader.Chunks()) { if (chunk.kind == FrameMemoryKind::Delta) groups[chunk.group].push_back(&chunk); @@ -1001,21 +1001,18 @@ int listSubmits(const Options& options) { const auto& calls = reader.Calls(); const auto frameStart = std::find_if(calls.begin(), calls.end(), [](const FrameCall& call) { return call.kind == FrameCallKind::FrameStart; }); std::size_t listed = 0; - for (auto call = frameStart; call != calls.end() && listed < options.submits; ++call) { + for (auto call = calls.begin(); call != calls.end() && listed < options.submits; ++call) { if (call->kind == FrameCallKind::Memory) { - if (const auto found = groups.find(call->values.at(0)); found != groups.end()) { - for (const auto* chunk : found->second) { - if (!skipped.Overlaps(*chunk)) restore(reader, *chunk); - } - } + if (const auto found = groups.find(call->values.at(0)); found != groups.end()) memory.Apply(found->second); continue; } + if (call < frameStart) continue; if (call->kind != FrameCallKind::Submit && call->kind != FrameCallKind::Flip && call->kind != FrameCallKind::ReserveEopFlip) continue; ++listed; std::printf("#%zu ordinal %llu %s thread %llu queue 0x%x serials %llu/%llu address 0x%llx dwords %llu\n", listed, static_cast(call->ordinal), std::string(AgcDriver::FrameCallName(call->kind)).c_str(), static_cast(call->thread), call->queue, static_cast(call->completed), static_cast(call->accepted), static_cast(call->address), static_cast(call->size)); - if (call->kind == FrameCallKind::Submit) describeSubmission(*call); + if (call->kind == FrameCallKind::Submit) describeSubmission(*call, memory); } std::fflush(stdout); return 0; @@ -1132,6 +1129,17 @@ int run(const Options& options) { return replay.unfinished == 0 ? 0 : 3; } +void configureEnvironment(const Options& options) { +#ifdef _WIN32 + _putenv_s("APS5_FRAME_CAPTURE", ""); + _putenv_s("APS5_HEADLESS_PIPELINE_CACHE", options.coldPipelines ? "" : "1"); +#else + unsetenv("APS5_FRAME_CAPTURE"); + if (options.coldPipelines) unsetenv("APS5_HEADLESS_PIPELINE_CACHE"); + else setenv("APS5_HEADLESS_PIPELINE_CACHE", "1", 1); +#endif +} + } int main(int argc, char** argv) { @@ -1150,9 +1158,9 @@ int main(int argc, char** argv) { options.shaderCode = argv[++index]; } else if (argument == "--submits" && index + 1 < argc) { - char* end = nullptr; - options.submits = std::strtoull(argv[++index], &end, 10); - if (options.submits == 0 || end == argv[index] || *end != '\0') return usage(); + const std::string_view value = argv[++index]; + const auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), options.submits); + if (error != std::errc{} || end != value.data() + value.size() || options.submits == 0) return usage(); } else if (argument == "--trace-serials") options.traceSerials = true; else if (argument == "--cold-pipelines") options.coldPipelines = true; else if (argument == "--dump" && index + 1 < argc) options.dumps.push_back(parseDump(argv[++index])); @@ -1161,14 +1169,7 @@ int main(int argc, char** argv) { else return usage(); } if (options.directory.empty()) return usage(); -#ifdef _WIN32 - _putenv_s("APS5_FRAME_CAPTURE", ""); - _putenv_s("APS5_HEADLESS_PIPELINE_CACHE", options.coldPipelines ? "" : "1"); -#else - unsetenv("APS5_FRAME_CAPTURE"); - if (options.coldPipelines) unsetenv("APS5_HEADLESS_PIPELINE_CACHE"); - else setenv("APS5_HEADLESS_PIPELINE_CACHE", "1", 1); -#endif + configureEnvironment(options); int code = 2; try { const bool listing = options.shaders || options.submits != 0; diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md index bd57560d77..6590403b5b 100644 --- a/docs/dev/BUILD.md +++ b/docs/dev/BUILD.md @@ -228,7 +228,7 @@ On Windows use `agc_pm4_replay.exe` and prepend `build/core/libs/libs/unpatched` On Windows and Linux, set `APS5_FRAME_CAPTURE` to a directory to record a few frames of a title at the AGC driver boundary, then replay them with `agc_frame_replay` through any build of the driver with no title running. From the start of the process the driver keeps every shader registration and ABI resolution, video output registration and EOP flip reservation. At the frame boundary picked by `APS5_FRAME_CAPTURE_AT_FLIP` (default 1), or by the appearance of the file `APS5_FRAME_CAPTURE_TRIGGER` names (a number in it replaces the frame count), it waits for the driver to go idle and writes every committed, readable allocation of the guest allocation registry, the title's image included, as zstd chunks without their all-zero 64 KiB blocks. A frame ends at a CPU flip or at a submission that flips (a flip packet, or the EOP release of a reserved flip token); the reservation itself only records its token. For the next `APS5_FRAME_CAPTURE_FRAMES` (10) frames it records each submission (queue, command buffer address and size), flip and suspend point with the thread that made it and the accepted and completed serials, each preceded by the guest pages the write tracker saw the CPU write since the previous call that handed work to the GPU (`GuestMemory::CollectWrittenBlocks`, per 4 KiB where the tracker kept page stamps), read once the submissions already accepted have completed (a recorded submission, flip or suspend point reaches the driver before the recorder takes its next call, so that wait covers every call recorded before the delta); an allocation the registry gained after the snapshot is recorded there as a new range with its nonzero 64 KiB blocks. A submission ends a frame only when it flips itself, not when another thread's submission flipped while it was being submitted. Shader registrations and ABI resolutions only collect the blocks of the shader header and the arrays it points to, and the pages of the title's image only when their content changed. At the end it stores each display buffer the title presented, decoded, as a reference image. Without the guest write watch (`APS5_NO_WRITE_WATCH`, or no write watch on the host) the capture throws when it starts, since it could not record CPU stores. A capture is `calls.bin`, `memory.bin` and `capture.json` (commit, switches, sizes, warnings), and the recorder stops with a warning when its volume has less than 4 GiB free. A 20-frame capture in the Demon's Souls Nexus is 5.3 GiB of snapshot and 11.5 GiB of deltas before compression, 3.9 GiB on disk. -`agc_frame_replay [--no-pace] [--no-release] [--release-after-ms ] [--no-synthesize] [--strict] [--repeat ] [--cold-pipelines] [--shaders] [--shader-code ] [--submits ] [--trace-serials] [--dump
::]... [--frames ]` maps the captured ranges at their addresses and registers them with their access (a range the replaying process already maps writable, such as the driver's fixed memory, is only refilled, and a non-GPU range it uses itself is skipped and counted), repeats the registrations, then replays the calls of each captured thread on a thread of its own, started in captured order. Before a call that hands work to the GPU it waits until no more submissions are in flight than when the title made it (`--no-pace` turns this off). Titles release GPU waits from threads that never call the driver, so when the driver completes nothing for 3 ms the replay reads the waits its queues are blocked on (`AgcDriverAwaitedWaits`) and writes the next recorded CPU value of the awaited dword that satisfies the compare, else the last earlier one, else a value derived from the reference (`--no-synthesize` turns this last step off). It prints one JSON line with the frame count and times, the submissions, the pacing wait, the released waits and how many were derived, the submissions that never completed and, for captures with reference images, the share of identical pixels and the largest channel difference of the replayed display buffers (`--frames` writes both images). Effects that depend on timing (temporal antialiasing, particles) make pixels differ, so expect a visual match rather than identical frames. The replay's device has no window, but it loads the title's Vulkan pipeline cache file (`vk-pipelines-*.bin` in the shader cache directory) as the windowed device does, so pipelines the title already compiled are not compiled again (`--cold-pipelines` starts with an empty one; the driver switch is `APS5_HEADLESS_PIPELINE_CACHE`). The title replayed its pipeline journal at boot, long before the captured frames, so after the registrations the replay waits for the driver's journal replay to finish before the first pass (`journal_s` in the JSON line). The first pass still compiles every shader and pipeline the frames use that neither cache holds, so `--repeat ` replays the frames n times in one process: before each later pass it waits for the driver to go idle, clears the captured ranges, restores the snapshot and makes the reservations that were pending at the snapshot again (a token still reserved from the previous pass is kept), and the frame times then come from passes 2 to n, with each pass listed under `passes`. Later passes replay the same frames, so driver caches keyed on values that change every frame in the title (ring addresses in user data, for example) can hit entries of an earlier pass; compare such changes on the first pass with warm disk caches instead. `--shaders` replays nothing and maps no guest memory: it reads the snapshot, applies the captured writes in call order and prints one JSON line per shader registration (code address, size, type, code hash and how many earlier registrations had the same code), then a summary; `--shader-code ` also writes each distinct code as `/type-.bin`. `--submits ` replays nothing either and lists the first n submissions and flips of the captured frames with their queue and serials, the packets of each and the fields of its waits and releases. `--trace-serials` prints each submission, flip and suspend point with its call ordinal and command address as it is issued; with `APS5_TRACE_GPU` the driver prints the serial it accepts for each address and each completion, so a replay that stops completing can be followed to the call that made the submission. Replay times follow the GPU clock state: lock the clocks (`nvidia-smi -lgc`, `-lmc`) when comparing two drivers. +`agc_frame_replay [--no-pace] [--no-release] [--release-after-ms ] [--no-synthesize] [--strict] [--repeat ] [--cold-pipelines] [--shaders] [--shader-code ] [--submits ] [--trace-serials] [--dump
::]... [--frames ]` maps the captured ranges at their addresses and registers them with their access (a range the replaying process already maps writable, such as the driver's fixed memory, is only refilled, and a non-GPU range it uses itself is skipped and counted), repeats the registrations, then replays the calls of each captured thread on a thread of its own, started in captured order. Before a call that hands work to the GPU it waits until no more submissions are in flight than when the title made it (`--no-pace` turns this off). Titles release GPU waits from threads that never call the driver, so when the driver completes nothing for 3 ms the replay reads the waits its queues are blocked on (`AgcDriverAwaitedWaits`) and writes the next recorded CPU value of the awaited dword that satisfies the compare, else the last earlier one, else a value derived from the reference (`--no-synthesize` turns this last step off). It prints one JSON line with the frame count and times, the submissions, the pacing wait, the released waits and how many were derived, the submissions that never completed and, for captures with reference images, the share of identical pixels and the largest channel difference of the replayed display buffers (`--frames` writes both images). Effects that depend on timing (temporal antialiasing, particles) make pixels differ, so expect a visual match rather than identical frames. The replay's device has no window, but it loads the title's Vulkan pipeline cache file (`vk-pipelines-*.bin` in the shader cache directory) as the windowed device does, so pipelines the title already compiled are not compiled again (`--cold-pipelines` starts with an empty one and clears an inherited `APS5_HEADLESS_PIPELINE_CACHE`; that variable is the driver switch). The title replayed its pipeline journal at boot, long before the captured frames, so after the registrations the replay waits for the driver's journal replay to finish before the first pass (`journal_s` in the JSON line). The first pass still compiles every shader and pipeline the frames use that neither cache holds, so `--repeat ` replays the frames n times in one process: before each later pass it waits for the driver to go idle, clears the captured ranges, restores the snapshot and makes the reservations that were pending at the snapshot again (a token still reserved from the previous pass is kept), and the frame times then come from passes 2 to n, with each pass listed under `passes`. Later passes replay the same frames, so driver caches keyed on values that change every frame in the title (ring addresses in user data, for example) can hit entries of an earlier pass; compare such changes on the first pass with warm disk caches instead. `--shaders` replays nothing and maps no guest memory: it reads the snapshot, applies the captured writes in call order and prints one JSON line per shader registration (code address, size, type, code hash and how many earlier registrations had the same code), then a summary; `--shader-code ` also writes each distinct code as `/type-.bin`. `--submits ` also reads captured memory without mapping guest addresses, applies writes in call order, and lists the first n submissions and flips of the captured frames with their queue and serials, the packets of each and the fields of its waits and releases. `--trace-serials` prints each submission, flip and suspend point with its call ordinal and command address as it is issued; with `APS5_TRACE_GPU` the driver prints the serial it accepts for each address and each completion, so a replay that stops completing can be followed to the call that made the submission. Replay times follow the GPU clock state: lock the clocks (`nvidia-smi -lgc`, `-lmc`) when comparing two drivers. ```sh cmake --build build --target agc_frame_replay From 9f66f07d5b54ff763435dece0d8c1489c1f4a2a5 Mon Sep 17 00:00:00 2001 From: al3w0f205 Date: Tue, 27 Oct 2026 22:52:16 -0500 Subject: [PATCH 074/612] test(libScePad): cover closed special handle and invalid handle validation in scePadSetVibrationMode and scePadClose --- core/libs/tests/GuestPadVibrationMode.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/libs/tests/GuestPadVibrationMode.cpp b/core/libs/tests/GuestPadVibrationMode.cpp index 40dd9f6648..b5b1029a9c 100644 --- a/core/libs/tests/GuestPadVibrationMode.cpp +++ b/core/libs/tests/GuestPadVibrationMode.cpp @@ -34,4 +34,8 @@ int main() { Require(scePadSetVibrationMode(special, 1) == PAD_OK); Require(scePadSetVibrationMode(special, 4) == PAD_ERROR_INVALID_ARG); Require(scePadClose_nid_postfix(special) == PAD_OK); + Require(scePadSetVibrationMode(special, 1) == PAD_ERROR_INVALID_HANDLE); + Require(scePadClose_nid_postfix(-1) == PAD_ERROR_INVALID_HANDLE); + Require(scePadClose_nid_postfix(0) == PAD_ERROR_INVALID_HANDLE); + Require(scePadClose_nid_postfix(12345) == PAD_ERROR_INVALID_HANDLE); } From 2d360e23d239c5eef91db6c42edbc7e466be73d0 Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Sun, 25 Oct 2026 05:55:06 +0000 Subject: [PATCH 075/612] feat(libSceLibreSsl): implement RSA_new and RSA_free --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 2 +- core/libs/prx/libSceLibreSsl/Export.cpp | 10 -- core/libs/prx/libSceLibreSsl/include/Api.hpp | 4 +- core/libs/prx/libSceLibreSsl/src/Rsa.cpp | 120 +++++++++++++++++++ core/libs/tests/GuestLibreSslRsa.cpp | 101 ++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 1 + 6 files changed, 225 insertions(+), 13 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 0033e0157c..b70b9991b4 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -119,7 +119,7 @@ if(BUILD_TESTING) add_test_executable(guest_libressl_rsa_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslRsa.cpp) target_include_directories(guest_libressl_rsa_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_libressl_rsa_tests PRIVATE libSceLibreSsl) + target_link_libraries(guest_libressl_rsa_tests PRIVATE libSceLibreSsl libc) configure_windows_unwind(guest_libressl_rsa_tests) add_test(NAME guest_libressl_rsa COMMAND guest_libressl_rsa_tests) set_tests_properties(guest_libressl_rsa PROPERTIES TIMEOUT 30) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 6e5339c459..21e28e12f6 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -899,21 +899,11 @@ int APS5_VABI PKCS12_parse_nid_postfix(void) { return 0; } -int APS5_VABI RSA_free_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI RSA_generate_key_ex_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI RSA_new_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI RSA_padding_add_PKCS1_PSS_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 2669f58912..3a016cf3da 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -346,9 +346,9 @@ int APS5_VABI PKCS12_PBE_add_nid_postfix(void); int APS5_VABI PKCS12_free_nid_postfix(void); int APS5_VABI PKCS12_parse_nid_postfix(void); int APS5_VABI RSA_flags_nid_postfix(const LibreSslRsa* rsa); -int APS5_VABI RSA_free_nid_postfix(void); +void APS5_VABI RSA_free_nid_postfix(LibreSslRsa* rsa); int APS5_VABI RSA_generate_key_ex_nid_postfix(void); -int APS5_VABI RSA_new_nid_postfix(void); +LibreSslRsa* APS5_VABI RSA_new_nid_postfix(void); int APS5_VABI RSA_padding_add_PKCS1_PSS_nid_postfix(void); int APS5_VABI RSA_private_decrypt_nid_postfix(void); int APS5_VABI RSA_private_encrypt_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/Rsa.cpp b/core/libs/prx/libSceLibreSsl/src/Rsa.cpp index 66ff820ecc..43547f725f 100644 --- a/core/libs/prx/libSceLibreSsl/src/Rsa.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Rsa.cpp @@ -1,10 +1,12 @@ #include "prx/libSceLibreSsl/include/Api.hpp" +#include #include #include #include #include #include #include +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" struct LibreSslBignum { @@ -26,6 +28,90 @@ static_assert(offsetof(LibreSslRsa, flags) == 108); namespace { +using RsaCallback = int (APS5_VABI*)(LibreSslRsa* rsa); + +constexpr int OperationInit = 6; +constexpr int OperationFinish = 7; +constexpr int RsaFlagCachePublic = 0x0002; +constexpr int RsaFlagCachePrivate = 0x0004; +constexpr int RsaFlagNonFipsAllow = 0x0400; +constexpr int BignumFlagStaticData = 0x02; +constexpr int BignumFlagMalloced = 0x01; + +template TFunction Operation(const LibreSslRsaMethod& method, int index) { + return reinterpret_cast(method.operations[index]); +} + +void Wipe(void* data, std::size_t size) { + auto* bytes = static_cast(data); + for (std::size_t index = 0; index < size; ++index) bytes[index] = 0; +} + +void ClearFreeBignum(LibreSslBignum* number) { + if (!number) return; + if (number->d && !(number->flags & BignumFlagStaticData)) { + if (number->dmax < 0) throw std::invalid_argument("BN_clear_free: negative word capacity"); + Wipe(number->d, static_cast(number->dmax) * sizeof(number->d[0])); + free_nid_postfix(number->d); + } + const bool allocated = number->flags & BignumFlagMalloced; + Wipe(number, sizeof(LibreSslBignum)); + if (allocated) free_nid_postfix(number); +} + +int APS5_VABI DefaultPublicEncrypt(int, const unsigned char*, unsigned char*, LibreSslRsa*, int) { + NotImplemented_nid_no_patch("RSA_eay_public_encrypt"); +} + +int APS5_VABI DefaultPublicDecrypt(int, const unsigned char*, unsigned char*, LibreSslRsa*, int) { + NotImplemented_nid_no_patch("RSA_eay_public_decrypt"); +} + +int APS5_VABI DefaultPrivateEncrypt(int, const unsigned char*, unsigned char*, LibreSslRsa*, int) { + NotImplemented_nid_no_patch("RSA_eay_private_encrypt"); +} + +int APS5_VABI DefaultPrivateDecrypt(int, const unsigned char*, unsigned char*, LibreSslRsa*, int) { + NotImplemented_nid_no_patch("RSA_eay_private_decrypt"); +} + +int APS5_VABI DefaultModExp(void*, const void*, LibreSslRsa*, void*) { + NotImplemented_nid_no_patch("RSA_eay_mod_exp"); +} + +int APS5_VABI DefaultBignumModExp(void*, const void*, const void*, const void*, void*, void*) { + NotImplemented_nid_no_patch("BN_mod_exp_mont_ct"); +} + +int APS5_VABI DefaultInit(LibreSslRsa* rsa) { + rsa->flags |= RsaFlagCachePublic | RsaFlagCachePrivate; + return 1; +} + +int APS5_VABI DefaultFinish(LibreSslRsa* rsa) { + if (rsa->methodModN || rsa->methodModP || rsa->methodModQ) NotImplemented_nid_no_patch("RSA_eay_finish: freeing a Montgomery context"); + return 1; +} + +const LibreSslRsaMethod DefaultMethod { + "Eric Young's PKCS#1 RSA", + { + reinterpret_cast(DefaultPublicEncrypt), + reinterpret_cast(DefaultPublicDecrypt), + reinterpret_cast(DefaultPrivateEncrypt), + reinterpret_cast(DefaultPrivateDecrypt), + reinterpret_cast(DefaultModExp), + reinterpret_cast(DefaultBignumModExp), + reinterpret_cast(DefaultInit), + reinterpret_cast(DefaultFinish), + }, + 0, + nullptr, + nullptr, + nullptr, + nullptr, +}; + int ByteCount(const LibreSslBignum* number, const char* function) { if (!number) throw std::invalid_argument(std::string(function) + ": null modulus"); if (number->top == 0) return 0; @@ -51,4 +137,38 @@ int APS5_VABI RSA_size_nid_postfix(const LibreSslRsa* rsa) { return ByteCount(static_cast(rsa->n), __func__); } +LibreSslRsa* APS5_VABI RSA_new_nid_postfix(void) { + auto* rsa = static_cast(malloc_nid_postfix(sizeof(LibreSslRsa))); + if (!rsa) return nullptr; + rsa->meth = &DefaultMethod; + rsa->engine = nullptr; + rsa->pad = 0; + rsa->version = 0; + rsa->n = rsa->e = rsa->d = rsa->p = rsa->q = rsa->dmp1 = rsa->dmq1 = rsa->iqmp = nullptr; + rsa->references = 1; + rsa->methodModN = rsa->methodModP = rsa->methodModQ = nullptr; + rsa->blinding = rsa->mtBlinding = nullptr; + rsa->flags = rsa->meth->flags & ~RsaFlagNonFipsAllow; + rsa->exData = nullptr; + const auto init = Operation(*rsa->meth, OperationInit); + if (init && !init(rsa)) { + free_nid_postfix(rsa); + return nullptr; + } + return rsa; +} + +void APS5_VABI RSA_free_nid_postfix(LibreSslRsa* rsa) { + if (!rsa) return; + if (std::atomic_ref(rsa->references).fetch_sub(1) - 1 > 0) return; + if (!rsa->meth) throw std::invalid_argument(std::string(__func__) + ": null method"); + if (rsa->engine) NotImplemented_nid_no_patch("RSA_free: releasing an ENGINE"); + if (rsa->exData) NotImplemented_nid_no_patch("RSA_free: freeing ex_data"); + if (rsa->blinding || rsa->mtBlinding) NotImplemented_nid_no_patch("RSA_free: freeing blinding"); + if (const auto finish = Operation(*rsa->meth, OperationFinish)) finish(rsa); + for (void* number : {rsa->n, rsa->e, rsa->d, rsa->p, rsa->q, rsa->dmp1, rsa->dmq1, rsa->iqmp}) + ClearFreeBignum(static_cast(number)); + free_nid_postfix(rsa); +} + } diff --git a/core/libs/tests/GuestLibreSslRsa.cpp b/core/libs/tests/GuestLibreSslRsa.cpp index 80af7c5b46..3c8def6527 100644 --- a/core/libs/tests/GuestLibreSslRsa.cpp +++ b/core/libs/tests/GuestLibreSslRsa.cpp @@ -1,11 +1,15 @@ #include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include +#include #include #include +#include struct TestBignum { std::uint64_t* d; @@ -111,8 +115,105 @@ static void TestSize() { RequireThrows([] { RSA_size_nid_postfix(nullptr); }); } +constexpr int BignumStaticData = 0x02; +constexpr int BignumMalloced = 0x01; + +struct Finishes { + int calls; + LibreSslRsa* rsa; +}; + +static Finishes finishes; + +static int APS5_VABI RecordFinish(LibreSslRsa* rsa) { + ++finishes.calls; + finishes.rsa = rsa; + return 1; +} + +static void InitializeHeap() { + void* const api[10] {}; + ApplicationHeapRegister_nid_no_patch(api); +} + +static bool Zero(const void* data, std::size_t size) { + const auto* bytes = static_cast(data); + for (std::size_t index = 0; index < size; ++index) + if (bytes[index]) return false; + return true; +} + +static TestBignum* AllocatedBignum(std::uint64_t value) { + auto* number = static_cast(malloc_nid_postfix(sizeof(TestBignum))); + auto* words = static_cast(malloc_nid_postfix(2 * sizeof(std::uint64_t))); + Require(number != nullptr && words != nullptr); + words[0] = value; + words[1] = 0; + *number = {words, 1, 2, 0, BignumMalloced}; + return number; +} + +static void TestNew() { + auto* rsa = RSA_new_nid_postfix(); + Require(rsa != nullptr && rsa->meth != nullptr); + Require(rsa->pad == 0 && rsa->version == 0 && rsa->engine == nullptr && rsa->references == 1 && rsa->flags == 0x0006); + for (void* field : {rsa->n, rsa->e, rsa->d, rsa->p, rsa->q, rsa->dmp1, rsa->dmq1, rsa->iqmp, rsa->exData}) Require(field == nullptr); + for (void* field : {rsa->methodModN, rsa->methodModP, rsa->methodModQ, rsa->blinding, rsa->mtBlinding}) Require(field == nullptr); + const auto* method = rsa->meth; + Require(std::string(method->name) == "Eric Young's PKCS#1 RSA" && method->flags == 0 && method->appData == nullptr); + for (void* operation : method->operations) Require(operation != nullptr); + Require(method->sign == nullptr && method->verify == nullptr && method->keygen == nullptr); + Require(RSA_flags_nid_postfix(rsa) == 0); + auto* second = RSA_new_nid_postfix(); + Require(second != nullptr && second != rsa && second->meth == method); + RSA_free_nid_postfix(second); + RSA_free_nid_postfix(rsa); +} + +static void TestFree() { + RSA_free_nid_postfix(nullptr); + auto* rsa = RSA_new_nid_postfix(); + std::uint64_t words[4] {0x1234, 0x5678, 0, 0}; + TestBignum stored {words, 2, 4, 0, BignumStaticData}; + rsa->n = &stored; + rsa->e = AllocatedBignum(65537); + rsa->d = AllocatedBignum(3); + rsa->references = 2; + RSA_free_nid_postfix(rsa); + Require(rsa->references == 1 && rsa->n == &stored && stored.top == 2); + RSA_free_nid_postfix(rsa); + Require(Zero(&stored, sizeof(stored)) && words[0] == 0x1234 && words[1] == 0x5678); + + rsa = RSA_new_nid_postfix(); + LibreSslRsaMethod method = *rsa->meth; + method.operations[7] = reinterpret_cast(RecordFinish); + rsa->meth = &method; + finishes = {}; + RSA_free_nid_postfix(rsa); + Require(finishes.calls == 1 && finishes.rsa == rsa); + method.operations[7] = nullptr; + rsa = RSA_new_nid_postfix(); + rsa->meth = &method; + RSA_free_nid_postfix(rsa); + Require(finishes.calls == 1); + + int marker = 0; + for (int field = 0; field < 5; ++field) { + rsa = RSA_new_nid_postfix(); + void** slots[] {&rsa->engine, &rsa->exData, &rsa->blinding, &rsa->mtBlinding, &rsa->methodModN}; + *slots[field] = ▮ + RequireThrows([&] { RSA_free_nid_postfix(rsa); }); + } + rsa = RSA_new_nid_postfix(); + rsa->meth = nullptr; + RequireThrows([&] { RSA_free_nid_postfix(rsa); }); +} + int main() { + InitializeHeap(); TestFlags(); TestSize(); + TestNew(); + TestFree(); std::puts("libSceLibreSsl RSA tests passed"); } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index fa7c507ed7..0f44cf1041 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -20,6 +20,7 @@ - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. - [sk_num, sk_value, sk_pop, sk_pop_free](../../../core/libs/prx/libSceLibreSsl/src/Stack.cpp) (libSceLibreSsl) - the 32-byte `_STACK` layout (`num`, `data`, `sorted`, `num_alloc`, `comp`) and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/stack) `stack.h` and `stack.c`, the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214); nothing is checked on the console. `sk_pop_free` releases the element array and the stack with the guest `free`, as the module would through libc. No export of this module creates a stack yet, so only stacks the caller builds reach them. A null element array that LibreSSL would read through, and a null free function with a non-null element, throw where LibreSSL faults - [RSA_flags, RSA_size](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `rsa.h`, `rsa_crpt.c` and `bn_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a key yet, so only keys the caller builds reach them. `RSA_size` counts the modulus bytes from its top word, as `BN_num_bytes`, so a zero top word is not skipped. A null method, a null key or modulus, and a positive word count with null words, which LibreSSL dereferences, throw, as do a negative word count and a bit count that overflows `int` +- [RSA_new, RSA_free](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. They follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/rsa) `rsa_lib.c` and `rsa_eay.c`: the key comes from the guest `malloc` with the default method `"Eric Young's PKCS#1 RSA"` (method flags 0, key flags `RSA_FLAG_CACHE_PUBLIC | RSA_FLAG_CACHE_PRIVATE`) and one reference, and `RSA_free` drops a reference, runs the method's `finish` and clears and frees each `BIGNUM` as `BN_clear_free` does. The method's slots are host functions; its private-key operations, `rsa_mod_exp` and `bn_mod_exp` throw, as no export sets a private key yet. With no `ENGINE`, ex_data or blinding implemented, `RSA_free` throws for a key holding one, and for a Montgomery context, which this module never caches - [X509_STORE_CTX_get_error, X509_STORE_CTX_set_error, X509_STORE_CTX_get_error_depth, X509_STORE_CTX_get_current_cert, X509_STORE_CTX_get_chain](../../../core/libs/prx/libSceLibreSsl/src/X509StoreContext.cpp) (libSceLibreSsl) - the 240-byte `X509_STORE_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509) `x509_vfy.h` and `x509_vfy.c`, the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214); nothing is checked on the console. No export of this module creates or fills a context yet, so only contexts the caller builds reach them. A null context, which LibreSSL dereferences, throws - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console From 70605292d1de7f11e7d5e280df8f62d819ac03de Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Sun, 25 Oct 2026 06:00:25 +0000 Subject: [PATCH 076/612] feat(libSceLibreSsl): implement d2i_RSA_PUBKEY --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 - core/libs/prx/libSceLibreSsl/include/Api.hpp | 2 +- core/libs/prx/libSceLibreSsl/src/Rsa.cpp | 240 +++++++++++++++++++ core/libs/tests/GuestLibreSslRsa.cpp | 166 +++++++++++++ docs/dev/debt/libSceLibreSsl.md | 1 + 5 files changed, 408 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 21e28e12f6..134353a7e6 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1623,11 +1623,6 @@ int APS5_VABI d2i_PKCS12_fp_nid_postfix(void) { return 0; } -int APS5_VABI d2i_RSA_PUBKEY_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI d2i_X509_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 3a016cf3da..3d7ebe9188 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -505,7 +505,7 @@ const char* APS5_VABI X509_verify_cert_error_string_nid_postfix(std::int64_t err int APS5_VABI d2i_OCSP_RESPONSE_nid_postfix(void); int APS5_VABI d2i_PKCS12_bio_nid_postfix(void); int APS5_VABI d2i_PKCS12_fp_nid_postfix(void); -int APS5_VABI d2i_RSA_PUBKEY_nid_postfix(void); +LibreSslRsa* APS5_VABI d2i_RSA_PUBKEY_nid_postfix(LibreSslRsa** out, const unsigned char** input, long length); int APS5_VABI d2i_X509_nid_postfix(void); int APS5_VABI i2a_ASN1_OBJECT_nid_postfix(void); int APS5_VABI i2d_X509_PUBKEY_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/Rsa.cpp b/core/libs/prx/libSceLibreSsl/src/Rsa.cpp index 43547f725f..8487acf0ea 100644 --- a/core/libs/prx/libSceLibreSsl/src/Rsa.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Rsa.cpp @@ -4,8 +4,10 @@ #include #include #include +#include #include #include +#include #include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" @@ -59,6 +61,215 @@ void ClearFreeBignum(LibreSslBignum* number) { if (allocated) free_nid_postfix(number); } +LibreSslBignum* NewBignum(const unsigned char* bytes, std::size_t length) { + constexpr std::size_t maxWords = INT_MAX / (4 * 64); + auto* number = static_cast(malloc_nid_postfix(sizeof(LibreSslBignum))); + if (!number) return nullptr; + *number = {nullptr, 0, 0, 0, BignumFlagMalloced}; + if (!length) return number; + const std::size_t words = (length - 1) / 8 + 1; + if (words <= maxWords) number->d = static_cast(malloc_nid_postfix(words * sizeof(std::uint64_t))); + if (!number->d) { + ClearFreeBignum(number); + return nullptr; + } + number->dmax = static_cast(words); + for (std::size_t word = 0; word < words; ++word) { + std::uint64_t value = 0; + for (std::size_t byte = 0; byte < 8; ++byte) { + const std::size_t position = word * 8 + byte; + if (position < length) value |= std::uint64_t(bytes[length - 1 - position]) << (8 * byte); + } + number->d[word] = value; + } + number->top = static_cast(words); + while (number->top > 0 && !number->d[number->top - 1]) --number->top; + return number; +} + +struct Element { + int tag; + int tagClass; + bool constructed; + bool indefinite; + const unsigned char* content; + long length; + const unsigned char* end; +}; + +struct Cursor { + const unsigned char* data; + long length; +}; + +[[noreturn]] void UnsupportedEncoding(const char* function, const char* what) { + NotImplemented_nid_no_patch((std::string(function) + ": BER " + what).c_str()); +} + +bool ReadHeader(const unsigned char* start, long available, Element& element) { + if (available <= 0) return false; + const unsigned char* data = start; + long remaining = available; + const bool constructed = *data & 0x20; + const int tagClass = *data & 0xC0; + int tag = *data & 0x1F; + if (tag == 0x1F) { + ++data; + if (--remaining == 0) return false; + long value = 0; + while (*data & 0x80) { + value = value << 7 | (*data++ & 0x7F); + if (--remaining == 0) return false; + if (value > (INT_MAX >> 7)) return false; + } + value = value << 7 | (*data++ & 0x7F); + tag = static_cast(value); + if (--remaining == 0) return false; + } else { + ++data; + if (--remaining == 0) return false; + } + int lengthBytes = static_cast(remaining); + if (lengthBytes-- < 1) return false; + bool indefinite = false; + unsigned long length = 0; + if (*data == 0x80) { + indefinite = true; + ++data; + } else { + unsigned count = *data & 0x7F; + if (*data++ & 0x80) { + if (lengthBytes < static_cast(count)) return false; + while (count && !*data) { + ++data; + --count; + } + if (count > sizeof(long)) return false; + while (count-- > 0) length = length << 8 | *data++; + } else { + length = count; + } + } + if (length > static_cast(LONG_MAX)) return false; + if (indefinite && !constructed) return false; + const long header = data - start; + if (static_cast(length) > available - header) return false; + element = {tag, tagClass, constructed, indefinite, data, static_cast(length), data + length}; + return true; +} + +bool ReadTop(const unsigned char* data, long length, int tag, Element& element) { + return ReadHeader(data, length, element) && element.tag == tag && element.tagClass == 0; +} + +enum class Field { Present, Absent, Failed }; + +Field ReadField(Cursor& cursor, int tag, Element& element) { + if (!cursor.length) return Field::Absent; + if (cursor.length >= 2 && !cursor.data[0] && !cursor.data[1]) return Field::Failed; + if (!ReadHeader(cursor.data, cursor.length, element)) return Field::Failed; + if (tag >= 0 && (element.tag != tag || element.tagClass != 0)) return Field::Failed; + return Field::Present; +} + +void Advance(Cursor& cursor, const Element& element) { + cursor.length -= element.end - cursor.data; + cursor.data = element.end; +} + +bool ValidObject(const Element& element) { + if (element.length <= 0 || element.length > INT_MAX || (element.content[element.length - 1] & 0x80)) return false; + for (long index = 0; index < element.length; ++index) + if (element.content[index] == 0x80 && (!index || !(element.content[index - 1] & 0x80))) return false; + return true; +} + +bool ValidAny(const Element& element, const char* function) { + if (element.tagClass != 0) { + if (element.indefinite) UnsupportedEncoding(function, "indefinite-length parameter"); + return true; + } + if (element.tag == 16 || element.tag == 17) { + if (!element.constructed) return false; + if (element.indefinite) UnsupportedEncoding(function, "indefinite-length parameter"); + return true; + } + if (element.constructed) UnsupportedEncoding(function, "constructed parameter"); + switch (element.tag) { + case 1: return element.length == 1; + case 3: return element.length >= 1; + case 5: return element.length == 0; + case 6: return ValidObject(element); + case 28: return element.length % 4 == 0; + case 30: return element.length % 2 == 0; + default: return true; + } +} + +struct Algorithm { + const unsigned char* object; + long objectLength; + bool parameter; + bool nullParameter; +}; + +bool ReadSequence(Cursor& cursor, Element& element, const char* function) { + if (ReadField(cursor, 16, element) != Field::Present || !element.constructed) return false; + if (element.indefinite) UnsupportedEncoding(function, "indefinite-length SEQUENCE"); + return true; +} + +bool ReadPrimitive(Cursor& cursor, int tag, Element& element, const char* function) { + if (ReadField(cursor, tag, element) != Field::Present) return false; + if (element.constructed) UnsupportedEncoding(function, "constructed string"); + Advance(cursor, element); + return true; +} + +bool ReadAlgorithm(Cursor& cursor, Algorithm& algorithm, const char* function) { + Element sequence; + if (!ReadSequence(cursor, sequence, function)) return false; + Advance(cursor, sequence); + Cursor fields {sequence.content, sequence.length}; + Element object; + if (!ReadPrimitive(fields, 6, object, function) || !ValidObject(object)) return false; + algorithm = {object.content, object.length, false, false}; + if (fields.length) { + Element parameter; + if (ReadField(fields, -1, parameter) != Field::Present || !ValidAny(parameter, function)) return false; + Advance(fields, parameter); + algorithm.parameter = true; + algorithm.nullParameter = parameter.tagClass == 0 && parameter.tag == 5; + } + return !fields.length; +} + +template bool SameObject(const Algorithm& algorithm, const unsigned char (&object)[Size]) { + return algorithm.objectLength == static_cast(Size) && !std::memcmp(algorithm.object, object, Size); +} + +constexpr unsigned char RsaEncryptionObject[] {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01}; +constexpr unsigned char RsaObject[] {0x55, 0x08, 0x01, 0x01}; + +LibreSslRsa* DecodeRsaPublicKey(const unsigned char* data, long length, const char* function) { + Element sequence; + if (!ReadTop(data, length, 16, sequence) || !sequence.constructed) return nullptr; + if (sequence.indefinite) UnsupportedEncoding(function, "indefinite-length SEQUENCE"); + Cursor fields {sequence.content, sequence.length}; + Element modulus; + Element exponent; + if (!ReadPrimitive(fields, 2, modulus, function) || !ReadPrimitive(fields, 2, exponent, function) || fields.length) return nullptr; + auto* rsa = RSA_new_nid_postfix(); + if (!rsa) return nullptr; + rsa->n = NewBignum(modulus.content, static_cast(modulus.length)); + rsa->e = rsa->n ? NewBignum(exponent.content, static_cast(exponent.length)) : nullptr; + if (!rsa->e) { + RSA_free_nid_postfix(rsa); + return nullptr; + } + return rsa; +} + int APS5_VABI DefaultPublicEncrypt(int, const unsigned char*, unsigned char*, LibreSslRsa*, int) { NotImplemented_nid_no_patch("RSA_eay_public_encrypt"); } @@ -171,4 +382,33 @@ void APS5_VABI RSA_free_nid_postfix(LibreSslRsa* rsa) { free_nid_postfix(rsa); } +LibreSslRsa* APS5_VABI d2i_RSA_PUBKEY_nid_postfix(LibreSslRsa** out, const unsigned char** input, long length) { + if (!input) throw std::invalid_argument(std::string(__func__) + ": null input pointer"); + const unsigned char* data = *input; + if (length <= 0) return nullptr; + if (!data) throw std::invalid_argument(std::string(__func__) + ": null input"); + Element publicKey; + if (!ReadTop(data, length, 16, publicKey) || !publicKey.constructed) return nullptr; + if (publicKey.indefinite) UnsupportedEncoding(__func__, "indefinite-length SEQUENCE"); + Cursor fields {publicKey.content, publicKey.length}; + Algorithm algorithm; + Element bits; + if (!ReadAlgorithm(fields, algorithm, __func__) || !ReadPrimitive(fields, 3, bits, __func__) || bits.length < 1 || fields.length) return nullptr; + if (!SameObject(algorithm, RsaEncryptionObject) && !SameObject(algorithm, RsaObject)) return nullptr; + const unsigned unused = bits.content[0]; + std::vector key(bits.content + 1, bits.content + bits.length); + if (!key.empty()) { + if (unused >= 32) UnsupportedEncoding(__func__, "BIT STRING with 32 or more unused bits"); + key.back() &= static_cast(0xFF << unused); + } + auto* rsa = DecodeRsaPublicKey(key.data(), static_cast(key.size()), __func__); + if (!rsa) return nullptr; + *input = publicKey.end; + if (out) { + RSA_free_nid_postfix(*out); + *out = rsa; + } + return rsa; +} + } diff --git a/core/libs/tests/GuestLibreSslRsa.cpp b/core/libs/tests/GuestLibreSslRsa.cpp index 3c8def6527..3966b9e737 100644 --- a/core/libs/tests/GuestLibreSslRsa.cpp +++ b/core/libs/tests/GuestLibreSslRsa.cpp @@ -10,6 +10,7 @@ #include #include #include +#include struct TestBignum { std::uint64_t* d; @@ -209,11 +210,176 @@ static void TestFree() { RequireThrows([&] { RSA_free_nid_postfix(rsa); }); } +using Bytes = std::vector; + +constexpr const char* CavpModulus = + "cea80475324c1dc8347827818da58bac069d3419c614a6ea1ac6a3b510dcd72cc516954905e9fef908d45e13006adf27" + "d467a7d83c111d1a5df15ef293771aefb920032a5bb989f8e4f5e1b05093d3f130f984c07a772a3683f4dc6fb28a9681" + "5b32123ccdd13954f19d5b8b24a103e771a34c328755c65ed64e1924ffd04d30b2142cc262f6e0048fef6dbc652f2147" + "9ea1c4b1d66d28f4d46ef7185e390cbfa2e02380582f3188bb94ebbf05d31487a09aff01fcbb4cd4bfd1f0a833b38c11" + "813c84360bb53c7d4481031c40bad8713bb6b835cb08098ed15ba31ee4ba728a8c8e10f7294e1b4163b7aee57277bfd8" + "81a6f9d43e02c6925aa3a043fb7fb78d"; +constexpr const char* CavpSpki = + "30820122300d06092a864886f70d01010105000382010f003082010a0282010100cea80475324c1dc8347827818da58b" + "ac069d3419c614a6ea1ac6a3b510dcd72cc516954905e9fef908d45e13006adf27d467a7d83c111d1a5df15ef293771a" + "efb920032a5bb989f8e4f5e1b05093d3f130f984c07a772a3683f4dc6fb28a96815b32123ccdd13954f19d5b8b24a103" + "e771a34c328755c65ed64e1924ffd04d30b2142cc262f6e0048fef6dbc652f21479ea1c4b1d66d28f4d46ef7185e390c" + "bfa2e02380582f3188bb94ebbf05d31487a09aff01fcbb4cd4bfd1f0a833b38c11813c84360bb53c7d4481031c40bad8" + "713bb6b835cb08098ed15ba31ee4ba728a8c8e10f7294e1b4163b7aee57277bfd881a6f9d43e02c6925aa3a043fb7fb7" + "8d0203260445"; + +static Bytes Hex(const char* text) { + const auto nibble = [](char digit) { return static_cast(digit <= '9' ? digit - '0' : digit - 'a' + 10); }; + Bytes bytes; + for (; text[0] && text[1]; text += 2) bytes.push_back(static_cast(nibble(text[0]) << 4 | nibble(text[1]))); + return bytes; +} + +static Bytes Concat(std::initializer_list parts) { + Bytes bytes; + for (const auto& part : parts) bytes.insert(bytes.end(), part.begin(), part.end()); + return bytes; +} + +static Bytes Encode(unsigned char tag, const Bytes& content) { + Bytes bytes {tag}; + if (content.size() < 0x80) { + bytes.push_back(static_cast(content.size())); + } else if (content.size() < 0x100) { + bytes.insert(bytes.end(), {0x81, static_cast(content.size())}); + } else { + bytes.insert(bytes.end(), {0x82, static_cast(content.size() >> 8), static_cast(content.size())}); + } + return Concat({bytes, content}); +} + +static const Bytes RsaEncryption {0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01}; +static const Bytes Null {0x05, 0x00}; +static const Bytes CavpExponent {0x26, 0x04, 0x45}; + +static Bytes RsaKey(const Bytes& modulus, const Bytes& exponent) { + return Encode(0x30, Concat({Encode(0x02, modulus), Encode(0x02, exponent)})); +} + +static Bytes PublicKeyInfo(const Bytes& algorithm, const Bytes& bitString) { + return Encode(0x30, Concat({Encode(0x30, algorithm), Encode(0x03, bitString)})); +} + +static Bytes StandardKey() { + return RsaKey(Concat({{0x00}, Hex(CavpModulus)}), CavpExponent); +} + +static LibreSslRsa* Decode(const Bytes& der, long length, long* consumed = nullptr) { + const unsigned char* data = der.data(); + auto* rsa = d2i_RSA_PUBKEY_nid_postfix(nullptr, &data, length); + if (consumed) *consumed = data - der.data(); + return rsa; +} + +static const TestBignum& Number(const LibreSslRsa* rsa, bool modulus) { + return *static_cast(modulus ? rsa->n : rsa->e); +} + +static void RequireAccepted(const Bytes& der, std::uint64_t exponent, std::source_location location = std::source_location::current()) { + long consumed = 0; + auto* rsa = Decode(der, static_cast(der.size()), &consumed); + Require(rsa != nullptr && consumed == static_cast(der.size()), location); + Require(Number(rsa, false).top == (exponent ? 1 : 0) && (!exponent || Number(rsa, false).d[0] == exponent), location); + RSA_free_nid_postfix(rsa); +} + +static void RequireRefused(const Bytes& der, std::source_location location = std::source_location::current()) { + long consumed = -1; + Require(Decode(der, static_cast(der.size()), &consumed) == nullptr && consumed == 0, location); +} + +static void TestPublicKeyInfo() { + const auto published = Hex(CavpSpki); + const auto rsaKey = StandardKey(); + Require(PublicKeyInfo(Concat({RsaEncryption, Null}), Concat({{0x00}, rsaKey})) == published); + long consumed = 0; + auto* rsa = Decode(published, static_cast(published.size()), &consumed); + Require(rsa != nullptr && consumed == 294 && RSA_size_nid_postfix(rsa) == 256); + auto* fresh = RSA_new_nid_postfix(); + Require(rsa->meth == fresh->meth && rsa->flags == 0x0006 && rsa->references == 1 && rsa->engine == nullptr); + RSA_free_nid_postfix(fresh); + const auto& modulus = Number(rsa, true); + const auto& exponent = Number(rsa, false); + Require(modulus.dmax == 33 && modulus.top == 32 && modulus.neg == 0 && modulus.flags == BignumMalloced); + Require(modulus.d[0] == 0x5aa3a043fb7fb78dULL && modulus.d[31] == 0xcea80475324c1dc8ULL); + Require(exponent.dmax == 1 && exponent.top == 1 && exponent.d[0] == 0x260445 && exponent.flags == BignumMalloced); + for (void* field : {rsa->d, rsa->p, rsa->q, rsa->dmp1, rsa->dmq1, rsa->iqmp}) Require(field == nullptr); + + LibreSslRsa* previous = RSA_new_nid_postfix(); + LibreSslRsa* target = previous; + const unsigned char* data = published.data(); + auto* replaced = d2i_RSA_PUBKEY_nid_postfix(&target, &data, 294); + Require(replaced != nullptr && target == replaced && data == published.data() + 294); + RSA_free_nid_postfix(replaced); + RSA_free_nid_postfix(rsa); + + auto padded = Concat({published, Null}); + Require((rsa = Decode(padded, static_cast(padded.size()), &consumed)) != nullptr && consumed == 294); + RSA_free_nid_postfix(rsa); + Require(Decode(published, 293, &consumed) == nullptr && consumed == 0); + RequireRefused(Bytes(published.begin(), published.end() - 1)); + Require(Decode(published, 0) == nullptr && Decode(published, -1) == nullptr); + RequireThrows([&] { d2i_RSA_PUBKEY_nid_postfix(nullptr, nullptr, 294); }); + RequireThrows([&] { + const unsigned char* none = nullptr; + d2i_RSA_PUBKEY_nid_postfix(nullptr, &none, 294); + }); +} + +static void TestPublicKeyInfoVariants() { + const auto rsaKey = StandardKey(); + const auto bits = Concat({{0x00}, rsaKey}); + RequireAccepted(PublicKeyInfo(Concat({Encode(0x06, {0x55, 0x08, 0x01, 0x01}), Null}), bits), 0x260445); + RequireAccepted(PublicKeyInfo(RsaEncryption, bits), 0x260445); + for (const Bytes& parameter : {Bytes {0x04, 0x00}, Bytes {0x30, 0x00}, Bytes {0xA0, 0x00}, Bytes {0x02, 0x00}, Bytes {0x01, 0x01, 0xFF}, Bytes {0x05, 0x81, 0x00}}) + RequireAccepted(PublicKeyInfo(Concat({RsaEncryption, parameter}), bits), 0x260445); + for (const Bytes& parameter : {Bytes {0x05, 0x01, 0x00}, Bytes {0x10, 0x00}, Bytes {0x01, 0x02, 0x00, 0x00}, Bytes {0x06, 0x01, 0x80}, Bytes {0x03, 0x00}, Bytes {0x00, 0x00}}) + RequireRefused(PublicKeyInfo(Concat({RsaEncryption, parameter}), bits)); + RequireRefused(PublicKeyInfo(Concat({RsaEncryption, Null, Null}), bits)); + RequireRefused(PublicKeyInfo(Concat({Encode(0x06, {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01}), Null}), bits)); + RequireRefused(PublicKeyInfo(Concat({Encode(0x06, {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A}), Null}), bits)); + RequireRefused(PublicKeyInfo(Concat({Encode(0x06, {0x2A, 0x80, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01}), Null}), bits)); + RequireRefused(Encode(0x30, Concat({Encode(0x30, Concat({RsaEncryption, Null})), Encode(0x03, bits), Null}))); + RequireRefused(Encode(0x30, Concat({Encode(0x30, Concat({RsaEncryption, Null})), Bytes {0x00, 0x00}, Encode(0x03, bits)}))); + RequireRefused(Encode(0x30, Encode(0x30, Concat({RsaEncryption, Null})))); + RequireRefused(Encode(0x10, Concat({Encode(0x30, Concat({RsaEncryption, Null})), Encode(0x03, bits)}))); + RequireRefused(Encode(0x31, Concat({Encode(0x30, Concat({RsaEncryption, Null})), Encode(0x03, bits)}))); + const auto algorithm = Concat({RsaEncryption, Null}); + RequireAccepted(PublicKeyInfo(algorithm, Concat({{0x01}, rsaKey})), 0x260444); + RequireAccepted(PublicKeyInfo(algorithm, Concat({{0x08}, rsaKey})), 0x260400); + RequireAccepted(PublicKeyInfo(algorithm, Concat({{0x00}, rsaKey, Null})), 0x260445); + RequireAccepted(PublicKeyInfo(algorithm, Concat({{0x00}, RsaKey(Hex(CavpModulus), CavpExponent)})), 0x260445); + RequireAccepted(PublicKeyInfo(algorithm, Concat({{0x00}, RsaKey(Concat({{0x00}, Hex(CavpModulus)}), {})})), 0); + RequireAccepted(PublicKeyInfo(algorithm, Concat({{0x00}, RsaKey(Concat({{0x00}, Hex(CavpModulus)}), {0x00, 0x00, 0x01, 0x00, 0x01})})), 0x10001); + RequireRefused(PublicKeyInfo(algorithm, {})); + RequireRefused(PublicKeyInfo(algorithm, {0x00})); + RequireRefused(PublicKeyInfo(algorithm, Concat({{0x00}, Encode(0x30, Encode(0x02, Hex(CavpModulus)))}))); + RequireRefused(PublicKeyInfo(algorithm, Concat({{0x00}, Encode(0x30, Concat({Encode(0x02, Hex(CavpModulus)), Encode(0x02, CavpExponent), Encode(0x02, CavpExponent)}))}))); + RequireRefused(PublicKeyInfo(algorithm, Concat({{0x00}, Encode(0x31, Concat({Encode(0x02, Hex(CavpModulus)), Encode(0x02, CavpExponent)}))}))); + auto spki = Hex(CavpSpki); + auto longForm = Concat({{0x30, 0x83, 0x00, 0x01, 0x22}, Bytes(spki.begin() + 4, spki.end())}); + RequireAccepted(longForm, 0x260445); + auto highTag = Concat({{0x3F, 0x10}, Bytes(spki.begin() + 1, spki.end())}); + RequireAccepted(highTag, 0x260445); + RequireRefused(Concat({{0x30, 0x89, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x22}, Bytes(spki.begin() + 4, spki.end())})); + auto indefinite = Concat({{0x30, 0x80}, Bytes(spki.begin() + 4, spki.end()), {0x00, 0x00}}); + RequireThrows([&] { Decode(indefinite, static_cast(indefinite.size())); }); + auto constructedBits = Encode(0x30, Concat({Encode(0x30, algorithm), Encode(0x23, Encode(0x03, bits))})); + RequireThrows([&] { Decode(constructedBits, static_cast(constructedBits.size())); }); +} + int main() { InitializeHeap(); TestFlags(); TestSize(); TestNew(); TestFree(); + TestPublicKeyInfo(); + TestPublicKeyInfoVariants(); std::puts("libSceLibreSsl RSA tests passed"); } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 0f44cf1041..668d0f97a6 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -21,6 +21,7 @@ - [sk_num, sk_value, sk_pop, sk_pop_free](../../../core/libs/prx/libSceLibreSsl/src/Stack.cpp) (libSceLibreSsl) - the 32-byte `_STACK` layout (`num`, `data`, `sorted`, `num_alloc`, `comp`) and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/stack) `stack.h` and `stack.c`, the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214); nothing is checked on the console. `sk_pop_free` releases the element array and the stack with the guest `free`, as the module would through libc. No export of this module creates a stack yet, so only stacks the caller builds reach them. A null element array that LibreSSL would read through, and a null free function with a non-null element, throw where LibreSSL faults - [RSA_flags, RSA_size](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `rsa.h`, `rsa_crpt.c` and `bn_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a key yet, so only keys the caller builds reach them. `RSA_size` counts the modulus bytes from its top word, as `BN_num_bytes`, so a zero top word is not skipped. A null method, a null key or modulus, and a positive word count with null words, which LibreSSL dereferences, throw, as do a negative word count and a bit count that overflows `int` - [RSA_new, RSA_free](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. They follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/rsa) `rsa_lib.c` and `rsa_eay.c`: the key comes from the guest `malloc` with the default method `"Eric Young's PKCS#1 RSA"` (method flags 0, key flags `RSA_FLAG_CACHE_PUBLIC | RSA_FLAG_CACHE_PRIVATE`) and one reference, and `RSA_free` drops a reference, runs the method's `finish` and clears and frees each `BIGNUM` as `BN_clear_free` does. The method's slots are host functions; its private-key operations, `rsa_mod_exp` and `bn_mod_exp` throw, as no export sets a private key yet. With no `ENGINE`, ex_data or blinding implemented, `RSA_free` throws for a key holding one, and for a Montgomery context, which this module never caches +- [d2i_RSA_PUBKEY](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. It decodes a SubjectPublicKeyInfo as [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto)'s `x_pubkey.c`, `rsa_ameth.c`, `rsa_asn1.c` and `tasn_dec.c` do, without building the `X509_PUBKEY` and `EVP_PKEY` they use on the way: the `rsaEncryption` or `rsa` OID, any well-formed parameter, the masked BIT STRING and `n` and `e` read as unsigned `BIGNUM`s on a fresh `RSA_new` key, with the length, tag and header quirks of that decoder. The BER forms it also accepts throw: indefinite lengths, constructed strings and parameters, and a BIT STRING with 32 or more unused bits, whose mask is undefined C. A null input pointer, and a null input with a positive length, throw - [X509_STORE_CTX_get_error, X509_STORE_CTX_set_error, X509_STORE_CTX_get_error_depth, X509_STORE_CTX_get_current_cert, X509_STORE_CTX_get_chain](../../../core/libs/prx/libSceLibreSsl/src/X509StoreContext.cpp) (libSceLibreSsl) - the 240-byte `X509_STORE_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509) `x509_vfy.h` and `x509_vfy.c`, the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214); nothing is checked on the console. No export of this module creates or fills a context yet, so only contexts the caller builds reach them. A null context, which LibreSSL dereferences, throws - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console From dfccc19bdeb9455c50d933912335054dc92f978c Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Sun, 25 Oct 2026 06:10:09 +0000 Subject: [PATCH 077/612] feat(libSceLibreSsl): implement RSA_public_encrypt and RSA_public_decrypt --- core/libs/prx/libSceLibreSsl/Export.cpp | 10 - core/libs/prx/libSceLibreSsl/include/Api.hpp | 4 +- core/libs/prx/libSceLibreSsl/src/Rsa.cpp | 263 +++++++++++++++++- core/libs/tests/GuestLibreSslRsa.cpp | 275 +++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 1 + 5 files changed, 537 insertions(+), 16 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 134353a7e6..600f90bf8e 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -919,16 +919,6 @@ int APS5_VABI RSA_private_encrypt_nid_postfix(void) { return 0; } -int APS5_VABI RSA_public_decrypt_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI RSA_public_encrypt_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI RSA_sign_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 3d7ebe9188..71a4b2bb8f 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -352,8 +352,8 @@ LibreSslRsa* APS5_VABI RSA_new_nid_postfix(void); int APS5_VABI RSA_padding_add_PKCS1_PSS_nid_postfix(void); int APS5_VABI RSA_private_decrypt_nid_postfix(void); int APS5_VABI RSA_private_encrypt_nid_postfix(void); -int APS5_VABI RSA_public_decrypt_nid_postfix(void); -int APS5_VABI RSA_public_encrypt_nid_postfix(void); +int APS5_VABI RSA_public_decrypt_nid_postfix(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding); +int APS5_VABI RSA_public_encrypt_nid_postfix(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding); int APS5_VABI RSA_sign_nid_postfix(void); int APS5_VABI RSA_size_nid_postfix(const LibreSslRsa* rsa); int APS5_VABI RSA_verify_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/Rsa.cpp b/core/libs/prx/libSceLibreSsl/src/Rsa.cpp index 8487acf0ea..2abff1df43 100644 --- a/core/libs/prx/libSceLibreSsl/src/Rsa.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Rsa.cpp @@ -8,8 +8,11 @@ #include #include #include +#include "mbedtls/bignum.h" +#include "mbedtls/sha1.h" #include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/HostEntropy.hpp" struct LibreSslBignum { std::uint64_t* d; @@ -31,7 +34,12 @@ static_assert(offsetof(LibreSslRsa, flags) == 108); namespace { using RsaCallback = int (APS5_VABI*)(LibreSslRsa* rsa); +using RsaCrypt = int (APS5_VABI*)(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding); +using Words = std::vector; +constexpr int OperationPublicEncrypt = 0; +constexpr int OperationPublicDecrypt = 1; +constexpr int OperationBignumModExp = 5; constexpr int OperationInit = 6; constexpr int OperationFinish = 7; constexpr int RsaFlagCachePublic = 0x0002; @@ -39,6 +47,15 @@ constexpr int RsaFlagCachePrivate = 0x0004; constexpr int RsaFlagNonFipsAllow = 0x0400; constexpr int BignumFlagStaticData = 0x02; constexpr int BignumFlagMalloced = 0x01; +constexpr int PaddingPkcs1 = 1; +constexpr int PaddingSslv23 = 2; +constexpr int PaddingNone = 3; +constexpr int PaddingOaep = 4; +constexpr int PaddingX931 = 5; +constexpr int MaxModulusBits = 16384; +constexpr int SmallModulusBits = 3072; +constexpr int MaxPublicExponentBits = 64; +constexpr int Sha1Bytes = 20; template TFunction Operation(const LibreSslRsaMethod& method, int index) { return reinterpret_cast(method.operations[index]); @@ -270,12 +287,236 @@ LibreSslRsa* DecodeRsaPublicKey(const unsigned char* data, long length, const ch return rsa; } -int APS5_VABI DefaultPublicEncrypt(int, const unsigned char*, unsigned char*, LibreSslRsa*, int) { - NotImplemented_nid_no_patch("RSA_eay_public_encrypt"); +const LibreSslBignum& RequireNumber(const void* number, const char* function, const char* name) { + if (!number) throw std::invalid_argument(std::string(function) + ": null " + name); + const auto& value = *static_cast(number); + if (value.top < 0 || value.top > INT_MAX / 64) throw std::invalid_argument(std::string(function) + ": " + name + " word count " + std::to_string(value.top)); + if (value.top && !value.d) throw std::invalid_argument(std::string(function) + ": null " + name + " words"); + if (value.neg) NotImplemented_nid_no_patch((std::string(function) + ": negative " + name).c_str()); + return value; } -int APS5_VABI DefaultPublicDecrypt(int, const unsigned char*, unsigned char*, LibreSslRsa*, int) { - NotImplemented_nid_no_patch("RSA_eay_public_decrypt"); +int Bits(const LibreSslBignum& value) { + if (!value.top) return 0; + return (value.top - 1) * 64 + static_cast(std::bit_width(value.d[value.top - 1])); +} + +int Compare(const std::uint64_t* left, int leftTop, const std::uint64_t* right, int rightTop) { + if (leftTop != rightTop) return leftTop - rightTop; + for (int index = leftTop - 1; index >= 0; --index) + if (left[index] != right[index]) return left[index] > right[index] ? 1 : -1; + return 0; +} + +Words FromBytes(const unsigned char* bytes, std::size_t length) { + Words words((length + 7) / 8); + for (std::size_t position = 0; position < length; ++position) words[position / 8] |= std::uint64_t(bytes[length - 1 - position]) << (8 * (position % 8)); + while (!words.empty() && !words.back()) words.pop_back(); + return words; +} + +std::vector ToBytes(const Words& words) { + std::size_t length = words.size() * 8; + if (!words.empty()) length -= static_cast(std::countl_zero(words.back()) / 8); + std::vector bytes(length); + for (std::size_t position = 0; position < length; ++position) bytes[length - 1 - position] = static_cast(words[position / 8] >> (8 * (position % 8))); + return bytes; +} + +bool ModExp(const Words& base, const LibreSslBignum& exponent, const LibreSslBignum& modulus, Words& result) { + if (!modulus.top || !(modulus.d[0] & 1)) return false; + if (!Bits(exponent)) { + result = modulus.top == 1 && modulus.d[0] == 1 ? Words {} : Words {1}; + return true; + } + if (Bits(modulus) > MBEDTLS_MPI_MAX_BITS) NotImplemented_nid_no_patch("BN_mod_exp_mont_ct: a modulus over 8192 bits"); + mbedtls_mpi value, power, mod, output; + for (auto* number : {&value, &power, &mod, &output}) mbedtls_mpi_init(number); + int status = mbedtls_mpi_read_binary_le(&value, reinterpret_cast(base.data()), base.size() * 8); + if (!status) status = mbedtls_mpi_read_binary_le(&power, reinterpret_cast(exponent.d), static_cast(exponent.top) * 8); + if (!status) status = mbedtls_mpi_read_binary_le(&mod, reinterpret_cast(modulus.d), static_cast(modulus.top) * 8); + if (!status) status = mbedtls_mpi_exp_mod(&output, &value, &power, &mod, nullptr); + if (!status) { + result.assign((mbedtls_mpi_size(&output) + 7) / 8, 0); + status = mbedtls_mpi_write_binary_le(&output, reinterpret_cast(result.data()), result.size() * 8); + while (!result.empty() && !result.back()) result.pop_back(); + } + for (auto* number : {&value, &power, &mod, &output}) mbedtls_mpi_free(number); + return !status; +} + +void RequireBuffer(const void* buffer, long length, const char* function, const char* name) { + if (length > 0 && !buffer) throw std::invalid_argument(std::string(function) + ": null " + name); +} + +bool Mgf1Sha1(unsigned char* mask, long length, const unsigned char* seed, long seedLength) { + mbedtls_sha1_context context; + mbedtls_sha1_init(&context); + bool done = true; + for (std::uint32_t counter = 0; done && length > 0; ++counter) { + const unsigned char count[4] {static_cast(counter >> 24), static_cast(counter >> 16), static_cast(counter >> 8), static_cast(counter)}; + unsigned char digest[Sha1Bytes]; + done = !mbedtls_sha1_starts(&context) && !mbedtls_sha1_update(&context, seed, static_cast(seedLength)) && + !mbedtls_sha1_update(&context, count, sizeof(count)) && !mbedtls_sha1_finish(&context, digest); + const long part = length < Sha1Bytes ? length : Sha1Bytes; + std::memcpy(mask, digest, static_cast(part)); + mask += part; + length -= part; + } + mbedtls_sha1_free(&context); + return done; +} + +int AddPkcs1Type2(unsigned char* to, int size, const unsigned char* from, int length, const char* function) { + if (length > size - 11) return 0; + if (length < 0) throw std::invalid_argument(std::string(function) + ": negative input length"); + RequireBuffer(from, length, function, "input"); + to[0] = 0; + to[1] = 2; + unsigned char* padding = to + 2; + const int random = size - 3 - length; + HostEntropy_nid_no_patch(padding, static_cast(random)); + for (int index = 0; index < random; ++index, ++padding) + while (!*padding) HostEntropy_nid_no_patch(padding, 1); + *padding++ = 0; + if (length) std::memcpy(padding, from, static_cast(length)); + return 1; +} + +int AddOaep(unsigned char* to, int size, const unsigned char* from, int length, const char* function) { + const int encoded = size - 1; + if (length > encoded - 2 * Sha1Bytes - 1) return 0; + if (encoded < 2 * Sha1Bytes + 1) return 0; + if (length < 0) throw std::invalid_argument(std::string(function) + ": negative input length"); + RequireBuffer(from, length, function, "input"); + to[0] = 0; + unsigned char* seed = to + 1; + unsigned char* block = to + Sha1Bytes + 1; + static constexpr unsigned char empty[1] {}; + if (mbedtls_sha1(empty, 0, block)) return 0; + std::memset(block + Sha1Bytes, 0, static_cast(encoded - length - 2 * Sha1Bytes - 1)); + block[encoded - length - Sha1Bytes - 1] = 0x01; + if (length) std::memcpy(block + encoded - length - Sha1Bytes, from, static_cast(length)); + HostEntropy_nid_no_patch(seed, Sha1Bytes); + std::vector blockMask(static_cast(encoded - Sha1Bytes)); + if (!Mgf1Sha1(blockMask.data(), encoded - Sha1Bytes, seed, Sha1Bytes)) return 0; + for (int index = 0; index < encoded - Sha1Bytes; ++index) block[index] ^= blockMask[static_cast(index)]; + unsigned char seedMask[Sha1Bytes]; + if (!Mgf1Sha1(seedMask, Sha1Bytes, block, encoded - Sha1Bytes)) return 0; + for (int index = 0; index < Sha1Bytes; ++index) seed[index] ^= seedMask[index]; + return 1; +} + +int AddNone(unsigned char* to, int size, const unsigned char* from, int length, const char* function) { + if (length > size || length < size) return 0; + RequireBuffer(from, length, function, "input"); + std::memcpy(to, from, static_cast(length)); + return 1; +} + +int CheckPkcs1Type1(unsigned char* to, int size, const unsigned char* from, int length, int modulusLength, const char* function) { + if (modulusLength != length + 1 || !length || from[0] != 0x01) return -1; + const unsigned char* data = from + 1; + int remaining = length - 1; + int index = 0; + for (; index < remaining; ++index, ++data) { + if (*data == 0xFF) continue; + if (*data) return -1; + ++data; + break; + } + if (index == remaining || index < 8) return -1; + remaining -= index + 1; + if (remaining > size) return -1; + RequireBuffer(to, remaining, function, "output"); + if (remaining) std::memcpy(to, data, static_cast(remaining)); + return remaining; +} + +int CheckNone(unsigned char* to, int size, const unsigned char* from, int length, const char* function) { + if (length > size) return -1; + RequireBuffer(to, size, function, "output"); + std::memset(to, 0, static_cast(size - length)); + if (length) std::memcpy(to + size - length, from, static_cast(length)); + return size; +} + +int APS5_VABI DefaultBignumModExp(void*, const void*, const void*, const void*, void*, void*); + +bool UsablePublicKey(const LibreSslRsa* rsa, const char* function, const LibreSslBignum*& modulus, const LibreSslBignum*& exponent) { + if (!rsa) throw std::invalid_argument(std::string(function) + ": null key"); + modulus = &RequireNumber(rsa->n, function, "modulus"); + const int bits = Bits(*modulus); + if (bits > MaxModulusBits) return false; + exponent = &RequireNumber(rsa->e, function, "exponent"); + if (Compare(modulus->d, modulus->top, exponent->d, exponent->top) <= 0) return false; + return bits <= SmallModulusBits || Bits(*exponent) <= MaxPublicExponentBits; +} + +bool PublicOperation(const LibreSslRsa& rsa, const Words& value, const LibreSslBignum& exponent, const LibreSslBignum& modulus, Words& result, const char* function) { + if (!rsa.meth) throw std::invalid_argument(std::string(function) + ": null method"); + if (rsa.meth->operations[OperationBignumModExp] != reinterpret_cast(DefaultBignumModExp)) + NotImplemented_nid_no_patch((std::string(function) + ": a method with its own bn_mod_exp").c_str()); + return ModExp(value, exponent, modulus, result); +} + +int APS5_VABI DefaultPublicEncrypt(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding) { + constexpr const char* function = "RSA_eay_public_encrypt"; + const LibreSslBignum* modulus; + const LibreSslBignum* exponent; + if (!UsablePublicKey(rsa, function, modulus, exponent)) return -1; + const int size = (Bits(*modulus) + 7) / 8; + std::vector block(static_cast(size)); + int padded = 0; + switch (padding) { + case PaddingPkcs1: + padded = AddPkcs1Type2(block.data(), size, from, length, function); + break; + case PaddingOaep: + padded = AddOaep(block.data(), size, from, length, function); + break; + case PaddingSslv23: + NotImplemented_nid_no_patch("RSA_padding_add_SSLv23"); + case PaddingNone: + padded = AddNone(block.data(), size, from, length, function); + break; + default: + return -1; + } + if (padded <= 0) return -1; + const auto value = FromBytes(block.data(), block.size()); + if (Compare(value.data(), static_cast(value.size()), modulus->d, modulus->top) >= 0) return -1; + Words result; + if (!PublicOperation(*rsa, value, *exponent, *modulus, result, function)) return -1; + const auto bytes = ToBytes(result); + RequireBuffer(to, size, function, "output"); + std::memset(to, 0, static_cast(size) - bytes.size()); + std::memcpy(to + size - bytes.size(), bytes.data(), bytes.size()); + return size; +} + +int APS5_VABI DefaultPublicDecrypt(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding) { + constexpr const char* function = "RSA_eay_public_decrypt"; + const LibreSslBignum* modulus; + const LibreSslBignum* exponent; + if (!UsablePublicKey(rsa, function, modulus, exponent)) return -1; + const int size = (Bits(*modulus) + 7) / 8; + if (length > size || length < 0) return -1; + RequireBuffer(from, length, function, "input"); + const auto value = FromBytes(from, static_cast(length)); + if (Compare(value.data(), static_cast(value.size()), modulus->d, modulus->top) >= 0) return -1; + Words result; + if (!PublicOperation(*rsa, value, *exponent, *modulus, result, function)) return -1; + if (padding == PaddingX931) NotImplemented_nid_no_patch("RSA_padding_check_X931"); + const auto bytes = ToBytes(result); + switch (padding) { + case PaddingPkcs1: + return CheckPkcs1Type1(to, size, bytes.data(), static_cast(bytes.size()), size, function); + case PaddingNone: + return CheckNone(to, size, bytes.data(), static_cast(bytes.size()), function); + default: + return -1; + } } int APS5_VABI DefaultPrivateEncrypt(int, const unsigned char*, unsigned char*, LibreSslRsa*, int) { @@ -382,6 +623,20 @@ void APS5_VABI RSA_free_nid_postfix(LibreSslRsa* rsa) { free_nid_postfix(rsa); } +int APS5_VABI RSA_public_encrypt_nid_postfix(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding) { + if (!rsa || !rsa->meth) throw std::invalid_argument(std::string(__func__) + ": null key or method"); + const auto encrypt = Operation(*rsa->meth, OperationPublicEncrypt); + if (!encrypt) throw std::invalid_argument(std::string(__func__) + ": method without rsa_pub_enc"); + return encrypt(length, from, to, rsa, padding); +} + +int APS5_VABI RSA_public_decrypt_nid_postfix(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding) { + if (!rsa || !rsa->meth) throw std::invalid_argument(std::string(__func__) + ": null key or method"); + const auto decrypt = Operation(*rsa->meth, OperationPublicDecrypt); + if (!decrypt) throw std::invalid_argument(std::string(__func__) + ": method without rsa_pub_dec"); + return decrypt(length, from, to, rsa, padding); +} + LibreSslRsa* APS5_VABI d2i_RSA_PUBKEY_nid_postfix(LibreSslRsa** out, const unsigned char** input, long length) { if (!input) throw std::invalid_argument(std::string(__func__) + ": null input pointer"); const unsigned char* data = *input; diff --git a/core/libs/tests/GuestLibreSslRsa.cpp b/core/libs/tests/GuestLibreSslRsa.cpp index 3966b9e737..2e7653346d 100644 --- a/core/libs/tests/GuestLibreSslRsa.cpp +++ b/core/libs/tests/GuestLibreSslRsa.cpp @@ -228,6 +228,22 @@ constexpr const char* CavpSpki = "713bb6b835cb08098ed15ba31ee4ba728a8c8e10f7294e1b4163b7aee57277bfd881a6f9d43e02c6925aa3a043fb7fb7" "8d0203260445"; +constexpr const char* CavpPrivateExponent = + "0997634c477c1a039d44c810b2aaa3c7862b0b88d3708272e1e15f66fc9389709f8a11f3ea6a5af7effa2d01c189c50f" + "0d5bcbe3fa272e56cfc4a4e1d388a9dcd65df8628902556c8b6bb6a641709b5a35dd2622c73d4640bfa1359d0e76e1f2" + "19f8e33eb9bd0b59ec198eb2fccaae0346bd8b401e12e3c67cb629569c185a2e0f35a2f741644c1cca5ebb139d77a89a" + "2953fc5e30048c0e619f07c8d21d1e56b8af07193d0fdf3f49cd49f2ef3138b5138862f1470bd2d16e34a2b9e7777a6c" + "8c8d4cb94b4e8b5d616cd5393753e7b0f31cc7da559ba8e98d888914e334773baf498ad88d9631eb5fe32e53a4145bf0" + "ba548bf2b0a50c63f67b14e398a34b0d"; +constexpr const char* CavpSignature = + "6b8be97d9e518a2ede746ff4a7d91a84a1fc665b52f154a927650db6e7348c69f8c8881f7bcf9b1a6d3366eed30c3aed" + "4e93c203c43f5528a45de791895747ade9c5fa5eee81427edee02082147aa311712a6ad5fb1732e93b3d6cd23ffd46a0" + "b3caf62a8b69957cc68ae39f9993c1a779599cdda949bdaababb77f248fcfeaa44059be5459fb9b899278e929528ee13" + "0facd53372ecbc42f3e8de2998425860406440f248d817432de687112e504d734028e6c5620fa282ca07647006cf0a2f" + "f83e19a916554cc61810c2e855305db4e5cf893a6a96767365794556ff033359084d7e38a8456e68e21155b76151314a" + "29875feee09557161cbc654541e89e42"; +constexpr const char* CavpDigest = "077877895a428028f60998b985550820025a2a42c0beab27165c0802d3098150"; + static Bytes Hex(const char* text) { const auto nibble = [](char digit) { return static_cast(digit <= '9' ? digit - '0' : digit - 'a' + 10); }; Bytes bytes; @@ -373,6 +389,262 @@ static void TestPublicKeyInfoVariants() { RequireThrows([&] { Decode(constructedBits, static_cast(constructedBits.size())); }); } +constexpr int PaddingPkcs1 = 1; +constexpr int PaddingSslv23 = 2; +constexpr int PaddingNone = 3; +constexpr int PaddingOaep = 4; +constexpr int PaddingX931 = 5; + +struct StaticNumber { + std::vector words; + TestBignum number; +}; + +static void SetNumber(StaticNumber& storage, const Bytes& bigEndian) { + storage.words.assign(bigEndian.size() / 8 + 1, 0); + for (std::size_t position = 0; position < bigEndian.size(); ++position) + storage.words[position / 8] |= std::uint64_t(bigEndian[bigEndian.size() - 1 - position]) << (8 * (position % 8)); + int top = static_cast(storage.words.size()); + while (top > 0 && !storage.words[static_cast(top) - 1]) --top; + storage.number = {storage.words.data(), top, static_cast(storage.words.size()), 0, BignumStaticData}; +} + +struct TestKey { + StaticNumber modulus; + StaticNumber exponent; + LibreSslRsa* rsa; + + TestKey(const Bytes& n, const Bytes& e) : rsa(RSA_new_nid_postfix()) { + SetNumber(modulus, n); + SetNumber(exponent, e); + rsa->n = &modulus.number; + rsa->e = &exponent.number; + } + TestKey(const TestKey&) = delete; + ~TestKey() { RSA_free_nid_postfix(rsa); } +}; + +static Bytes Pattern(std::size_t length, unsigned seed) { + Bytes bytes(length); + for (std::size_t index = 0; index < length; ++index) bytes[index] = static_cast(seed + 29 * index); + return bytes; +} + +static Bytes Sha1(const Bytes& data) { + Sha1Context context {}; + unsigned char digest[20]; + Require(SHA1_Init_nid_postfix(&context) == 1 && SHA1_Update_nid_postfix(&context, data.data(), data.size()) == 1); + Require(SHA1_Final_nid_postfix(digest, &context) == 1); + return Bytes(digest, digest + 20); +} + +static Bytes Mgf1(const Bytes& seed, std::size_t length) { + Bytes mask; + for (std::uint32_t counter = 0; mask.size() < length; ++counter) { + auto input = seed; + for (int shift = 24; shift >= 0; shift -= 8) input.push_back(static_cast(counter >> shift)); + const auto digest = Sha1(input); + mask.insert(mask.end(), digest.begin(), digest.end()); + } + mask.resize(length); + return mask; +} + +static Bytes Recover(const Bytes& encrypted) { + TestKey privateKey(Hex(CavpModulus), Hex(CavpPrivateExponent)); + Bytes block(256); + Require(RSA_public_decrypt_nid_postfix(256, encrypted.data(), block.data(), privateKey.rsa, PaddingNone) == 256); + return block; +} + +static bool Pkcs1Block(const Bytes& block, const Bytes& message) { + if (block[0] != 0 || block[1] != 2) return false; + const std::size_t separator = block.size() - message.size() - 1; + for (std::size_t index = 2; index < separator; ++index) + if (!block[index]) return false; + return block[separator] == 0 && Bytes(block.begin() + static_cast(separator) + 1, block.end()) == message; +} + +static bool OaepBlock(const Bytes& block, const Bytes& message) { + if (block[0]) return false; + const Bytes maskedSeed(block.begin() + 1, block.begin() + 21); + const Bytes maskedData(block.begin() + 21, block.end()); + auto seed = maskedSeed; + const auto seedMask = Mgf1(maskedData, 20); + for (std::size_t index = 0; index < 20; ++index) seed[index] ^= seedMask[index]; + auto data = maskedData; + const auto dataMask = Mgf1(seed, data.size()); + for (std::size_t index = 0; index < data.size(); ++index) data[index] ^= dataMask[index]; + if (Bytes(data.begin(), data.begin() + 20) != Sha1({})) return false; + const std::size_t one = data.size() - message.size() - 1; + for (std::size_t index = 20; index < one; ++index) + if (data[index]) return false; + return data[one] == 1 && Bytes(data.begin() + static_cast(one) + 1, data.end()) == message; +} + +static void TestPublicDecrypt() { + const auto spki = Hex(CavpSpki); + auto* rsa = Decode(spki, static_cast(spki.size())); + const auto signature = Hex(CavpSignature); + const auto digestInfo = Concat({Hex("3031300d060960864801650304020105000420"), Hex(CavpDigest)}); + Bytes out(300, 0xEE); + Require(RSA_public_decrypt_nid_postfix(256, signature.data(), out.data(), rsa, PaddingPkcs1) == 51); + Require(Bytes(out.begin(), out.begin() + 51) == digestInfo && out[51] == 0xEE); + Require(RSA_public_decrypt_nid_postfix(256, signature.data(), out.data(), rsa, PaddingNone) == 256); + Require(out[0] == 0 && out[1] == 1 && out[2] == 0xFF && out[204] == 0 && Bytes(out.begin() + 205, out.begin() + 256) == digestInfo); + auto tampered = signature; + tampered[255] ^= 1; + Require(RSA_public_decrypt_nid_postfix(256, tampered.data(), out.data(), rsa, PaddingPkcs1) == -1); + Require(RSA_public_decrypt_nid_postfix(256, tampered.data(), out.data(), rsa, PaddingNone) == 256); + Require(RSA_public_decrypt_nid_postfix(257, Concat({signature, {0}}).data(), out.data(), rsa, PaddingPkcs1) == -1); + Require(RSA_public_decrypt_nid_postfix(-1, signature.data(), out.data(), rsa, PaddingPkcs1) == -1); + const Bytes large(256, 0xFF); + Require(RSA_public_decrypt_nid_postfix(256, large.data(), out.data(), rsa, PaddingNone) == -1); + Require(RSA_public_decrypt_nid_postfix(256, Hex(CavpModulus).data(), out.data(), rsa, PaddingNone) == -1); + std::memset(out.data(), 0xEE, out.size()); + Require(RSA_public_decrypt_nid_postfix(0, nullptr, out.data(), rsa, PaddingNone) == 256); + Require(Zero(out.data(), 256) && out[256] == 0xEE); + Require(RSA_public_decrypt_nid_postfix(0, nullptr, out.data(), rsa, PaddingPkcs1) == -1); + for (int padding : {0, PaddingSslv23, PaddingOaep, 6, 7}) Require(RSA_public_decrypt_nid_postfix(256, signature.data(), out.data(), rsa, padding) == -1); + RequireThrows([&] { RSA_public_decrypt_nid_postfix(256, signature.data(), out.data(), rsa, PaddingX931); }); + RequireThrows([&] { RSA_public_decrypt_nid_postfix(256, nullptr, out.data(), rsa, PaddingPkcs1); }); + RequireThrows([&] { RSA_public_decrypt_nid_postfix(256, signature.data(), nullptr, rsa, PaddingPkcs1); }); + RSA_free_nid_postfix(rsa); +} + +static void TestPublicEncrypt() { + const auto spki = Hex(CavpSpki); + auto* rsa = Decode(spki, static_cast(spki.size())); + const auto message = Pattern(32, 5); + Bytes first(256); + Bytes second(256); + for (int padding : {PaddingPkcs1, PaddingOaep}) { + Require(RSA_public_encrypt_nid_postfix(32, message.data(), first.data(), rsa, padding) == 256); + Require(RSA_public_encrypt_nid_postfix(32, message.data(), second.data(), rsa, padding) == 256); + Require(first != second); + for (const auto* encrypted : {&first, &second}) { + const auto block = Recover(*encrypted); + Require(padding == PaddingPkcs1 ? Pkcs1Block(block, message) : OaepBlock(block, message)); + } + } + for (auto [padding, longest] : {std::pair {PaddingPkcs1, 245}, std::pair {PaddingOaep, 214}}) { + const auto longMessage = Pattern(static_cast(longest) + 1, 9); + Require(RSA_public_encrypt_nid_postfix(longest, longMessage.data(), first.data(), rsa, padding) == 256); + const Bytes accepted(longMessage.begin(), longMessage.begin() + longest); + Require(padding == PaddingPkcs1 ? Pkcs1Block(Recover(first), accepted) : OaepBlock(Recover(first), accepted)); + Require(RSA_public_encrypt_nid_postfix(longest + 1, longMessage.data(), first.data(), rsa, padding) == -1); + Require(RSA_public_encrypt_nid_postfix(0, nullptr, first.data(), rsa, padding) == 256); + Require(padding == PaddingPkcs1 ? Pkcs1Block(Recover(first), {}) : OaepBlock(Recover(first), {})); + RequireThrows([&] { RSA_public_encrypt_nid_postfix(-1, message.data(), first.data(), rsa, padding); }); + } + auto raw = Concat({{0x00}, Pattern(255, 3)}); + Require(RSA_public_encrypt_nid_postfix(256, raw.data(), first.data(), rsa, PaddingNone) == 256 && Recover(first) == raw); + Require(RSA_public_encrypt_nid_postfix(255, raw.data(), first.data(), rsa, PaddingNone) == -1); + Require(RSA_public_encrypt_nid_postfix(257, Concat({raw, {0}}).data(), first.data(), rsa, PaddingNone) == -1); + Require(RSA_public_encrypt_nid_postfix(-1, raw.data(), first.data(), rsa, PaddingNone) == -1); + Require(RSA_public_encrypt_nid_postfix(256, Hex(CavpModulus).data(), first.data(), rsa, PaddingNone) == -1); + for (int padding : {0, PaddingX931, 6, 7}) Require(RSA_public_encrypt_nid_postfix(32, message.data(), first.data(), rsa, padding) == -1); + RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, message.data(), first.data(), rsa, PaddingSslv23); }); + RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, message.data(), nullptr, rsa, PaddingPkcs1); }); + RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, nullptr, first.data(), rsa, PaddingPkcs1); }); + RSA_free_nid_postfix(rsa); +} + +struct Crypts { + int calls; + int length; + const unsigned char* from; + unsigned char* to; + LibreSslRsa* rsa; + int padding; +}; + +static Crypts crypts; + +static int APS5_VABI RecordCrypt(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding) { + crypts = {crypts.calls + 1, length, from, to, rsa, padding}; + return 77; +} + +static int APS5_VABI OtherModExp(void*, const void*, const void*, const void*, void*, void*) { + return 1; +} + +static void TestPublicKeyChecks() { + const auto modulus = Hex(CavpModulus); + const auto message = Pattern(256, 1); + Bytes out(4096); + { + TestKey key(modulus, {}); + Require(RSA_public_encrypt_nid_postfix(256, Concat({{0}, Pattern(255, 1)}).data(), out.data(), key.rsa, PaddingNone) == 256); + Require(Zero(out.data(), 255) && out[255] == 1); + } + { + TestKey key(modulus, modulus); + Require(RSA_public_encrypt_nid_postfix(32, message.data(), out.data(), key.rsa, PaddingPkcs1) == -1); + Require(RSA_public_decrypt_nid_postfix(256, message.data(), out.data(), key.rsa, PaddingNone) == -1); + } + { + auto even = modulus; + even.back() &= 0xFE; + TestKey key(even, CavpExponent); + Require(RSA_public_encrypt_nid_postfix(32, message.data(), out.data(), key.rsa, PaddingPkcs1) == -1); + Require(RSA_public_decrypt_nid_postfix(255, message.data(), out.data(), key.rsa, PaddingNone) == -1); + } + Bytes large(385, 0); + large[0] = 0x01; + large.back() = 0x03; + Bytes wideExponent(9, 0); + wideExponent[0] = 0x01; + wideExponent.back() = 0x01; + { + TestKey key(large, wideExponent); + Require(RSA_public_encrypt_nid_postfix(385, Bytes(385, 0).data(), out.data(), key.rsa, PaddingNone) == -1); + Require(RSA_public_decrypt_nid_postfix(1, message.data(), out.data(), key.rsa, PaddingNone) == -1); + } + { + TestKey key(large, Bytes(wideExponent.begin() + 1, wideExponent.end())); + Require(RSA_public_decrypt_nid_postfix(1, message.data(), out.data(), key.rsa, PaddingNone) == 385); + } + Bytes huge(2049, 0); + huge[0] = 0x01; + huge.back() = 0x01; + { + TestKey key(huge, {0x03}); + Require(RSA_public_decrypt_nid_postfix(1, message.data(), out.data(), key.rsa, PaddingNone) == -1); + } + { + Bytes wide(1025, 0); + wide[0] = 0x01; + wide.back() = 0x01; + TestKey key(wide, {0x03}); + RequireThrows([&] { RSA_public_decrypt_nid_postfix(1, message.data(), out.data(), key.rsa, PaddingNone); }); + } + { + TestKey key(modulus, CavpExponent); + const auto* defaultMethod = key.rsa->meth; + LibreSslRsaMethod method = *defaultMethod; + method.operations[0] = reinterpret_cast(RecordCrypt); + method.operations[1] = reinterpret_cast(RecordCrypt); + key.rsa->meth = &method; + crypts = {}; + Require(RSA_public_encrypt_nid_postfix(5, message.data(), out.data(), key.rsa, 9) == 77); + Require(crypts.calls == 1 && crypts.length == 5 && crypts.from == message.data() && crypts.to == out.data() && crypts.rsa == key.rsa && crypts.padding == 9); + Require(RSA_public_decrypt_nid_postfix(6, message.data(), out.data(), key.rsa, 8) == 77 && crypts.calls == 2 && crypts.length == 6); + method = *defaultMethod; + method.operations[5] = reinterpret_cast(OtherModExp); + RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, message.data(), out.data(), key.rsa, PaddingPkcs1); }); + method.operations[0] = nullptr; + RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, message.data(), out.data(), key.rsa, PaddingPkcs1); }); + key.rsa->meth = nullptr; + RequireThrows([&] { RSA_public_decrypt_nid_postfix(32, message.data(), out.data(), key.rsa, PaddingPkcs1); }); + key.rsa->meth = defaultMethod; + key.rsa->e = nullptr; + RequireThrows([&] { RSA_public_decrypt_nid_postfix(32, message.data(), out.data(), key.rsa, PaddingPkcs1); }); + } + RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, message.data(), out.data(), nullptr, PaddingPkcs1); }); +} + int main() { InitializeHeap(); TestFlags(); @@ -381,5 +653,8 @@ int main() { TestFree(); TestPublicKeyInfo(); TestPublicKeyInfoVariants(); + TestPublicDecrypt(); + TestPublicEncrypt(); + TestPublicKeyChecks(); std::puts("libSceLibreSsl RSA tests passed"); } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 668d0f97a6..f8976c642a 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -22,6 +22,7 @@ - [RSA_flags, RSA_size](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `rsa.h`, `rsa_crpt.c` and `bn_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a key yet, so only keys the caller builds reach them. `RSA_size` counts the modulus bytes from its top word, as `BN_num_bytes`, so a zero top word is not skipped. A null method, a null key or modulus, and a positive word count with null words, which LibreSSL dereferences, throw, as do a negative word count and a bit count that overflows `int` - [RSA_new, RSA_free](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. They follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/rsa) `rsa_lib.c` and `rsa_eay.c`: the key comes from the guest `malloc` with the default method `"Eric Young's PKCS#1 RSA"` (method flags 0, key flags `RSA_FLAG_CACHE_PUBLIC | RSA_FLAG_CACHE_PRIVATE`) and one reference, and `RSA_free` drops a reference, runs the method's `finish` and clears and frees each `BIGNUM` as `BN_clear_free` does. The method's slots are host functions; its private-key operations, `rsa_mod_exp` and `bn_mod_exp` throw, as no export sets a private key yet. With no `ENGINE`, ex_data or blinding implemented, `RSA_free` throws for a key holding one, and for a Montgomery context, which this module never caches - [d2i_RSA_PUBKEY](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. It decodes a SubjectPublicKeyInfo as [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto)'s `x_pubkey.c`, `rsa_ameth.c`, `rsa_asn1.c` and `tasn_dec.c` do, without building the `X509_PUBKEY` and `EVP_PKEY` they use on the way: the `rsaEncryption` or `rsa` OID, any well-formed parameter, the masked BIT STRING and `n` and `e` read as unsigned `BIGNUM`s on a fresh `RSA_new` key, with the length, tag and header quirks of that decoder. The BER forms it also accepts throw: indefinite lengths, constructed strings and parameters, and a BIT STRING with 32 or more unused bits, whose mask is undefined C. A null input pointer, and a null input with a positive length, throw +- [RSA_public_encrypt, RSA_public_decrypt](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. They call the key method's slots; the default ones follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/rsa) `rsa_eay.c`, `rsa_pk1.c`, `rsa_oaep.c` and `rsa_none.c`: the modulus and exponent limits, encryption with PKCS#1 v1.5, OAEP (SHA-1, empty label) or no padding, decryption checking PKCS#1 v1.5 type 1 or no padding, and -1 for other paddings, without the error codes LibreSSL queues. Random padding comes from host entropy and the arithmetic from mbedtls, whose 8192-bit limit makes a larger modulus throw; the Montgomery context is never cached. `RSA_SSLV23_PADDING` encryption and `RSA_X931_PADDING` decryption throw, as do a method with its own `bn_mod_exp`, a negative `BIGNUM` and a null key, method or slot - [X509_STORE_CTX_get_error, X509_STORE_CTX_set_error, X509_STORE_CTX_get_error_depth, X509_STORE_CTX_get_current_cert, X509_STORE_CTX_get_chain](../../../core/libs/prx/libSceLibreSsl/src/X509StoreContext.cpp) (libSceLibreSsl) - the 240-byte `X509_STORE_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509) `x509_vfy.h` and `x509_vfy.c`, the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214); nothing is checked on the console. No export of this module creates or fills a context yet, so only contexts the caller builds reach them. A null context, which LibreSSL dereferences, throws - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console From 8cd8de977dfb5ba156079c982afa13fcb7cda9b2 Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Sun, 25 Oct 2026 06:18:48 +0000 Subject: [PATCH 078/612] feat(libSceLibreSsl): implement RSA_verify --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 - core/libs/prx/libSceLibreSsl/include/Api.hpp | 2 +- core/libs/prx/libSceLibreSsl/src/Rsa.cpp | 72 ++++++++++++ core/libs/tests/GuestLibreSslRsa.cpp | 117 +++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 1 + 5 files changed, 191 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 600f90bf8e..9154084eaa 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -924,11 +924,6 @@ int APS5_VABI RSA_sign_nid_postfix(void) { return 0; } -int APS5_VABI RSA_verify_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI RSA_verify_PKCS1_PSS_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 71a4b2bb8f..24f64f4ea6 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -356,7 +356,7 @@ int APS5_VABI RSA_public_decrypt_nid_postfix(int length, const unsigned char* fr int APS5_VABI RSA_public_encrypt_nid_postfix(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding); int APS5_VABI RSA_sign_nid_postfix(void); int APS5_VABI RSA_size_nid_postfix(const LibreSslRsa* rsa); -int APS5_VABI RSA_verify_nid_postfix(void); +int APS5_VABI RSA_verify_nid_postfix(int type, const unsigned char* digest, unsigned int digestLength, const unsigned char* signature, unsigned int signatureLength, LibreSslRsa* rsa); int APS5_VABI RSA_verify_PKCS1_PSS_nid_postfix(void); int APS5_VABI SSL_CIPHER_get_bits_nid_postfix(const void* cipher, std::int32_t* algorithmBits); const char* APS5_VABI SSL_CIPHER_get_name_nid_postfix(const void* cipher); diff --git a/core/libs/prx/libSceLibreSsl/src/Rsa.cpp b/core/libs/prx/libSceLibreSsl/src/Rsa.cpp index 2abff1df43..f9e1d45bec 100644 --- a/core/libs/prx/libSceLibreSsl/src/Rsa.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Rsa.cpp @@ -35,6 +35,7 @@ namespace { using RsaCallback = int (APS5_VABI*)(LibreSslRsa* rsa); using RsaCrypt = int (APS5_VABI*)(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding); +using RsaVerify = int (APS5_VABI*)(int type, const unsigned char* digest, unsigned int digestLength, const unsigned char* signature, unsigned int signatureLength, const LibreSslRsa* rsa); using Words = std::vector; constexpr int OperationPublicEncrypt = 0; @@ -44,6 +45,7 @@ constexpr int OperationInit = 6; constexpr int OperationFinish = 7; constexpr int RsaFlagCachePublic = 0x0002; constexpr int RsaFlagCachePrivate = 0x0004; +constexpr int RsaFlagSignVerify = 0x0040; constexpr int RsaFlagNonFipsAllow = 0x0400; constexpr int BignumFlagStaticData = 0x02; constexpr int BignumFlagMalloced = 0x01; @@ -56,6 +58,9 @@ constexpr int MaxModulusBits = 16384; constexpr int SmallModulusBits = 3072; constexpr int MaxPublicExponentBits = 64; constexpr int Sha1Bytes = 20; +constexpr int DigestMd5Sha1 = 114; +constexpr int ObjectTypeCount = 956; +constexpr unsigned int Md5Sha1Bytes = 36; template TFunction Operation(const LibreSslRsaMethod& method, int index) { return reinterpret_cast(method.operations[index]); @@ -519,6 +524,56 @@ int APS5_VABI DefaultPublicDecrypt(int length, const unsigned char* from, unsign } } +struct DigestObject { + int type; + long length; + unsigned char object[9]; +}; + +constexpr DigestObject DigestObjects[] { + {3, 8, {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x02}}, + {4, 8, {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05}}, + {41, 5, {0x2B, 0x0E, 0x03, 0x02, 0x12}}, + {64, 5, {0x2B, 0x0E, 0x03, 0x02, 0x1A}}, + {95, 4, {0x55, 0x08, 0x03, 0x65}}, + {117, 5, {0x2B, 0x24, 0x03, 0x02, 0x01}}, + {257, 8, {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x04}}, + {672, 9, {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01}}, + {673, 9, {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02}}, + {674, 9, {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03}}, + {675, 9, {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04}}, + {804, 6, {0x28, 0xCF, 0x06, 0x03, 0x00, 0x37}}, + {809, 6, {0x2A, 0x85, 0x03, 0x02, 0x02, 0x09}}, + {941, 8, {0x2A, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x02}}, + {942, 8, {0x2A, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x03}}, +}; + +bool SameDigestType(const Algorithm& algorithm, int type, const char* function) { + if (type < 0 || type >= ObjectTypeCount) return false; + for (const auto& digest : DigestObjects) { + const bool same = algorithm.objectLength == digest.length && !std::memcmp(algorithm.object, digest.object, static_cast(digest.length)); + if (digest.type == type) return same; + if (same) return false; + } + NotImplemented_nid_no_patch((std::string(function) + ": digest type " + std::to_string(type) + " with an object outside the digest table").c_str()); +} + +int VerifyDigestInfo(int type, const unsigned char* digest, unsigned int digestLength, const unsigned char* data, long length, const char* function) { + Element sequence; + if (!ReadTop(data, length, 16, sequence) || !sequence.constructed) return 0; + if (sequence.indefinite) UnsupportedEncoding(function, "indefinite-length SEQUENCE"); + Cursor fields {sequence.content, sequence.length}; + Algorithm algorithm; + Element hash; + if (!ReadAlgorithm(fields, algorithm, function) || !ReadPrimitive(fields, 4, hash, function) || fields.length) return 0; + if (sequence.end != data + length) return 0; + if (algorithm.parameter && !algorithm.nullParameter) return 0; + if (!SameDigestType(algorithm, type, function)) return 0; + if (static_cast(hash.length) != digestLength) return 0; + RequireBuffer(digest, digestLength, function, "digest"); + return !digestLength || !std::memcmp(digest, hash.content, digestLength); +} + int APS5_VABI DefaultPrivateEncrypt(int, const unsigned char*, unsigned char*, LibreSslRsa*, int) { NotImplemented_nid_no_patch("RSA_eay_private_encrypt"); } @@ -637,6 +692,23 @@ int APS5_VABI RSA_public_decrypt_nid_postfix(int length, const unsigned char* fr return decrypt(length, from, to, rsa, padding); } +int APS5_VABI RSA_verify_nid_postfix(int type, const unsigned char* digest, unsigned int digestLength, const unsigned char* signature, unsigned int signatureLength, LibreSslRsa* rsa) { + if (!rsa) throw std::invalid_argument(std::string(__func__) + ": null key"); + if (rsa->flags & RsaFlagSignVerify) { + if (!rsa->meth) throw std::invalid_argument(std::string(__func__) + ": null method"); + if (rsa->meth->verify) return reinterpret_cast(rsa->meth->verify)(type, digest, digestLength, signature, signatureLength, rsa); + } + if (signatureLength != static_cast(RSA_size_nid_postfix(rsa))) return 0; + if (type == DigestMd5Sha1 && digestLength != Md5Sha1Bytes) return 0; + std::vector block(signatureLength); + const int length = RSA_public_decrypt_nid_postfix(static_cast(signatureLength), signature, block.data(), rsa, PaddingPkcs1); + if (length <= 0) return 0; + if (type != DigestMd5Sha1) return VerifyDigestInfo(type, digest, digestLength, block.data(), length, __func__); + if (length != static_cast(Md5Sha1Bytes)) return 0; + RequireBuffer(digest, Md5Sha1Bytes, __func__, "digest"); + return !std::memcmp(block.data(), digest, Md5Sha1Bytes); +} + LibreSslRsa* APS5_VABI d2i_RSA_PUBKEY_nid_postfix(LibreSslRsa** out, const unsigned char** input, long length) { if (!input) throw std::invalid_argument(std::string(__func__) + ": null input pointer"); const unsigned char* data = *input; diff --git a/core/libs/tests/GuestLibreSslRsa.cpp b/core/libs/tests/GuestLibreSslRsa.cpp index 2e7653346d..7bc2b87faa 100644 --- a/core/libs/tests/GuestLibreSslRsa.cpp +++ b/core/libs/tests/GuestLibreSslRsa.cpp @@ -645,6 +645,122 @@ static void TestPublicKeyChecks() { RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, message.data(), out.data(), nullptr, PaddingPkcs1); }); } +constexpr int DigestSha1 = 64; +constexpr int DigestMd5Sha1 = 114; +constexpr int DigestSha256 = 672; +constexpr int RsaFlagSignVerify = 0x0040; + +static Bytes Sign(const Bytes& encoded) { + return Recover(Concat({{0x00, 0x01}, Bytes(253 - encoded.size(), 0xFF), {0x00}, encoded})); +} + +static Bytes DigestInfo(const Bytes& object, const Bytes& parameter, const Bytes& digest) { + return Encode(0x30, Concat({Encode(0x30, Concat({Encode(0x06, object), parameter})), Encode(0x04, digest)})); +} + +static int Verify(int type, const Bytes& digest, const Bytes& signature, LibreSslRsa* rsa) { + return RSA_verify_nid_postfix(type, digest.data(), static_cast(digest.size()), signature.data(), static_cast(signature.size()), rsa); +} + +struct Verifies { + int calls; + int type; + const unsigned char* digest; + unsigned digestLength; + const unsigned char* signature; + unsigned signatureLength; + const LibreSslRsa* rsa; +}; + +static Verifies verifies; + +static int APS5_VABI RecordVerify(int type, const unsigned char* digest, unsigned int digestLength, const unsigned char* signature, unsigned int signatureLength, const LibreSslRsa* rsa) { + verifies = {verifies.calls + 1, type, digest, digestLength, signature, signatureLength, rsa}; + return 55; +} + +static void TestVerify() { + const auto spki = Hex(CavpSpki); + auto* rsa = Decode(spki, static_cast(spki.size())); + const auto digest = Hex(CavpDigest); + const auto signature = Hex(CavpSignature); + Require(Verify(DigestSha256, digest, signature, rsa) == 1); + auto tampered = signature; + tampered[100] ^= 0x10; + Require(Verify(DigestSha256, digest, tampered, rsa) == 0); + auto wrong = digest; + wrong[31] ^= 1; + Require(Verify(DigestSha256, wrong, signature, rsa) == 0); + Require(Verify(DigestSha256, Bytes(digest.begin(), digest.end() - 1), signature, rsa) == 0); + Require(Verify(DigestSha256, Concat({digest, {0}}), signature, rsa) == 0); + for (int type : {DigestSha1, 0, 1, 673, 675, -1, 956, DigestMd5Sha1}) Require(Verify(type, digest, signature, rsa) == 0); + Require(Verify(DigestSha256, digest, Bytes(signature.begin() + 1, signature.end()), rsa) == 0); + Require(Verify(DigestSha256, digest, Concat({{0}, signature}), rsa) == 0); + const auto ssl = Pattern(36, 7); + const auto sslSignature = Sign(ssl); + Require(Verify(DigestMd5Sha1, ssl, sslSignature, rsa) == 1); + Require(Verify(DigestMd5Sha1, Bytes(ssl.begin(), ssl.end() - 1), sslSignature, rsa) == 0); + auto otherSsl = ssl; + otherSsl[35] ^= 1; + Require(Verify(DigestMd5Sha1, otherSsl, sslSignature, rsa) == 0); + Require(Verify(DigestMd5Sha1, ssl, Sign(Bytes(ssl.begin(), ssl.end() - 1)), rsa) == 0); + Require(Verify(DigestSha256, ssl, sslSignature, rsa) == 0); + RequireThrows([&] { RSA_verify_nid_postfix(DigestMd5Sha1, nullptr, 36, sslSignature.data(), 256, rsa); }); + const auto sha256 = Hex("608648016503040201"); + Require(Verify(DigestSha256, digest, Sign(DigestInfo(sha256, Null, digest)), rsa) == 1); + Require(Verify(DigestSha256, digest, Sign(DigestInfo(sha256, {}, digest)), rsa) == 1); + Require(Verify(DigestSha256, digest, Sign(DigestInfo(sha256, {0x05, 0x81, 0x00}, digest)), rsa) == 1); + for (const auto& parameter : {Bytes {0x04, 0x00}, Bytes {0x05, 0x01, 0x00}, Bytes {0x30, 0x00}, Bytes {0x80, 0x00}, Bytes {0x01, 0x01, 0x00}, Concat({Null, Null})}) + Require(Verify(DigestSha256, digest, Sign(DigestInfo(sha256, parameter, digest)), rsa) == 0); + const auto algorithm = Encode(0x30, Concat({Encode(0x06, sha256), Null})); + Require(Verify(DigestSha256, digest, Sign(Concat({DigestInfo(sha256, Null, digest), {0x00}})), rsa) == 0); + Require(Verify(DigestSha256, digest, Sign(Encode(0x30, Concat({algorithm, Encode(0x04, digest), Null}))), rsa) == 0); + Require(Verify(DigestSha256, digest, Sign(Encode(0x30, algorithm)), rsa) == 0); + Require(Verify(DigestSha256, digest, Sign(Encode(0x30, Concat({algorithm, Encode(0x03, digest)}))), rsa) == 0); + Require(Verify(DigestSha256, digest, Sign(Encode(0x31, Concat({algorithm, Encode(0x04, digest)}))), rsa) == 0); + Require(Verify(DigestSha256, digest, Sign(Encode(0x04, digest)), rsa) == 0); + Require(Verify(0, digest, Sign(DigestInfo({}, Null, digest)), rsa) == 0); + Require(Verify(0, digest, Sign(DigestInfo({0x2A, 0x80}, Null, digest)), rsa) == 0); + RequireThrows([&] { Verify(DigestSha256, digest, Sign(Encode(0x30, Concat({algorithm, Encode(0x24, Encode(0x04, digest))}))), rsa); }); + RequireThrows([&] { Verify(DigestSha256, digest, Sign(Concat({{0x30, 0x80}, algorithm, Encode(0x04, digest), {0x00, 0x00}})), rsa); }); + const std::pair objects[] {{3, "2a864886f70d0202"}, {4, "2a864886f70d0205"}, {41, "2b0e030212"}, {64, "2b0e03021a"}, {95, "55080365"}, + {117, "2b24030201"}, {257, "2a864886f70d0204"}, {672, "608648016503040201"}, {673, "608648016503040202"}, {674, "608648016503040203"}, + {675, "608648016503040204"}, {804, "28cf06030037"}, {809, "2a8503020209"}, {941, "2a85030701010202"}, {942, "2a85030701010203"}}; + const auto hash = Pattern(20, 11); + for (std::size_t index = 0; index < std::size(objects); ++index) { + const auto encoded = Sign(DigestInfo(Hex(objects[index].second), Null, hash)); + Require(Verify(objects[index].first, hash, encoded, rsa) == 1); + Require(Verify(objects[(index + 1) % std::size(objects)].first, hash, encoded, rsa) == 0); + Require(Verify(1, hash, encoded, rsa) == 0); + } + const auto unknown = Sign(DigestInfo({0x2A, 0x03, 0x04}, Null, hash)); + for (int type : {-1, 956, 2000, DigestSha1, DigestSha256}) Require(Verify(type, hash, unknown, rsa) == 0); + for (int type : {0, 1, 955}) RequireThrows([&] { Verify(type, hash, unknown, rsa); }); + const auto* defaultMethod = rsa->meth; + LibreSslRsaMethod method = *defaultMethod; + method.verify = reinterpret_cast(RecordVerify); + rsa->meth = &method; + verifies = {}; + Require(Verify(DigestSha256, digest, signature, rsa) == 1 && verifies.calls == 0); + rsa->flags |= RsaFlagSignVerify; + Require(Verify(5, wrong, tampered, rsa) == 55); + Require(verifies.calls == 1 && verifies.type == 5 && verifies.digest == wrong.data() && verifies.digestLength == 32); + Require(verifies.signature == tampered.data() && verifies.signatureLength == 256 && verifies.rsa == rsa); + method.verify = nullptr; + Require(Verify(DigestSha256, digest, signature, rsa) == 1 && verifies.calls == 1); + rsa->meth = nullptr; + RequireThrows([&] { Verify(DigestSha256, digest, signature, rsa); }); + rsa->flags &= ~RsaFlagSignVerify; + rsa->meth = defaultMethod; + RequireThrows([&] { Verify(DigestSha256, digest, signature, nullptr); }); + RequireThrows([&] { RSA_verify_nid_postfix(DigestSha256, nullptr, 32, signature.data(), 256, rsa); }); + Require(RSA_verify_nid_postfix(DigestSha256, nullptr, 32, tampered.data(), 256, rsa) == 0); + Require(RSA_verify_nid_postfix(DigestSha256, nullptr, 0, signature.data(), 256, rsa) == 0); + RequireThrows([&] { RSA_verify_nid_postfix(DigestSha256, digest.data(), 32, nullptr, 256, rsa); }); + Require(RSA_verify_nid_postfix(DigestSha256, digest.data(), 32, nullptr, 255, rsa) == 0); + RSA_free_nid_postfix(rsa); +} + int main() { InitializeHeap(); TestFlags(); @@ -656,5 +772,6 @@ int main() { TestPublicDecrypt(); TestPublicEncrypt(); TestPublicKeyChecks(); + TestVerify(); std::puts("libSceLibreSsl RSA tests passed"); } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index f8976c642a..0211001eba 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -23,6 +23,7 @@ - [RSA_new, RSA_free](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. They follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/rsa) `rsa_lib.c` and `rsa_eay.c`: the key comes from the guest `malloc` with the default method `"Eric Young's PKCS#1 RSA"` (method flags 0, key flags `RSA_FLAG_CACHE_PUBLIC | RSA_FLAG_CACHE_PRIVATE`) and one reference, and `RSA_free` drops a reference, runs the method's `finish` and clears and frees each `BIGNUM` as `BN_clear_free` does. The method's slots are host functions; its private-key operations, `rsa_mod_exp` and `bn_mod_exp` throw, as no export sets a private key yet. With no `ENGINE`, ex_data or blinding implemented, `RSA_free` throws for a key holding one, and for a Montgomery context, which this module never caches - [d2i_RSA_PUBKEY](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. It decodes a SubjectPublicKeyInfo as [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto)'s `x_pubkey.c`, `rsa_ameth.c`, `rsa_asn1.c` and `tasn_dec.c` do, without building the `X509_PUBKEY` and `EVP_PKEY` they use on the way: the `rsaEncryption` or `rsa` OID, any well-formed parameter, the masked BIT STRING and `n` and `e` read as unsigned `BIGNUM`s on a fresh `RSA_new` key, with the length, tag and header quirks of that decoder. The BER forms it also accepts throw: indefinite lengths, constructed strings and parameters, and a BIT STRING with 32 or more unused bits, whose mask is undefined C. A null input pointer, and a null input with a positive length, throw - [RSA_public_encrypt, RSA_public_decrypt](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. They call the key method's slots; the default ones follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/rsa) `rsa_eay.c`, `rsa_pk1.c`, `rsa_oaep.c` and `rsa_none.c`: the modulus and exponent limits, encryption with PKCS#1 v1.5, OAEP (SHA-1, empty label) or no padding, decryption checking PKCS#1 v1.5 type 1 or no padding, and -1 for other paddings, without the error codes LibreSSL queues. Random padding comes from host entropy and the arithmetic from mbedtls, whose 8192-bit limit makes a larger modulus throw; the Montgomery context is never cached. `RSA_SSLV23_PADDING` encryption and `RSA_X931_PADDING` decryption throw, as do a method with its own `bn_mod_exp`, a negative `BIGNUM` and a null key, method or slot +- [RSA_verify](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. It follows [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/rsa/rsa_sign.c) `RSA_verify` and `int_rsa_verify`: the method's `rsa_verify` under `RSA_FLAG_SIGN_VER`, the signature length, `RSA_public_decrypt` with PKCS#1 v1.5 padding, the 36-byte `NID_md5_sha1` case and the `X509_SIG` decode with its trailing-data, parameter, object and digest checks, without the error codes LibreSSL queues. `OBJ_obj2nid` is modelled by the 15 digest objects of that version's object table, so a digest type and an object both outside them, `NID_undef` included, throw, and objects added with `OBJ_create` are not seen. The BER forms of the `d2i_RSA_PUBKEY` entry throw here too, as do a null key, a null method under `RSA_FLAG_SIGN_VER`, and a null digest or signature that LibreSSL reads - [X509_STORE_CTX_get_error, X509_STORE_CTX_set_error, X509_STORE_CTX_get_error_depth, X509_STORE_CTX_get_current_cert, X509_STORE_CTX_get_chain](../../../core/libs/prx/libSceLibreSsl/src/X509StoreContext.cpp) (libSceLibreSsl) - the 240-byte `X509_STORE_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509) `x509_vfy.h` and `x509_vfy.c`, the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214); nothing is checked on the console. No export of this module creates or fills a context yet, so only contexts the caller builds reach them. A null context, which LibreSSL dereferences, throws - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console From 42ec3b23c61bfd615ecf87584187b9db9a72b10d Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Tue, 27 Oct 2026 22:35:30 -0700 Subject: [PATCH 079/612] docs(debt): count the libSceLibreSsl exports still pending after the RSA calls The pending-exports line said 313 while Export.cpp on main has 307 calls that throw; with the RSA_new, RSA_free, d2i_RSA_PUBKEY, RSA_public_encrypt, RSA_public_decrypt and RSA_verify implementations it has 301. --- docs/dev/debt/libSceLibreSsl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 0211001eba..d7e11def55 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -6,7 +6,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 313 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 301 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. From 7cf76af1d5b3d7deb160d535079791f670bd494c Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Mon, 26 Oct 2026 06:37:18 -0700 Subject: [PATCH 080/612] test(libSceLibreSsl): stop requiring unimplemented RSA features to throw The RSA test pinned features that aren't implemented yet as throwing: RSA_free with an ENGINE, ex_data, blinding or Montgomery context set, the BER forms of d2i_RSA_PUBKEY and of a signature's DigestInfo, SSLv23 and X9.31 padding, moduli over 8192 bits, a custom bn_mod_exp, and the digest types with no object. A test shouldn't lock in a missing feature, so these checks and the OtherModExp fixture go. The calls still refuse them, as the debt entries say; the null-pointer, malformed-input and invalid-length checks stay. Asked for in oneandonlydean's review. --- core/libs/tests/GuestLibreSslRsa.cpp | 29 ---------------------------- 1 file changed, 29 deletions(-) diff --git a/core/libs/tests/GuestLibreSslRsa.cpp b/core/libs/tests/GuestLibreSslRsa.cpp index 7bc2b87faa..c6ccfbeaa0 100644 --- a/core/libs/tests/GuestLibreSslRsa.cpp +++ b/core/libs/tests/GuestLibreSslRsa.cpp @@ -198,13 +198,6 @@ static void TestFree() { RSA_free_nid_postfix(rsa); Require(finishes.calls == 1); - int marker = 0; - for (int field = 0; field < 5; ++field) { - rsa = RSA_new_nid_postfix(); - void** slots[] {&rsa->engine, &rsa->exData, &rsa->blinding, &rsa->mtBlinding, &rsa->methodModN}; - *slots[field] = ▮ - RequireThrows([&] { RSA_free_nid_postfix(rsa); }); - } rsa = RSA_new_nid_postfix(); rsa->meth = nullptr; RequireThrows([&] { RSA_free_nid_postfix(rsa); }); @@ -383,10 +376,6 @@ static void TestPublicKeyInfoVariants() { auto highTag = Concat({{0x3F, 0x10}, Bytes(spki.begin() + 1, spki.end())}); RequireAccepted(highTag, 0x260445); RequireRefused(Concat({{0x30, 0x89, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x22}, Bytes(spki.begin() + 4, spki.end())})); - auto indefinite = Concat({{0x30, 0x80}, Bytes(spki.begin() + 4, spki.end()), {0x00, 0x00}}); - RequireThrows([&] { Decode(indefinite, static_cast(indefinite.size())); }); - auto constructedBits = Encode(0x30, Concat({Encode(0x30, algorithm), Encode(0x23, Encode(0x03, bits))})); - RequireThrows([&] { Decode(constructedBits, static_cast(constructedBits.size())); }); } constexpr int PaddingPkcs1 = 1; @@ -506,7 +495,6 @@ static void TestPublicDecrypt() { Require(Zero(out.data(), 256) && out[256] == 0xEE); Require(RSA_public_decrypt_nid_postfix(0, nullptr, out.data(), rsa, PaddingPkcs1) == -1); for (int padding : {0, PaddingSslv23, PaddingOaep, 6, 7}) Require(RSA_public_decrypt_nid_postfix(256, signature.data(), out.data(), rsa, padding) == -1); - RequireThrows([&] { RSA_public_decrypt_nid_postfix(256, signature.data(), out.data(), rsa, PaddingX931); }); RequireThrows([&] { RSA_public_decrypt_nid_postfix(256, nullptr, out.data(), rsa, PaddingPkcs1); }); RequireThrows([&] { RSA_public_decrypt_nid_postfix(256, signature.data(), nullptr, rsa, PaddingPkcs1); }); RSA_free_nid_postfix(rsa); @@ -544,7 +532,6 @@ static void TestPublicEncrypt() { Require(RSA_public_encrypt_nid_postfix(-1, raw.data(), first.data(), rsa, PaddingNone) == -1); Require(RSA_public_encrypt_nid_postfix(256, Hex(CavpModulus).data(), first.data(), rsa, PaddingNone) == -1); for (int padding : {0, PaddingX931, 6, 7}) Require(RSA_public_encrypt_nid_postfix(32, message.data(), first.data(), rsa, padding) == -1); - RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, message.data(), first.data(), rsa, PaddingSslv23); }); RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, message.data(), nullptr, rsa, PaddingPkcs1); }); RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, nullptr, first.data(), rsa, PaddingPkcs1); }); RSA_free_nid_postfix(rsa); @@ -566,10 +553,6 @@ static int APS5_VABI RecordCrypt(int length, const unsigned char* from, unsigned return 77; } -static int APS5_VABI OtherModExp(void*, const void*, const void*, const void*, void*, void*) { - return 1; -} - static void TestPublicKeyChecks() { const auto modulus = Hex(CavpModulus); const auto message = Pattern(256, 1); @@ -613,13 +596,6 @@ static void TestPublicKeyChecks() { TestKey key(huge, {0x03}); Require(RSA_public_decrypt_nid_postfix(1, message.data(), out.data(), key.rsa, PaddingNone) == -1); } - { - Bytes wide(1025, 0); - wide[0] = 0x01; - wide.back() = 0x01; - TestKey key(wide, {0x03}); - RequireThrows([&] { RSA_public_decrypt_nid_postfix(1, message.data(), out.data(), key.rsa, PaddingNone); }); - } { TestKey key(modulus, CavpExponent); const auto* defaultMethod = key.rsa->meth; @@ -632,8 +608,6 @@ static void TestPublicKeyChecks() { Require(crypts.calls == 1 && crypts.length == 5 && crypts.from == message.data() && crypts.to == out.data() && crypts.rsa == key.rsa && crypts.padding == 9); Require(RSA_public_decrypt_nid_postfix(6, message.data(), out.data(), key.rsa, 8) == 77 && crypts.calls == 2 && crypts.length == 6); method = *defaultMethod; - method.operations[5] = reinterpret_cast(OtherModExp); - RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, message.data(), out.data(), key.rsa, PaddingPkcs1); }); method.operations[0] = nullptr; RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, message.data(), out.data(), key.rsa, PaddingPkcs1); }); key.rsa->meth = nullptr; @@ -721,8 +695,6 @@ static void TestVerify() { Require(Verify(DigestSha256, digest, Sign(Encode(0x04, digest)), rsa) == 0); Require(Verify(0, digest, Sign(DigestInfo({}, Null, digest)), rsa) == 0); Require(Verify(0, digest, Sign(DigestInfo({0x2A, 0x80}, Null, digest)), rsa) == 0); - RequireThrows([&] { Verify(DigestSha256, digest, Sign(Encode(0x30, Concat({algorithm, Encode(0x24, Encode(0x04, digest))}))), rsa); }); - RequireThrows([&] { Verify(DigestSha256, digest, Sign(Concat({{0x30, 0x80}, algorithm, Encode(0x04, digest), {0x00, 0x00}})), rsa); }); const std::pair objects[] {{3, "2a864886f70d0202"}, {4, "2a864886f70d0205"}, {41, "2b0e030212"}, {64, "2b0e03021a"}, {95, "55080365"}, {117, "2b24030201"}, {257, "2a864886f70d0204"}, {672, "608648016503040201"}, {673, "608648016503040202"}, {674, "608648016503040203"}, {675, "608648016503040204"}, {804, "28cf06030037"}, {809, "2a8503020209"}, {941, "2a85030701010202"}, {942, "2a85030701010203"}}; @@ -735,7 +707,6 @@ static void TestVerify() { } const auto unknown = Sign(DigestInfo({0x2A, 0x03, 0x04}, Null, hash)); for (int type : {-1, 956, 2000, DigestSha1, DigestSha256}) Require(Verify(type, hash, unknown, rsa) == 0); - for (int type : {0, 1, 955}) RequireThrows([&] { Verify(type, hash, unknown, rsa); }); const auto* defaultMethod = rsa->meth; LibreSslRsaMethod method = *defaultMethod; method.verify = reinterpret_cast(RecordVerify); From 210bd86b58b015bd07eba4d84afc0c1081ebfc70 Mon Sep 17 00:00:00 2001 From: Sam <85789460+Sam-mon7@users.noreply.github.com> Date: Wed, 28 Oct 2026 12:01:17 +0000 Subject: [PATCH 081/612] test(agc): accept the host CPU line in the late shader output failure check On Linux libc prints its host CPU line to stdout, so the check that a failed write left stdout empty failed even though the tool reported the failure and exited 2. Check that no success summary was printed instead, as the blocked output case does. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/tests/FrameReplayTool.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplayTool.cmake b/core/libs/prx/libSceAgcDriver/tests/FrameReplayTool.cmake index 86da56139d..dcb2bf078b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/FrameReplayTool.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplayTool.cmake @@ -96,7 +96,7 @@ elseif(MODE STREQUAL "shader_output") endif() runTool(2 --shader-code "${full}") requireContains("${error}" "${fullFile}") - if(NOT error MATCHES "cannot (write|flush|close)" OR NOT output STREQUAL "") + if(NOT error MATCHES "cannot (write|flush|close)" OR output MATCHES "\"registrations\"") message(FATAL_ERROR "late shader output failure was not reported:\n${output}\n${error}") endif() endif() From 3428c0e8464ebf13f59f5c7b1762366f1e1c99a9 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Fri, 23 Oct 2026 13:09:21 -0400 Subject: [PATCH 082/612] fix(libc): compute atanh with FreeBSD msun's algorithm, as titles' libc.prx does atanh returned the host's atanh, whose last bit differs from the titles' copy on Windows. The titles' copy is FreeBSD msun's e_atanh.c over msun's log1p, and it sets EDOM for |x| > 1, the infinities and every NaN, and ERANGE for +-1. atanh now computes that algorithm over this file's log1p and sets errno there. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Math.cpp | 29 ++++++++++++++++++++++++++++- core/libs/tests/GuestMath.cpp | 27 +++++++++++++++++++++++++++ docs/dev/debt/libc.md | 1 + 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 9c34d18ed7..c8ea682e14 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -1234,7 +1234,34 @@ float APS5_VABI acoshf_nid_postfix(float x) { const float t = x - 1.0f; return AtanhfLog1p(t + std::sqrt(2.0f * t + t * t)); } -double APS5_VABI atanh_nid_postfix(double x) { return std::atanh(x); } +double APS5_VABI atanh_nid_postfix(double x) { + const double huge = 1e300; + static volatile double zero = 0.0; + const std::uint64_t bits = std::bit_cast(x); + const std::int32_t hx = static_cast(bits >> 32); + const std::uint32_t lx = static_cast(bits); + const std::int32_t ix = hx & 0x7fffffff; + if ((static_cast(ix) | ((lx | (0u - lx)) >> 31)) > 0x3ff00000u) { + errno = EDOM; + return (x - x) / (x - x); + } + if (ix == 0x3ff00000) { + errno = ERANGE; + return x / zero; + } + if (ix < 0x3e300000 && huge + x > 0.0) { + return x; + } + x = std::bit_cast((static_cast(static_cast(ix)) << 32) | lx); + double t; + if (ix < 0x3fe00000) { + t = x + x; + t = 0.5 * log1p_nid_postfix(t + t * x / (1.0 - x)); + } else { + t = 0.5 * log1p_nid_postfix((x + x) / (1.0 - x)); + } + return hx >= 0 ? t : -t; +} float APS5_VABI atanhf_nid_postfix(float x) { const std::uint32_t magnitude = std::bit_cast(x) & 0x7fffffffu; diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 80f1217e86..ef410c483e 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -1039,6 +1039,32 @@ static void CheckExpm1ScalingRounding() { _mm_setcsr(savedCsr); } +static void CheckAtanhConsoleBits() { + constexpr struct { + std::uint64_t input; + std::uint64_t expected; + int error; + } cases[] = { + {0x0000000000000000ull, 0x0000000000000000ull, 0}, {0x8000000000000000ull, 0x8000000000000000ull, 0}, + {0x3e2fffffffffffffull, 0x3e2fffffffffffffull, 0}, {0x3e30000000000000ull, 0x3e30000000000000ull, 0}, + {0xbe30000000000000ull, 0xbe30000000000000ull, 0}, {0x3fdfffffffffffffull, 0x3fe193ea7aad030aull, 0}, + {0x3fe0000000000000ull, 0x3fe193ea7aad030aull, 0}, {0xbfe0000000000000ull, 0xbfe193ea7aad030aull, 0}, + {0x3fefffffffffffffull, 0x4032b708872320e2ull, 0}, {0xbfefffffffffffffull, 0xc032b708872320e2ull, 0}, + {0x3ff0000000000000ull, 0x7ff0000000000000ull, ERANGE}, {0xbff0000000000000ull, 0xfff0000000000000ull, ERANGE}, + {0x3ff0000000000001ull, 0xfff8000000000000ull, EDOM}, {0xc000000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff0000000000000ull, 0xfff8000000000000ull, EDOM}, {0xfff0000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, EDOM}, {0xfff8000000000002ull, 0xfff8000000000002ull, EDOM}, + {0x3f03a67d5567227bull, 0x3f03a67d558ea76eull, 0}, {0x3faa28c4621dd8d7ull, 0x3faa2e9a77f5a83full, 0}, + {0x3f4742fce55398bbull, 0x3f4742fd26e249ceull, 0}, {0x3f2cf9c68a297d4aull, 0x3f2cf9c69214cc60ull, 0}, + {0x3fc796d8c6ddd135ull, 0x3fc7dca409c1ba11ull, 0}, {0x3f73b85b718bdc38ull, 0x3f73b8656ddda145ull, 0}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(atanh_nid_postfix(std::bit_cast(item.input))) == item.expected); + Require(errno == (item.error != 0 ? item.error : 1234)); + } +} + static void CheckAsinl() { const struct { std::uint64_t inputMantissa; std::uint16_t inputSignExponent; std::uint64_t outputMantissa; std::uint16_t outputSignExponent; } cases[] = { {0, 0x0000, 0, 0x0000}, {0, 0x8000, 0, 0x8000}, @@ -2369,6 +2395,7 @@ int main() { CheckNextafterEnvironment(nextafterf_nid_postfix, 0x80000000u, 0x3f800000u, 0x00800000u, 0x7f7fffffu, 0x7f800000u); CheckNextafterEnvironment(nextafter_nid_postfix, std::uint64_t{0x8000000000000000ull}, std::uint64_t{0x3ff0000000000000ull}, std::uint64_t{0x0010000000000000ull}, std::uint64_t{0x7fefffffffffffffull}, std::uint64_t{0x7ff0000000000000ull}); CheckExpm1ScalingRounding(); + CheckAtanhConsoleBits(); CheckAsinl(); Require(scalbn_nid_postfix(0.75, -2) == 0.1875); Require(scalbnf_nid_postfix(0.75f, 4) == 12.f); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 568e1931c1..3ef9660417 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -142,6 +142,7 @@ - [log1p](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`s_log1p.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_log1p.c). As the titles' `libc.prx` does, it sets `EDOM` at msun's NaN return (arguments below -1, `-inf` and negative NaNs) and leaves `errno` alone for `log1p(-1)`, where it returns `-inf`. It gives the same bits, NaN payloads included, and `errno` as the `log1p` bundled with PPSA07923 and PPSA05916 on 320 million random and 68,216 structured arguments on a Windows host. The FMA-contracted build bundled with PPSA13934 differs on 73,682 of the random ones. - [nextafter and nextafterf](../../../core/libs/prx/libc/src/Math.cpp) (libc) have no physical-console or unmasked-exception validation. A Linux CPU probe compares result bits, `errno` and masked floating-point flags with the unchanged math code in FAR: Changing Tides PPSA03766 bundled `libc.prx` (`nextafter` 0x51b30, `nextafterf` 0x51c80, `_Feraise` 0x11da0): 16,864 calls across both precisions, four rounding modes and all DAZ/FTZ combinations, not exhaustive inputs. Earlier Windows sampling of eleven bundled libraries compares only bits and `errno`; exception behavior outside the FAR module remains unverified. - [tanh](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`s_tanh.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_tanh.c) over this file's msun `Expm1`, with `tiny` read at run time; it sets no `errno`, as the titles' `libc.prx` does not. It gives the same bits as the `tanh` bundled with PPSA07923 and PPSA05916 on 16 million random and 68,211 structured arguments on a Windows host; the FMA-contracted build bundled with PPSA13934 differs on 1,054 of them. +- [atanh](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`e_atanh.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_atanh.c) over this file's msun `log1p`. As the titles' `libc.prx` does, it sets `EDOM` for |x| > 1, the infinities and every NaN, and `ERANGE` for ±1, msun's two error returns. It gives the same bits, NaN payloads included, and `errno` as the `atanh` bundled with PPSA07923 and PPSA05916 on 16 million random and 68,211 structured arguments on a Windows host; the FMA-contracted build bundled with PPSA13934 differs on 2,778 of them. - [sinhf](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows the `sinhf` of the `libc.prx` bundled with PPSA28319 (disassembly): FreeBSD msun's [`e_sinhf.c`](https://github.com/freebsd/freebsd-src/blob/5aa554c7e5b6fd9042986df07f12ac5687e41392/lib/msun/src/e_sinhf.c) from before `__ldexp_expf`, which squares `expf(|x| / 2)` between `logf(FLT_MAX)` and the overflow threshold, calling msun's [`expm1f`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/s_expm1f.c) and [`expf`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/e_expf.c), whose paths for the arguments it passes are inlined here. Past the threshold Sony's error helper sets `errno` to `ERANGE` before returning `x * 1e37`; msun itself sets no `errno`. That code, run on a Windows host, gives the same bits as this implementation for every finite argument up to the threshold, of both signs; it was not run on Linux. That the system `libSceLibcInternal`, which Ruffle (PPSA68091) and RetroArch (PPSA99169) import while bundling a zero-return `libc.prx`, has the same code is assumed - [acoshf](../../../core/libs/prx/libc/src/Math.cpp) (libc) exact system `libSceLibcInternal` behavior and FMA-contracted copies remain unverified. Positive finite inputs use [msun](https://github.com/freebsd/freebsd-src/blob/5aa554c7e5b6fd9042986df07f12ac5687e41392/lib/msun/src/e_acoshf.c) range handling and this file's `logf`; the middle range divides -1 before addition under directed rounding. Bits, errno and SSE/x87 status and controls match the supplied PPSA03766/PPSA07923 copies on 236,592 positive finite calls (4,929 inputs, four rounding modes, DAZ/FTZ combinations and sticky flags); these samples are not exhaustive. Invalid-domain errno and SSE/x87 invalid flags are checked against two supplied bundled copies; those samples are not exhaustive either. - [acoshl, asinhl, atanhl, atan2l, cbrtl, erfl, erfcl, expm1l, hypotl, log1pl, log2l, lgammal, tgammal, j0f, j1f, jnf, y0f, y1f and ynf](../../../core/libs/prx/libc/src/Math.cpp) (libc) return the host C library's results (the float Bessel functions through its double ones), not those of the console's own code, so the last bits can differ (`y0f`, `y1f`, `ynf` follow the console at `+-0`). `tgammaf` delegates to the host's double `tgamma` as FreeBSD 11 does ([`s_tgammaf.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/msun/src/s_tgammaf.c)), then restores the sign of a zero result for negative non-integer inputs from the parity of `floor(-x)`; nine title-bundled `libc.prx` builds return that sign for underflowing inputs, where Windows UCRT can return the opposite sign ([#9707](https://github.com/boykopovar/AnyPS5/issues/9707)). As in the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x56410), `lgammal` does not store the sign in `signgam`; `hypotl` returns infinity for an infinite argument even when the other is a NaN, as C11 F.10.4.3 requires. [hypot3, hypot3f and hypot3l](../../../core/libs/prx/libc/src/Math.cpp) follow that module (0x51D30, 0x520D0, 0x52420): the first NaN of x, y and z is returned unchanged, any infinity gives positive infinity, a zero argument gives the two-argument hypot of the other two, and otherwise the largest magnitude is combined with the hypot of the other two From 6aea92f4350bc48870c61664c30f013ad2631a4b Mon Sep 17 00:00:00 2001 From: JJenkx Date: Sat, 24 Oct 2026 06:06:32 -0400 Subject: [PATCH 083/612] test(libc): check atanh pole and domain exception flags --- core/libs/tests/GuestMath.cpp | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index ef410c483e..703e8a3536 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -1038,6 +1038,55 @@ static void CheckExpm1ScalingRounding() { } _mm_setcsr(savedCsr); } +static void CheckAtanhDomainEnvironment() { + const RestoreFloatingPointState saved; + const unsigned savedCsr = _mm_getcsr(); + const struct { + std::uint64_t input; + std::uint64_t result; + unsigned raised; + int error; + } cases[] = { + {0x3ff0000000000000ull, 0x7ff0000000000000ull, 4u, ERANGE}, + {0xbff0000000000000ull, 0xfff0000000000000ull, 4u, ERANGE}, + {0x3ff0000000000001ull, 0xfff8000000000000ull, 1u, EDOM}, + {0xbff0000000000001ull, 0xfff8000000000000ull, 1u, EDOM}, + {0x4000000000000000ull, 0xfff8000000000000ull, 1u, EDOM}, + {0xc000000000000000ull, 0xfff8000000000000ull, 1u, EDOM}, + {0x7ff0000000000000ull, 0xfff8000000000000ull, 1u, EDOM}, + {0xfff0000000000000ull, 0xfff8000000000000ull, 1u, EDOM}, + {0x7ff8000000001234ull, 0x7ff8000000001234ull, 1u, EDOM}, + {0xfff8000000001234ull, 0xfff8000000001234ull, 1u, EDOM}, + {0x7ff0000000000001ull, 0x7ff8000000000001ull, 1u, EDOM}, + {0xfff0000000000001ull, 0xfff8000000000001ull, 1u, EDOM}, + }; + const int rounding[] = {FE_TONEAREST, FE_DOWNWARD, FE_UPWARD, FE_TOWARDZERO}; + double (APS5_VABI * volatile call)(double) = atanh_nid_postfix; + for (unsigned mode = 0; mode != 4; ++mode) { + for (unsigned denorm = 0; denorm != 4; ++denorm) { + const unsigned control = 0x1f80u | (mode << 13u) | ((denorm & 1u) ? 0x40u : 0u) | ((denorm & 2u) ? 0x8000u : 0u); + for (std::uint16_t sticky : {0u, 5u, 32u}) { + for (const auto& item : cases) { + Require(std::fesetenv(FE_DFL_ENV) == 0 && std::fesetround(rounding[mode]) == 0); + Require(fesetexceptflag_nid_postfix(&sticky, 0x3f) == 0); + _mm_setcsr(control | sticky); + GuestFloatingPointEnvironment before; + Require(fegetenv_nid_postfix(&before) == 0 && (before.status & 0x3fu) == sticky); + errno = 1234; + const auto result = std::bit_cast(call(std::bit_cast(item.input))); + const int error = errno; + GuestFloatingPointEnvironment after; + Require(fegetenv_nid_postfix(&after) == 0); + if (result != item.result || after.mxcsr != (control | sticky | item.raised) || (after.status & 0x3fu) != (sticky | item.raised) || after.control != before.control || error != item.error) { + std::fprintf(stderr, "atanh domain: input=%016llx mode=%u denorm=%u sticky=%u bits=%016llx csr=%x x87=%x errno=%d\n", static_cast(item.input), mode, denorm, sticky, static_cast(result), after.mxcsr, after.status, error); + Require(false); + } + } + } + } + } + _mm_setcsr(savedCsr); +} static void CheckAtanhConsoleBits() { constexpr struct { @@ -2396,6 +2445,7 @@ int main() { CheckNextafterEnvironment(nextafter_nid_postfix, std::uint64_t{0x8000000000000000ull}, std::uint64_t{0x3ff0000000000000ull}, std::uint64_t{0x0010000000000000ull}, std::uint64_t{0x7fefffffffffffffull}, std::uint64_t{0x7ff0000000000000ull}); CheckExpm1ScalingRounding(); CheckAtanhConsoleBits(); + CheckAtanhDomainEnvironment(); CheckAsinl(); Require(scalbn_nid_postfix(0.75, -2) == 0.1875); Require(scalbnf_nid_postfix(0.75f, 4) == 12.f); From 26c43ad44c145126c7ab24707957d520a5bdf9fc Mon Sep 17 00:00:00 2001 From: JJenkx Date: Sat, 24 Oct 2026 06:07:34 -0400 Subject: [PATCH 084/612] fix(libc): raise atanh pole and domain flags in both exception banks --- core/libs/prx/libc/src/Math.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index c8ea682e14..22cb3b9c10 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -1242,10 +1242,12 @@ double APS5_VABI atanh_nid_postfix(double x) { const std::uint32_t lx = static_cast(bits); const std::int32_t ix = hx & 0x7fffffff; if ((static_cast(ix) | ((lx | (0u - lx)) >> 31)) > 0x3ff00000u) { + _Feraise_nid_postfix(0x01); errno = EDOM; return (x - x) / (x - x); } if (ix == 0x3ff00000) { + _Feraise_nid_postfix(0x04); errno = ERANGE; return x / zero; } From ac3b199860766a5b781e24b18dce32208235328e Mon Sep 17 00:00:00 2001 From: JJenkx Date: Fri, 23 Oct 2026 13:09:21 -0400 Subject: [PATCH 085/612] fix(libc): compute atanh with FreeBSD msun's algorithm, as titles' libc.prx does atanh returned the host's atanh, whose last bit differs from the titles' copy on Windows. The titles' copy is FreeBSD msun's e_atanh.c over msun's log1p, and it sets EDOM for |x| > 1, the infinities and every NaN, and ERANGE for +-1. atanh now computes that algorithm over this file's log1p and sets errno there. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Math.cpp | 29 ++++++++++++++++++++++++++++- core/libs/tests/GuestMath.cpp | 26 ++++++++++++++++++++++++++ docs/dev/debt/libc.md | 1 + 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 9c34d18ed7..c8ea682e14 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -1234,7 +1234,34 @@ float APS5_VABI acoshf_nid_postfix(float x) { const float t = x - 1.0f; return AtanhfLog1p(t + std::sqrt(2.0f * t + t * t)); } -double APS5_VABI atanh_nid_postfix(double x) { return std::atanh(x); } +double APS5_VABI atanh_nid_postfix(double x) { + const double huge = 1e300; + static volatile double zero = 0.0; + const std::uint64_t bits = std::bit_cast(x); + const std::int32_t hx = static_cast(bits >> 32); + const std::uint32_t lx = static_cast(bits); + const std::int32_t ix = hx & 0x7fffffff; + if ((static_cast(ix) | ((lx | (0u - lx)) >> 31)) > 0x3ff00000u) { + errno = EDOM; + return (x - x) / (x - x); + } + if (ix == 0x3ff00000) { + errno = ERANGE; + return x / zero; + } + if (ix < 0x3e300000 && huge + x > 0.0) { + return x; + } + x = std::bit_cast((static_cast(static_cast(ix)) << 32) | lx); + double t; + if (ix < 0x3fe00000) { + t = x + x; + t = 0.5 * log1p_nid_postfix(t + t * x / (1.0 - x)); + } else { + t = 0.5 * log1p_nid_postfix((x + x) / (1.0 - x)); + } + return hx >= 0 ? t : -t; +} float APS5_VABI atanhf_nid_postfix(float x) { const std::uint32_t magnitude = std::bit_cast(x) & 0x7fffffffu; diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 80f1217e86..34a0a628d9 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -1038,6 +1038,31 @@ static void CheckExpm1ScalingRounding() { } _mm_setcsr(savedCsr); } +static void CheckAtanhConsoleBits() { + constexpr struct { + std::uint64_t input; + std::uint64_t expected; + int error; + } cases[] = { + {0x0000000000000000ull, 0x0000000000000000ull, 0}, {0x8000000000000000ull, 0x8000000000000000ull, 0}, + {0x3e2fffffffffffffull, 0x3e2fffffffffffffull, 0}, {0x3e30000000000000ull, 0x3e30000000000000ull, 0}, + {0xbe30000000000000ull, 0xbe30000000000000ull, 0}, {0x3fdfffffffffffffull, 0x3fe193ea7aad030aull, 0}, + {0x3fe0000000000000ull, 0x3fe193ea7aad030aull, 0}, {0xbfe0000000000000ull, 0xbfe193ea7aad030aull, 0}, + {0x3fefffffffffffffull, 0x4032b708872320e2ull, 0}, {0xbfefffffffffffffull, 0xc032b708872320e2ull, 0}, + {0x3ff0000000000000ull, 0x7ff0000000000000ull, ERANGE}, {0xbff0000000000000ull, 0xfff0000000000000ull, ERANGE}, + {0x3ff0000000000001ull, 0xfff8000000000000ull, EDOM}, {0xc000000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff0000000000000ull, 0xfff8000000000000ull, EDOM}, {0xfff0000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, EDOM}, {0xfff8000000000002ull, 0xfff8000000000002ull, EDOM}, + {0x3f03a67d5567227bull, 0x3f03a67d558ea76eull, 0}, {0x3faa28c4621dd8d7ull, 0x3faa2e9a77f5a83full, 0}, + {0x3f4742fce55398bbull, 0x3f4742fd26e249ceull, 0}, {0x3f2cf9c68a297d4aull, 0x3f2cf9c69214cc60ull, 0}, + {0x3fc796d8c6ddd135ull, 0x3fc7dca409c1ba11ull, 0}, {0x3f73b85b718bdc38ull, 0x3f73b8656ddda145ull, 0}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(atanh_nid_postfix(std::bit_cast(item.input))) == item.expected); + Require(errno == (item.error != 0 ? item.error : 1234)); + } +} static void CheckAsinl() { const struct { std::uint64_t inputMantissa; std::uint16_t inputSignExponent; std::uint64_t outputMantissa; std::uint16_t outputSignExponent; } cases[] = { @@ -2369,6 +2394,7 @@ int main() { CheckNextafterEnvironment(nextafterf_nid_postfix, 0x80000000u, 0x3f800000u, 0x00800000u, 0x7f7fffffu, 0x7f800000u); CheckNextafterEnvironment(nextafter_nid_postfix, std::uint64_t{0x8000000000000000ull}, std::uint64_t{0x3ff0000000000000ull}, std::uint64_t{0x0010000000000000ull}, std::uint64_t{0x7fefffffffffffffull}, std::uint64_t{0x7ff0000000000000ull}); CheckExpm1ScalingRounding(); + CheckAtanhConsoleBits(); CheckAsinl(); Require(scalbn_nid_postfix(0.75, -2) == 0.1875); Require(scalbnf_nid_postfix(0.75f, 4) == 12.f); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 568e1931c1..3ef9660417 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -142,6 +142,7 @@ - [log1p](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`s_log1p.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_log1p.c). As the titles' `libc.prx` does, it sets `EDOM` at msun's NaN return (arguments below -1, `-inf` and negative NaNs) and leaves `errno` alone for `log1p(-1)`, where it returns `-inf`. It gives the same bits, NaN payloads included, and `errno` as the `log1p` bundled with PPSA07923 and PPSA05916 on 320 million random and 68,216 structured arguments on a Windows host. The FMA-contracted build bundled with PPSA13934 differs on 73,682 of the random ones. - [nextafter and nextafterf](../../../core/libs/prx/libc/src/Math.cpp) (libc) have no physical-console or unmasked-exception validation. A Linux CPU probe compares result bits, `errno` and masked floating-point flags with the unchanged math code in FAR: Changing Tides PPSA03766 bundled `libc.prx` (`nextafter` 0x51b30, `nextafterf` 0x51c80, `_Feraise` 0x11da0): 16,864 calls across both precisions, four rounding modes and all DAZ/FTZ combinations, not exhaustive inputs. Earlier Windows sampling of eleven bundled libraries compares only bits and `errno`; exception behavior outside the FAR module remains unverified. - [tanh](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`s_tanh.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_tanh.c) over this file's msun `Expm1`, with `tiny` read at run time; it sets no `errno`, as the titles' `libc.prx` does not. It gives the same bits as the `tanh` bundled with PPSA07923 and PPSA05916 on 16 million random and 68,211 structured arguments on a Windows host; the FMA-contracted build bundled with PPSA13934 differs on 1,054 of them. +- [atanh](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`e_atanh.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_atanh.c) over this file's msun `log1p`. As the titles' `libc.prx` does, it sets `EDOM` for |x| > 1, the infinities and every NaN, and `ERANGE` for ±1, msun's two error returns. It gives the same bits, NaN payloads included, and `errno` as the `atanh` bundled with PPSA07923 and PPSA05916 on 16 million random and 68,211 structured arguments on a Windows host; the FMA-contracted build bundled with PPSA13934 differs on 2,778 of them. - [sinhf](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows the `sinhf` of the `libc.prx` bundled with PPSA28319 (disassembly): FreeBSD msun's [`e_sinhf.c`](https://github.com/freebsd/freebsd-src/blob/5aa554c7e5b6fd9042986df07f12ac5687e41392/lib/msun/src/e_sinhf.c) from before `__ldexp_expf`, which squares `expf(|x| / 2)` between `logf(FLT_MAX)` and the overflow threshold, calling msun's [`expm1f`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/s_expm1f.c) and [`expf`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/e_expf.c), whose paths for the arguments it passes are inlined here. Past the threshold Sony's error helper sets `errno` to `ERANGE` before returning `x * 1e37`; msun itself sets no `errno`. That code, run on a Windows host, gives the same bits as this implementation for every finite argument up to the threshold, of both signs; it was not run on Linux. That the system `libSceLibcInternal`, which Ruffle (PPSA68091) and RetroArch (PPSA99169) import while bundling a zero-return `libc.prx`, has the same code is assumed - [acoshf](../../../core/libs/prx/libc/src/Math.cpp) (libc) exact system `libSceLibcInternal` behavior and FMA-contracted copies remain unverified. Positive finite inputs use [msun](https://github.com/freebsd/freebsd-src/blob/5aa554c7e5b6fd9042986df07f12ac5687e41392/lib/msun/src/e_acoshf.c) range handling and this file's `logf`; the middle range divides -1 before addition under directed rounding. Bits, errno and SSE/x87 status and controls match the supplied PPSA03766/PPSA07923 copies on 236,592 positive finite calls (4,929 inputs, four rounding modes, DAZ/FTZ combinations and sticky flags); these samples are not exhaustive. Invalid-domain errno and SSE/x87 invalid flags are checked against two supplied bundled copies; those samples are not exhaustive either. - [acoshl, asinhl, atanhl, atan2l, cbrtl, erfl, erfcl, expm1l, hypotl, log1pl, log2l, lgammal, tgammal, j0f, j1f, jnf, y0f, y1f and ynf](../../../core/libs/prx/libc/src/Math.cpp) (libc) return the host C library's results (the float Bessel functions through its double ones), not those of the console's own code, so the last bits can differ (`y0f`, `y1f`, `ynf` follow the console at `+-0`). `tgammaf` delegates to the host's double `tgamma` as FreeBSD 11 does ([`s_tgammaf.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/msun/src/s_tgammaf.c)), then restores the sign of a zero result for negative non-integer inputs from the parity of `floor(-x)`; nine title-bundled `libc.prx` builds return that sign for underflowing inputs, where Windows UCRT can return the opposite sign ([#9707](https://github.com/boykopovar/AnyPS5/issues/9707)). As in the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x56410), `lgammal` does not store the sign in `signgam`; `hypotl` returns infinity for an infinite argument even when the other is a NaN, as C11 F.10.4.3 requires. [hypot3, hypot3f and hypot3l](../../../core/libs/prx/libc/src/Math.cpp) follow that module (0x51D30, 0x520D0, 0x52420): the first NaN of x, y and z is returned unchanged, any infinity gives positive infinity, a zero argument gives the two-argument hypot of the other two, and otherwise the largest magnitude is combined with the hypot of the other two From 72a6683e64531e4fc948e6115af96037b367ab5e Mon Sep 17 00:00:00 2001 From: JJenkx Date: Wed, 28 Oct 2026 06:01:54 -0400 Subject: [PATCH 086/612] fix(libc): raise the titles' flags from atanh at its special inputs Titles' libc.prx raises FE_INVALID wherever atanh stores EDOM, a NaN argument included, where (x - x) / zero raised nothing for a NaN operand. The domain path now raises through _Feraise, which stores EDOM and raises the flag in both units. Results and errno are unchanged. CheckAtanhEdgeFlags covers it. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Math.cpp | 2 +- core/libs/tests/GuestMath.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index c8ea682e14..1fb4fca6ee 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -1242,7 +1242,7 @@ double APS5_VABI atanh_nid_postfix(double x) { const std::uint32_t lx = static_cast(bits); const std::int32_t ix = hx & 0x7fffffff; if ((static_cast(ix) | ((lx | (0u - lx)) >> 31)) > 0x3ff00000u) { - errno = EDOM; + _Feraise_nid_postfix(0x01); return (x - x) / (x - x); } if (ix == 0x3ff00000) { diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 34a0a628d9..220f103fdd 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -1038,6 +1038,13 @@ static void CheckExpm1ScalingRounding() { } _mm_setcsr(savedCsr); } +static void CheckAtanhEdgeFlags() { + for (const double input : {2.0, -2.0, std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN(), -std::numeric_limits::quiet_NaN()}) { + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + Require(std::isnan(atanh_nid_postfix(input)) && errno == EDOM && std::fetestexcept(FE_INVALID) != 0); + } +} static void CheckAtanhConsoleBits() { constexpr struct { std::uint64_t input; @@ -2395,6 +2402,7 @@ int main() { CheckNextafterEnvironment(nextafter_nid_postfix, std::uint64_t{0x8000000000000000ull}, std::uint64_t{0x3ff0000000000000ull}, std::uint64_t{0x0010000000000000ull}, std::uint64_t{0x7fefffffffffffffull}, std::uint64_t{0x7ff0000000000000ull}); CheckExpm1ScalingRounding(); CheckAtanhConsoleBits(); + CheckAtanhEdgeFlags(); CheckAsinl(); Require(scalbn_nid_postfix(0.75, -2) == 0.1875); Require(scalbnf_nid_postfix(0.75f, 4) == 12.f); From 4d98e8148f038bca77cc104a1796608d5e99999a Mon Sep 17 00:00:00 2001 From: JJenkx Date: Fri, 23 Oct 2026 11:34:21 -0400 Subject: [PATCH 087/612] fix(libc): compute sin, cos and tan with FreeBSD msun's algorithm, as titles' libc.prx does sin, cos and tan returned the host's functions, whose last bit differs from the titles' copies on Windows on 3,338,607, 3,340,843 and 8,098,628 of 320 million random arguments. The titles' copies are FreeBSD msun's s_sin.c, s_cos.c and s_tan.c over its k_sin.c, k_cos.c, k_tan.c, e_rem_pio2.c and k_rem_pio2.c, and set EDOM when the argument's high word is 0x7ff00000 or 0xfff00000. They now compute that algorithm and set EDOM there. The k_rem_pio2.c port that sinf, cosf and tanf use now takes msun's input word count and precision, so the double functions share it. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Math.cpp | 270 ++++++++++++++++++++++++++++++-- core/libs/tests/GuestMath.cpp | 139 ++++++++++++++++ docs/dev/debt/libc.md | 3 +- 3 files changed, 399 insertions(+), 13 deletions(-) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 9c34d18ed7..61841626d3 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -613,7 +613,6 @@ long double APS5_VABI frexpl_nid_postfix(long double x, int* exponent) { } double APS5_VABI exp_nid_postfix(double x) { return std::exp(x); } double APS5_VABI atan_nid_postfix(double x) { return std::atan(x); } -double APS5_VABI tan_nid_postfix(double x) { return std::tan(x); } double APS5_VABI log2_nid_postfix(double x) { return std::log2(x); } double APS5_VABI log_nid_postfix(double x) { return std::log(x); } double APS5_VABI log1p_nid_postfix(double x) { @@ -821,7 +820,7 @@ double APS5_VABI lgamma_nid_postfix(double x) { return std::lgamma(x); } -static const std::int32_t TrigfIpio2[] = { +static const std::int32_t RemPio2Ipio2[] = { 0xA2F983, 0x6E4E44, 0x1529FC, 0x2757D1, 0xF534DD, 0xC0DB62, 0x95993C, 0x439041, 0xFE5163, 0xABDEBB, 0xC561B7, 0x246E3A, 0x424DD2, 0xE00649, 0x2EEA09, 0xD1921C, 0xFE1DEB, 0x1CB129, 0xA73EE8, 0x8235F5, 0x2EBB44, 0x84E99C, 0x7026B4, 0x5F7E41, 0x3991D6, 0x398353, 0x39F49C, 0x845F8B, 0xBDF928, 0x3B1FF8, 0x97FFDE, 0x05980F, 0xEF2F11, 0x8B5A0A, 0x6D1F6D, 0x367ECF, @@ -830,24 +829,24 @@ static const std::int32_t TrigfIpio2[] = { 0x4D7327, 0x310606, 0x1556CA, 0x73A8C9, 0x60E27B, 0xC08C6B, }; -static const double TrigfPio2Parts[] = { +static const double RemPio2Parts[] = { 1.57079625129699707031e+00, 7.54978941586159635335e-08, 5.39030252995776476554e-15, 3.28200341580791294123e-22, 1.27065575308067607349e-29, 1.22933308981111328932e-36, 2.73370053816464559624e-44, 2.16741683877804819444e-51, }; -static int TrigfKernelRemPio2(const double* x, double* y, int e0) { +static int KernelRemPio2(const double* x, double* y, int e0, int nx, int prec) { const double two24 = 1.67772160000000000000e+07, twon24 = 5.96046447753906250000e-08; std::int32_t jz, jv, jp, jk, carry, n, iq[20], i, j, k, m, q0, ih; double z, fw, f[20], fq[20], q[20]; - const std::int32_t jx = 0; - jk = 3; + const std::int32_t jx = nx - 1; + jk = prec == 0 ? 3 : 4; jp = jk; jv = (e0 - 3) / 24; if (jv < 0) jv = 0; q0 = e0 - 24 * (jv + 1); j = jv - jx; m = jx + jk; - for (i = 0; i <= m; i++, j++) f[i] = j < 0 ? 0.0 : static_cast(TrigfIpio2[j]); + for (i = 0; i <= m; i++, j++) f[i] = j < 0 ? 0.0 : static_cast(RemPio2Ipio2[j]); for (i = 0; i <= jk; i++) { for (j = 0, fw = 0.0; j <= jx; j++) fw += x[j] * f[jx + i - j]; q[i] = fw; @@ -906,7 +905,7 @@ recompute: for (k = 1; iq[jk - k] == 0; k++) { } for (i = jz + 1; i <= jz + k; i++) { - f[jx + i] = static_cast(TrigfIpio2[jv + i]); + f[jx + i] = static_cast(RemPio2Ipio2[jv + i]); for (j = 0, fw = 0.0; j <= jx; j++) fw += x[j] * f[jx + i - j]; q[i] = fw; } @@ -939,12 +938,17 @@ recompute: fw *= twon24; } for (i = jz; i >= 0; i--) { - for (fw = 0.0, k = 0; k <= jp && k <= jz - i; k++) fw += TrigfPio2Parts[k] * q[i + k]; + for (fw = 0.0, k = 0; k <= jp && k <= jz - i; k++) fw += RemPio2Parts[k] * q[i + k]; fq[jz - i] = fw; } fw = 0.0; for (i = jz; i >= 0; i--) fw += fq[i]; y[0] = ih == 0 ? fw : -fw; + if (prec != 0) { + fw = fq[0] - fw; + for (i = 1; i <= jz; i++) fw += fq[i]; + y[1] = ih == 0 ? fw : -fw; + } return n & 7; } @@ -969,7 +973,7 @@ static int TrigfRemPio2(float x, double* y) { const std::int32_t e0 = (ix >> 23) - 150; const float z = std::bit_cast(ix - static_cast(static_cast(e0) << 23)); double tx[1] = {z}, ty[1]; - const int n = TrigfKernelRemPio2(tx, ty, e0); + const int n = KernelRemPio2(tx, ty, e0, 1, 0); if (hx < 0) { *y = -ty[0]; return -n; @@ -1099,8 +1103,231 @@ void APS5_VABI sincosf_nid_postfix(float x, float* sinp, float* cosp) { *cosp = cosf_nid_postfix(x); } -double APS5_VABI sin_nid_postfix(double x) { return std::sin(x); } -double APS5_VABI cos_nid_postfix(double x) { return std::cos(x); } +static std::int32_t HighWord(double x) { + return static_cast(std::bit_cast(x) >> 32); +} + +static double ClearLowWord(double x) { + return std::bit_cast(std::bit_cast(x) & 0xffffffff00000000ull); +} + +static int RemPio2Near(double x, double* y, std::int32_t hx, int k) { + const double pio2_1 = 1.57079632673412561417e+00; + const double pio2_1t = 6.07710050650619224932e-11; + if (hx > 0) { + const double z = x - k * pio2_1; + y[0] = z - k * pio2_1t; + y[1] = (z - y[0]) - k * pio2_1t; + return k; + } + const double z = x + k * pio2_1; + y[0] = z + k * pio2_1t; + y[1] = (z - y[0]) + k * pio2_1t; + return -k; +} + +static int RemPio2(double x, double* y) { + const double two24 = 1.67772160000000000000e+07; + const double invpio2 = 6.36619772367581382433e-01; + const double pio2_1 = 1.57079632673412561417e+00; + const double pio2_1t = 6.07710050650619224932e-11; + const double pio2_2 = 6.07710050630396597660e-11; + const double pio2_2t = 2.02226624879595063154e-21; + const double pio2_3 = 2.02226624871116645580e-21; + const double pio2_3t = 8.47842766036889956997e-32; + const std::int32_t hx = HighWord(x); + const std::int32_t ix = hx & 0x7fffffff; + bool medium = false; + if (ix <= 0x400f6a7a) { + if ((ix & 0xfffff) != 0x921fb) { + return RemPio2Near(x, y, hx, ix <= 0x4002d97c ? 1 : 2); + } + medium = true; + } else if (ix <= 0x401c463b) { + if (ix <= 0x4015fdbc) { + if (ix != 0x4012d97c) { + return RemPio2Near(x, y, hx, 3); + } + } else if (ix != 0x401921fb) { + return RemPio2Near(x, y, hx, 4); + } + medium = true; + } + if (medium || ix < 0x413921fb) { + double fn = x * invpio2 + 0x1.8p52; + fn = fn - 0x1.8p52; + const std::int32_t n = static_cast(fn); + double r = x - fn * pio2_1; + double w = fn * pio2_1t; + const std::int32_t j = ix >> 20; + y[0] = r - w; + std::int32_t i = j - ((HighWord(y[0]) >> 20) & 0x7ff); + if (i > 16) { + double t = r; + w = fn * pio2_2; + r = t - w; + w = fn * pio2_2t - ((t - r) - w); + y[0] = r - w; + i = j - ((HighWord(y[0]) >> 20) & 0x7ff); + if (i > 49) { + t = r; + w = fn * pio2_3; + r = t - w; + w = fn * pio2_3t - ((t - r) - w); + y[0] = r - w; + } + } + y[1] = (r - y[0]) - w; + return n; + } + if (ix >= 0x7ff00000) { + y[0] = y[1] = x - x; + return 0; + } + const std::uint32_t low = static_cast(std::bit_cast(x)); + const std::int32_t e0 = (ix >> 20) - 1046; + double z = std::bit_cast((static_cast(static_cast(ix - (e0 << 20))) << 32) | low); + double tx[3]; + double ty[2]; + for (int i = 0; i < 2; i++) { + tx[i] = static_cast(static_cast(z)); + z = (z - tx[i]) * two24; + } + tx[2] = z; + int nx = 3; + while (tx[nx - 1] == 0.0) nx--; + const int n = KernelRemPio2(tx, ty, e0, nx, 1); + if (hx < 0) { + y[0] = -ty[0]; + y[1] = -ty[1]; + return -n; + } + y[0] = ty[0]; + y[1] = ty[1]; + return n; +} + +static double KernelSin(double x, double y, int iy) { + const double S1 = -1.66666666666666324348e-01; + const double S2 = 8.33333333332248946124e-03; + const double S3 = -1.98412698298579493134e-04; + const double S4 = 2.75573137070700676789e-06; + const double S5 = -2.50507602534068634195e-08; + const double S6 = 1.58969099521155010221e-10; + const double z = x * x; + const double w = z * z; + const double r = S2 + z * (S3 + z * S4) + z * w * (S5 + z * S6); + const double v = z * x; + if (iy == 0) { + return x + v * (S1 + z * r); + } + return x - ((z * (0.5 * y - v * r) - y) - v * S1); +} + +static double KernelCos(double x, double y) { + const double C1 = 4.16666666666666019037e-02; + const double C2 = -1.38888888888741095749e-03; + const double C3 = 2.48015872894767294178e-05; + const double C4 = -2.75573143513906633035e-07; + const double C5 = 2.08757232129817482790e-09; + const double C6 = -1.13596475577881948265e-11; + const double z = x * x; + double w = z * z; + const double r = z * (C1 + z * (C2 + z * C3)) + w * w * (C4 + z * (C5 + z * C6)); + const double hz = 0.5 * z; + w = 1.0 - hz; + return w + (((1.0 - w) - hz) + (z * r - x * y)); +} + +static double KernelTan(double x, double y, int iy) { + static constexpr double T[] = { + 3.33333333333334091986e-01, 1.33333333333201242699e-01, 5.39682539762260521377e-02, 2.18694882948595424599e-02, + 8.86323982359930005737e-03, 3.59207910759131235356e-03, 1.45620945432529025516e-03, 5.88041240820264096874e-04, + 2.46463134818469906812e-04, 7.81794442939557092300e-05, 7.14072491382608190305e-05, -1.85586374855275456654e-05, + 2.59073051863633712884e-05, + }; + const double pio4 = 7.85398163397448278999e-01; + const double pio4lo = 3.06161699786838301793e-17; + const std::int32_t hx = HighWord(x); + const std::int32_t ix = hx & 0x7fffffff; + if (ix >= 0x3fe59428) { + if (hx < 0) { + x = -x; + y = -y; + } + const double z = pio4 - x; + const double w = pio4lo - y; + x = z + w; + y = 0.0; + } + const double z = x * x; + double w = z * z; + double r = T[1] + w * (T[3] + w * (T[5] + w * (T[7] + w * (T[9] + w * T[11])))); + double v = z * (T[2] + w * (T[4] + w * (T[6] + w * (T[8] + w * (T[10] + w * T[12]))))); + double s = z * x; + r = y + z * (s * (r + v) + y); + r += T[0] * s; + w = x + r; + if (ix >= 0x3fe59428) { + v = static_cast(iy); + return static_cast(1 - ((hx >> 30) & 2)) * (v - 2.0 * (x - (w * w / (w + v) - r))); + } + if (iy == 1) { + return w; + } + const double head = ClearLowWord(w); + v = r - (head - x); + const double a = -1.0 / w; + const double t = ClearLowWord(a); + s = 1.0 + t * head; + return t + a * (s + t * v); +} + +double APS5_VABI sin_nid_postfix(double x) { + const std::int32_t ix = HighWord(x) & 0x7fffffff; + if (ix <= 0x3fe921fb) { + if (ix < 0x3e500000 && static_cast(x) == 0) { + return x; + } + return KernelSin(x, 0.0, 0); + } + if (ix >= 0x7ff00000) { + if (ix == 0x7ff00000) { + errno = EDOM; + } + return x - x; + } + double y[2]; + switch (RemPio2(x, y) & 3) { + case 0: return KernelSin(y[0], y[1], 1); + case 1: return KernelCos(y[0], y[1]); + case 2: return -KernelSin(y[0], y[1], 1); + default: return -KernelCos(y[0], y[1]); + } +} + +double APS5_VABI cos_nid_postfix(double x) { + const std::int32_t ix = HighWord(x) & 0x7fffffff; + if (ix <= 0x3fe921fb) { + if (ix < 0x3e46a09e && static_cast(x) == 0) { + return 1.0; + } + return KernelCos(x, 0.0); + } + if (ix >= 0x7ff00000) { + if (ix == 0x7ff00000) { + errno = EDOM; + } + return x - x; + } + double y[2]; + switch (RemPio2(x, y) & 3) { + case 0: return KernelCos(y[0], y[1]); + case 1: return -KernelSin(y[0], y[1], 1); + case 2: return -KernelCos(y[0], y[1]); + default: return KernelSin(y[0], y[1], 1); + } +} /*- * Copyright (c) 2011 David Schultz * All rights reserved. @@ -1149,6 +1376,25 @@ static double SinhLdexpExpHalf(double x) { * ==================================================== */ /* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_sinh.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_sinh.c */ + +double APS5_VABI tan_nid_postfix(double x) { + const std::int32_t ix = HighWord(x) & 0x7fffffff; + if (ix <= 0x3fe921fb) { + if (ix < 0x3e400000 && static_cast(x) == 0) { + return x; + } + return KernelTan(x, 0.0, 1); + } + if (ix >= 0x7ff00000) { + if (ix == 0x7ff00000) { + errno = EDOM; + } + return x - x; + } + double y[2]; + const int n = RemPio2(x, y); + return KernelTan(y[0], y[1], 1 - ((n & 1) << 1)); +} double APS5_VABI sinh_nid_postfix(double x) { const double one = 1.0; const double shuge = 1.0e307; diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 80f1217e86..76649b01c2 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -36,6 +36,100 @@ extern SonyDconst _LSnan_nid_postfix; extern int signgam_nid_postfix; +float APS5_VABI logbf_nid_postfix(float); +int APS5_VABI ilogb_nid_postfix(double); +int APS5_VABI ilogbf_nid_postfix(float); +double APS5_VABI exp2_nid_postfix(double); +float APS5_VABI __powisf2_nid_postfix(float, int); +double APS5_VABI __powidf2_nid_postfix(double, int); +double APS5_VABI ldexp_nid_postfix(double, int); +double APS5_VABI scalbn_nid_postfix(double, int); +float APS5_VABI scalbnf_nid_postfix(float, int); +float APS5_VABI nextafterf_nid_postfix(float, float); +double APS5_VABI nextafter_nid_postfix(double, double); +double APS5_VABI frexp_nid_postfix(double, int*); +float APS5_VABI frexpf_nid_postfix(float, int*); +std::int64_t APS5_VABI lround_nid_postfix(double); +std::div_t APS5_VABI div_nid_postfix(int, int); +double APS5_VABI sinh_nid_postfix(double); +double APS5_VABI cosh_nid_postfix(double); +double APS5_VABI _Cosh_nid_postfix(double, double); +float APS5_VABI coshf_nid_postfix(float); +float APS5_VABI sinhf_nid_postfix(float); +float APS5_VABI _FSinh_nid_postfix(float, float); +float APS5_VABI _FCosh_nid_postfix(float, float); +double APS5_VABI asinh_nid_postfix(double); +float APS5_VABI asinhf_nid_postfix(float); +double APS5_VABI acosh_nid_postfix(double); +float APS5_VABI acoshf_nid_postfix(float); +double APS5_VABI atanh_nid_postfix(double); +double APS5_VABI sin_nid_postfix(double); +double APS5_VABI cos_nid_postfix(double); +double APS5_VABI tan_nid_postfix(double); +float APS5_VABI atanhf_nid_postfix(float); +std::int64_t APS5_VABI lroundf_nid_postfix(float); +std::int64_t APS5_VABI llround_nid_postfix(double); +std::int64_t APS5_VABI llroundf_nid_postfix(float); +std::int64_t APS5_VABI llroundl_nid_postfix(long double); +int APS5_VABI __isfinitef_nid_postfix(float); +int APS5_VABI __isfinitel_nid_postfix(long double); +int APS5_VABI __isnormal_nid_postfix(double); +int APS5_VABI __isnormalf_nid_postfix(float); +int APS5_VABI __isinff_nid_postfix(float); +int APS5_VABI __isinf_nid_postfix(double); +int APS5_VABI isnan_nid_postfix(double); +int APS5_VABI isinf_nid_postfix(double); +std::lldiv_t APS5_VABI lldiv_nid_postfix(long long, long long); +std::lldiv_t APS5_VABI imaxdiv_nid_postfix(std::int64_t, std::int64_t); +std::int64_t APS5_VABI labs_nid_postfix(std::int64_t); +std::int64_t APS5_VABI llabs_nid_postfix(std::int64_t); +std::int64_t APS5_VABI imaxabs_nid_postfix(std::int64_t); +long double APS5_VABI scalbnl_nid_postfix(long double, int); +float APS5_VABI significandf_nid_postfix(float); +double APS5_VABI significand_nid_postfix(double); +long double APS5_VABI acoshl_nid_postfix(long double); +long double APS5_VABI asinhl_nid_postfix(long double); +long double APS5_VABI atanhl_nid_postfix(long double); +long double APS5_VABI atan2l_nid_postfix(long double, long double); +long double APS5_VABI cbrtl_nid_postfix(long double); +long double APS5_VABI erfl_nid_postfix(long double); +long double APS5_VABI erfcl_nid_postfix(long double); +long double APS5_VABI expm1l_nid_postfix(long double); +long double APS5_VABI log1pl_nid_postfix(long double); +long double APS5_VABI log2l_nid_postfix(long double); +long double APS5_VABI lgammal_nid_postfix(long double); +long double APS5_VABI tgammal_nid_postfix(long double); +float APS5_VABI tgammaf_nid_postfix(float); +long double APS5_VABI hypotl_nid_postfix(long double, long double); +float APS5_VABI hypot3f_nid_postfix(float, float, float); +double APS5_VABI hypot3_nid_postfix(double, double, double); +long double APS5_VABI hypot3l_nid_postfix(long double, long double, long double); +float APS5_VABI j0f_nid_postfix(float); +float APS5_VABI j1f_nid_postfix(float); +float APS5_VABI jnf_nid_postfix(int, float); +float APS5_VABI y0f_nid_postfix(float); +float APS5_VABI y1f_nid_postfix(float); +float APS5_VABI ynf_nid_postfix(int, float); +std::lldiv_t APS5_VABI ldiv_nid_postfix(std::int64_t, std::int64_t); +float APS5_VABI nanf_nid_postfix(const char*); +double APS5_VABI nan_nid_postfix(const char*); +long double APS5_VABI atanl_nid_postfix(long double); +long double APS5_VABI sinl_nid_postfix(long double); +long double APS5_VABI cosl_nid_postfix(long double); +long double APS5_VABI tanl_nid_postfix(long double); +long double APS5_VABI sinhl_nid_postfix(long double); +long double APS5_VABI coshl_nid_postfix(long double); +long double APS5_VABI tanhl_nid_postfix(long double); +long double APS5_VABI expl_nid_postfix(long double); +long double APS5_VABI exp2l_nid_postfix(long double); +long double APS5_VABI logl_nid_postfix(long double); +long double APS5_VABI log10l_nid_postfix(long double); +long double APS5_VABI powl_nid_postfix(long double, long double); +long double APS5_VABI ldexpl_nid_postfix(long double, int); +long double APS5_VABI truncl_nid_postfix(long double); +long double APS5_VABI nearbyintl_nid_postfix(long double); +double APS5_VABI logb_nid_postfix(double); +double APS5_VABI remainder_nid_postfix(double, double); struct DinkumwareFloatCharacteristics { int digits; int mantissaDigits; @@ -1038,6 +1132,50 @@ static void CheckExpm1ScalingRounding() { } _mm_setcsr(savedCsr); } +static void CheckTrigConsoleBits() { + constexpr struct { + std::uint64_t input; + std::uint64_t sine; + std::uint64_t cosine; + std::uint64_t tangent; + int error; + } cases[] = { + {0x0000000000000000ull, 0x0000000000000000ull, 0x3ff0000000000000ull, 0x0000000000000000ull, 0}, + {0x8000000000000000ull, 0x8000000000000000ull, 0x3ff0000000000000ull, 0x8000000000000000ull, 0}, + {0x3e3fffffffffffffull, 0x3e3fffffffffffffull, 0x3ff0000000000000ull, 0x3e3fffffffffffffull, 0}, + {0x3fe921fb54442d18ull, 0x3fe6a09e667f3bccull, 0x3fe6a09e667f3bcdull, 0x3fefffffffffffffull, 0}, + {0x3ff921fb54442d18ull, 0x3ff0000000000000ull, 0x3c91a62633145c07ull, 0x434d02967c31cdb5ull, 0}, + {0x400921fb54442d18ull, 0x3ca1a62633145c07ull, 0xbff0000000000000ull, 0xbca1a62633145c07ull, 0}, + {0xc00921fb54442d18ull, 0xbca1a62633145c07ull, 0xbff0000000000000ull, 0x3ca1a62633145c07ull, 0}, + {0x401921fb54442d18ull, 0xbcb1a62633145c07ull, 0x3ff0000000000000ull, 0xbcb1a62633145c07ull, 0}, + {0x4012d97c7f3321d2ull, 0xbff0000000000000ull, 0xbcaa79394c9e8a0aull, 0x4333570efd768923ull, 0}, + {0x412e848000000000ull, 0xbfd6664b2568d867ull, 0x3fedf9df9906d32cull, 0xbfd7e9768ab734c0ull, 0}, + {0x4139000000000000ull, 0x3fd065dff07cc910ull, 0xbfeeee8b0ce93021ull, 0xbfd0f6d7d15bc5b1ull, 0}, + {0x7e37e43c8800759cull, 0xbfea2c16b010e385ull, 0xbfe2699022adc4c1ull, 0x3ff6be411f37ac77ull, 0}, + {0x7fefffffffffffffull, 0x3f7452fc98b34e97ull, 0xbfefffe62ecfab75ull, 0xbf74530cfe729484ull, 0}, + {0xffefffffffffffffull, 0xbf7452fc98b34e97ull, 0xbfefffe62ecfab75ull, 0x3f74530cfe729484ull, 0}, + {0x7ff0000000000000ull, 0xfff8000000000000ull, 0xfff8000000000000ull, 0xfff8000000000000ull, EDOM}, + {0xfff0000000000000ull, 0xfff8000000000000ull, 0xfff8000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff0000054442d18ull, 0x7ff8000054442d18ull, 0x7ff8000054442d18ull, 0x7ff8000054442d18ull, EDOM}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, 0x7ff8000000000001ull, 0x7ff8000000000001ull, 0}, + {0x3fdada3603b109ebull, 0x3fda124ac129e754ull, 0x3fed397712faa550ull, 0x3fdc8c2adf939d0aull, 0}, + {0xbfba382e79c9f161ull, 0xbfba2c73e99f121cull, 0x3fefd51240491c33ull, 0xbfba4fc011ff9a31ull, 0}, + {0x4079ab389c31a606ull, 0x3fe7fbf5be09b71cull, 0xbfe52f138550db6full, 0xbff21d74a8b63e76ull, 0}, + {0xbfd6066a236944ecull, 0xbfd597c69592a402ull, 0x3fee1faa6b709b8bull, 0xbfd6f0152b51d55cull, 0}, + {0xbfcdf9356ad39d8full, 0xbfcdb34648619610ull, 0x3fef206c15f26ee6ull, 0xbfce889b662f705full, 0}, + {0x40557a01c7c97d05ull, 0xbfec45eb63779e2eull, 0xbfddf9b1098520ebull, 0x3ffe2ecb59bfb87eull, 0}, + }; + for (const auto& item : cases) { + const double x = std::bit_cast(item.input); + const int expected = item.error != 0 ? item.error : 1234; + errno = 1234; + Require(std::bit_cast(sin_nid_postfix(x)) == item.sine && errno == expected); + errno = 1234; + Require(std::bit_cast(cos_nid_postfix(x)) == item.cosine && errno == expected); + errno = 1234; + Require(std::bit_cast(tan_nid_postfix(x)) == item.tangent && errno == expected); + } +} static void CheckAsinl() { const struct { std::uint64_t inputMantissa; std::uint16_t inputSignExponent; std::uint64_t outputMantissa; std::uint16_t outputSignExponent; } cases[] = { @@ -2369,6 +2507,7 @@ int main() { CheckNextafterEnvironment(nextafterf_nid_postfix, 0x80000000u, 0x3f800000u, 0x00800000u, 0x7f7fffffu, 0x7f800000u); CheckNextafterEnvironment(nextafter_nid_postfix, std::uint64_t{0x8000000000000000ull}, std::uint64_t{0x3ff0000000000000ull}, std::uint64_t{0x0010000000000000ull}, std::uint64_t{0x7fefffffffffffffull}, std::uint64_t{0x7ff0000000000000ull}); CheckExpm1ScalingRounding(); + CheckTrigConsoleBits(); CheckAsinl(); Require(scalbn_nid_postfix(0.75, -2) == 0.1875); Require(scalbnf_nid_postfix(0.75f, 4) == 12.f); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 568e1931c1..9c7fe8feee 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -117,7 +117,8 @@ - [Guest path names](../../../core/libs/prx/libc/src/General.cpp) (libc) are checked against FreeBSD 11.4's `NAME_MAX` (255 bytes per component) and `MAXPATHLEN` (1024 bytes with the terminating NUL) before they are resolved, and a longer name fails with `ENAMETOOLONG`, as `namei` does. The host's own limit still applies below that: on Windows a path whose host form exceeds `MAX_PATH` fails with `ENOENT`. A backslash counts as a path separator for the component length, because the guest path resolver turns it into `/` before the host call, while FreeBSD treats it as an ordinary name character, so a 301-byte name that contains one is accepted here. `sceNgs2ParseWaveformFile` resolves its path without a try block, so a long component ends in an uncaught `filesystem_error` there where a missing file ended in a `runtime_error` (both end the title). A path through a regular file (`file/x`, `file/`) is not always `ENOTDIR` on the calls that resolve it. None of this was measured on a console. - [Guest path names](../../../core/libs/prx/libc/src/General.cpp) (libc) fail on macOS with `ENAMETOOLONG` once their host form, the root directory followed by the guest path, reaches 1024 bytes, so a guest path of 1023 bytes, which FreeBSD accepts, fails there under any root directory - [ftrylockfile](../../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - tries the host CRT stream lock that `flockfile`, `funlockfile` and each locking host stdio call on the stream take, so it returns -1 while another thread holds the stream through `flockfile` or is inside such a call, and 0 when it acquires the lock or already owns it (recursively, released by as many `funlockfile` calls), as FreeBSD 11 [`_ftrylockfile`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/_flock_stub.c#L89-L104) does. A libc export holds the lock only for each host call it makes, not across several host calls or while it updates the guest `FILE` prefix. Linux uses glibc `ftrylockfile`. UCRT has no try variant of `_lock_file`, so Windows calls `TryEnterCriticalSection` on the `CRITICAL_SECTION` that `_lock_file` enters, found through the private UCRT stream layout (`__crt_stdio_stream_data` in `ucrt/inc/corecrt_internal_stdio.h`, unchanged from Windows SDK 10.0.19041 to 10.0.26100); another CRT or a change of that layout breaks it. A null or closed stream throws where FreeBSD dereferences it -- [sinf, cosf, tanf and sincosf](../../../core/libs/prx/libc/src/Math.cpp) (libc) are FreeBSD msun's [`s_sinf.c`, `s_cosf.c` and `s_tanf.c`](https://github.com/freebsd/freebsd-src/tree/release/11.4.0/lib/msun/src) over its double-precision kernels and `e_rem_pio2f.c`, with `k_rem_pio2.c` (for `|x| >= 2^28 * pi/2`) ported only for one input word and `prec` 0; `sincosf` returns the two. As in the `libc.prx` that titles ship, only an infinite argument sets `errno` (`EDOM`); NaN leaves it unchanged. Floating-point exceptions are not compared. All four give the same bits and `errno` as the copies bundled with PPSA07923, PPSA05916 and PPSA13934 for all 2^32 arguments, run on a Windows host. +- [sin, cos and tan](../../../core/libs/prx/libc/src/Math.cpp) (libc) do not compare floating-point exceptions, and their double inputs are checked by sampling, not exhaustively. They are FreeBSD msun's [`s_sin.c`, `s_cos.c` and `s_tan.c`](https://github.com/freebsd/freebsd-src/tree/release/11.4.0/lib/msun/src) over its `k_sin.c`, `k_cos.c`, `k_tan.c`, `e_rem_pio2.c` and `k_rem_pio2.c`. As the titles' `libc.prx` does, they set `EDOM` when the argument's high word is `0x7ff00000` or `0xfff00000`, the infinities and the NaNs that share that high word, and leave `errno` alone for other NaNs. They give the same bits as the copies bundled with PPSA07923 and PPSA05916 on 320 million random and 69,316 structured arguments on a Windows host; the FMA-contracted build bundled with PPSA13934 differs on about 0.35% (`sin`, `cos`) and 0.7% (`tan`) of the random ones. +- [sinf, cosf, tanf and sincosf](../../../core/libs/prx/libc/src/Math.cpp) (libc) are FreeBSD msun's [`s_sinf.c`, `s_cosf.c` and `s_tanf.c`](https://github.com/freebsd/freebsd-src/tree/release/11.4.0/lib/msun/src) over its double-precision kernels and `e_rem_pio2f.c`, with `k_rem_pio2.c` (for `|x| >= 2^28 * pi/2`) shared with `sin`, `cos` and `tan`; `sincosf` returns the two. As in the `libc.prx` that titles ship, only an infinite argument sets `errno` (`EDOM`); NaN leaves it unchanged. Floating-point exceptions are not compared. All four give the same bits and `errno` as the copies bundled with PPSA07923, PPSA05916 and PPSA13934 for all 2^32 arguments, run on a Windows host. - [fgetln](../../../core/libs/prx/libc/src/StandardStreams.cpp) (libSceLibcInternal, implemented in libc) throws: in the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x5FE80) the export is a `hlt` instruction, which faults in user mode and ends the process - [Wide-character conversions](../../../core/libs/prx/libc/src/Multibyte.cpp) (libc) - The wide `wcsto*` functions parse the ASCII prefix of the guest string, as the C locale accepts only ASCII white space and digits. The multibyte conversions follow the C locale of the console's `libSceLibcInternal.sprx`: UTF-8 of at most three bytes, so `_Getmbcurmax` returns 3, overlong forms and encoded surrogates decode, and four-byte forms are invalid. `__mb_cur_max` stays 1 like the console's variable; `___mb_cur_max` is not in that module and also stays 1, unverified. Other locales are not supported. With a null destination, `wcstombs` returns the byte count; the console's count is wrong when the capacity argument equals the running count, because it reads past the terminator - [realpath](../../../core/libs/prx/libc/src/RealPath.cpp) (libc) follows FreeBSD 11's [`realpath.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/libc/stdlib/realpath.c) with two gaps. A host symbolic link with a relative target under the guest root is expanded as `realpath1` does (a dangling link fails with `ENOENT`, more than 32 links with `ELOOP`), but a link with an absolute target is not expanded, nor is anything below it, because the target is a host path with no guest path to map to. Only Windows symbolic links are expanded there, not junctions. Expansion works on the guest path, so a host link whose text climbs above a path alias root is resolved against the guest tree, not the host one. Otherwise `..` drops the previous component lexically, as `Resolve` does for every guest path. From a3b71d51a5cdc0c27e3dbafe81817c7dce8e4ac7 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Wed, 28 Oct 2026 12:12:15 -0400 Subject: [PATCH 088/612] test(libc): use canonical math export declarations --- core/libs/tests/GuestMath.cpp | 94 ----------------------------------- 1 file changed, 94 deletions(-) diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 76649b01c2..f317f91898 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -36,100 +36,6 @@ extern SonyDconst _LSnan_nid_postfix; extern int signgam_nid_postfix; -float APS5_VABI logbf_nid_postfix(float); -int APS5_VABI ilogb_nid_postfix(double); -int APS5_VABI ilogbf_nid_postfix(float); -double APS5_VABI exp2_nid_postfix(double); -float APS5_VABI __powisf2_nid_postfix(float, int); -double APS5_VABI __powidf2_nid_postfix(double, int); -double APS5_VABI ldexp_nid_postfix(double, int); -double APS5_VABI scalbn_nid_postfix(double, int); -float APS5_VABI scalbnf_nid_postfix(float, int); -float APS5_VABI nextafterf_nid_postfix(float, float); -double APS5_VABI nextafter_nid_postfix(double, double); -double APS5_VABI frexp_nid_postfix(double, int*); -float APS5_VABI frexpf_nid_postfix(float, int*); -std::int64_t APS5_VABI lround_nid_postfix(double); -std::div_t APS5_VABI div_nid_postfix(int, int); -double APS5_VABI sinh_nid_postfix(double); -double APS5_VABI cosh_nid_postfix(double); -double APS5_VABI _Cosh_nid_postfix(double, double); -float APS5_VABI coshf_nid_postfix(float); -float APS5_VABI sinhf_nid_postfix(float); -float APS5_VABI _FSinh_nid_postfix(float, float); -float APS5_VABI _FCosh_nid_postfix(float, float); -double APS5_VABI asinh_nid_postfix(double); -float APS5_VABI asinhf_nid_postfix(float); -double APS5_VABI acosh_nid_postfix(double); -float APS5_VABI acoshf_nid_postfix(float); -double APS5_VABI atanh_nid_postfix(double); -double APS5_VABI sin_nid_postfix(double); -double APS5_VABI cos_nid_postfix(double); -double APS5_VABI tan_nid_postfix(double); -float APS5_VABI atanhf_nid_postfix(float); -std::int64_t APS5_VABI lroundf_nid_postfix(float); -std::int64_t APS5_VABI llround_nid_postfix(double); -std::int64_t APS5_VABI llroundf_nid_postfix(float); -std::int64_t APS5_VABI llroundl_nid_postfix(long double); -int APS5_VABI __isfinitef_nid_postfix(float); -int APS5_VABI __isfinitel_nid_postfix(long double); -int APS5_VABI __isnormal_nid_postfix(double); -int APS5_VABI __isnormalf_nid_postfix(float); -int APS5_VABI __isinff_nid_postfix(float); -int APS5_VABI __isinf_nid_postfix(double); -int APS5_VABI isnan_nid_postfix(double); -int APS5_VABI isinf_nid_postfix(double); -std::lldiv_t APS5_VABI lldiv_nid_postfix(long long, long long); -std::lldiv_t APS5_VABI imaxdiv_nid_postfix(std::int64_t, std::int64_t); -std::int64_t APS5_VABI labs_nid_postfix(std::int64_t); -std::int64_t APS5_VABI llabs_nid_postfix(std::int64_t); -std::int64_t APS5_VABI imaxabs_nid_postfix(std::int64_t); -long double APS5_VABI scalbnl_nid_postfix(long double, int); -float APS5_VABI significandf_nid_postfix(float); -double APS5_VABI significand_nid_postfix(double); -long double APS5_VABI acoshl_nid_postfix(long double); -long double APS5_VABI asinhl_nid_postfix(long double); -long double APS5_VABI atanhl_nid_postfix(long double); -long double APS5_VABI atan2l_nid_postfix(long double, long double); -long double APS5_VABI cbrtl_nid_postfix(long double); -long double APS5_VABI erfl_nid_postfix(long double); -long double APS5_VABI erfcl_nid_postfix(long double); -long double APS5_VABI expm1l_nid_postfix(long double); -long double APS5_VABI log1pl_nid_postfix(long double); -long double APS5_VABI log2l_nid_postfix(long double); -long double APS5_VABI lgammal_nid_postfix(long double); -long double APS5_VABI tgammal_nid_postfix(long double); -float APS5_VABI tgammaf_nid_postfix(float); -long double APS5_VABI hypotl_nid_postfix(long double, long double); -float APS5_VABI hypot3f_nid_postfix(float, float, float); -double APS5_VABI hypot3_nid_postfix(double, double, double); -long double APS5_VABI hypot3l_nid_postfix(long double, long double, long double); -float APS5_VABI j0f_nid_postfix(float); -float APS5_VABI j1f_nid_postfix(float); -float APS5_VABI jnf_nid_postfix(int, float); -float APS5_VABI y0f_nid_postfix(float); -float APS5_VABI y1f_nid_postfix(float); -float APS5_VABI ynf_nid_postfix(int, float); -std::lldiv_t APS5_VABI ldiv_nid_postfix(std::int64_t, std::int64_t); -float APS5_VABI nanf_nid_postfix(const char*); -double APS5_VABI nan_nid_postfix(const char*); -long double APS5_VABI atanl_nid_postfix(long double); -long double APS5_VABI sinl_nid_postfix(long double); -long double APS5_VABI cosl_nid_postfix(long double); -long double APS5_VABI tanl_nid_postfix(long double); -long double APS5_VABI sinhl_nid_postfix(long double); -long double APS5_VABI coshl_nid_postfix(long double); -long double APS5_VABI tanhl_nid_postfix(long double); -long double APS5_VABI expl_nid_postfix(long double); -long double APS5_VABI exp2l_nid_postfix(long double); -long double APS5_VABI logl_nid_postfix(long double); -long double APS5_VABI log10l_nid_postfix(long double); -long double APS5_VABI powl_nid_postfix(long double, long double); -long double APS5_VABI ldexpl_nid_postfix(long double, int); -long double APS5_VABI truncl_nid_postfix(long double); -long double APS5_VABI nearbyintl_nid_postfix(long double); -double APS5_VABI logb_nid_postfix(double); -double APS5_VABI remainder_nid_postfix(double, double); struct DinkumwareFloatCharacteristics { int digits; int mantissaDigits; From fefc3f1d98bf93505a33c2b76fe653b8b5d730b6 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Wed, 28 Oct 2026 17:18:47 -0400 Subject: [PATCH 089/612] fix(libc): take strtok_s's C11 s1max argument as titles' libc.prx does The strtok_s that titles' libc.prx exports (NID -vXEQdRADLI, PPSA07923 and PPSA05916) is the C11 Annex K form strtok_s(s1, s1max, s2, ptr). main implemented the Microsoft form strtok_s(str, delim, context), so a title's s1max pointer was read as the delimiter string and its delimiters as the context. The function now takes s1max, stops within it, updates it, and reports "strtok_s: bad argument", "missing token" and "missing delimiter" through the constraint handler with EINVAL, as wcstok_s already does. libSceAmpr re-exports the same NID and changes with it. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAmpr/Export.cpp | 4 +- core/libs/prx/libSceAmpr/include/Api.hpp | 2 +- core/libs/prx/libc/include/Api.hpp | 2 +- core/libs/prx/libc/include/SonyCrt.hpp | 55 ++++++++++++++++++------ core/libs/prx/libc/src/Strings.cpp | 4 +- core/libs/tests/GuestBoundsChecking.cpp | 40 +++++++++++++++++ core/libs/tests/GuestCrt.cpp | 15 ++++--- 7 files changed, 96 insertions(+), 26 deletions(-) diff --git a/core/libs/prx/libSceAmpr/Export.cpp b/core/libs/prx/libSceAmpr/Export.cpp index 5dc10dec26..ace6adca7c 100644 --- a/core/libs/prx/libSceAmpr/Export.cpp +++ b/core/libs/prx/libSceAmpr/Export.cpp @@ -862,8 +862,8 @@ double APS5_VABI sceAmprSinh(double x, double y) { } APS5_EXPORT("-vXEQdRADLI", sceAmprStrtokS); -char* APS5_VABI sceAmprStrtokS(char* str, const char* delim, char** context) { - return SonyStrtokS(str, delim, context); +char* APS5_VABI sceAmprStrtokS(char* str, std::size_t* remaining, const char* delimiters, char** context) { + return SonyStrtokS(str, remaining, delimiters, context); } APS5_EXPORT("2UFh+YKfuzk", sceAmprCtimeS); diff --git a/core/libs/prx/libSceAmpr/include/Api.hpp b/core/libs/prx/libSceAmpr/include/Api.hpp index 8939494652..8fe39aecb4 100644 --- a/core/libs/prx/libSceAmpr/include/Api.hpp +++ b/core/libs/prx/libSceAmpr/include/Api.hpp @@ -142,7 +142,7 @@ void APS5_VABI _Lock_shared_ptr_spin_lock_nid_postfix(void); SonyLconv* APS5_VABI sceAmprLocaleconv(void); unsigned __int128 APS5_VABI sceAmprUdivti3(unsigned __int128 dividend, unsigned __int128 divisor); double APS5_VABI sceAmprSinh(double x, double y); -char* APS5_VABI sceAmprStrtokS(char* str, const char* delim, char** context); +char* APS5_VABI sceAmprStrtokS(char* str, std::size_t* remaining, const char* delimiters, char** context); int APS5_VABI sceAmprCtimeS(char* buffer, std::size_t size, const std::int64_t* time); int APS5_VABI sceAmprWcscpyS(std::uint16_t* dest, std::size_t size, const std::uint16_t* src); void* APS5_VABI sceAmprSetConstraintHandlerS(void* handler); diff --git a/core/libs/prx/libc/include/Api.hpp b/core/libs/prx/libc/include/Api.hpp index 593d7d1906..6c69b33689 100644 --- a/core/libs/prx/libc/include/Api.hpp +++ b/core/libs/prx/libc/include/Api.hpp @@ -1037,7 +1037,7 @@ int APS5_VABI _WStopfx_nid_postfix(const char16_t** cursor, char16_t** end); int APS5_VABI wcscoll_nid_postfix(const char16_t* first, const char16_t* second); size_t APS5_VABI wcsxfrm_nid_postfix(char16_t* destination, const char16_t* source, size_t count); size_t APS5_VABI strxfrm_nid_postfix(char* destination, const char* source, size_t count); -char* APS5_VABI strtok_s_nid_postfix(char* str, const char* delim, char** context); +char* APS5_VABI strtok_s_nid_postfix(char* str, std::size_t* remaining, const char* delimiters, char** context); int APS5_VABI wmemcpy_s_nid_postfix(char16_t* dest, size_t destsz, const char16_t* src, size_t count); int APS5_VABI wmemmove_s_nid_postfix(char16_t* dest, size_t destsz, const char16_t* src, size_t count); int APS5_VABI wcsncpy_s_nid_postfix(char16_t* dest, size_t destsz, const char16_t* src, size_t count); diff --git a/core/libs/prx/libc/include/SonyCrt.hpp b/core/libs/prx/libc/include/SonyCrt.hpp index fc97c753f1..b9aa61f11a 100644 --- a/core/libs/prx/libc/include/SonyCrt.hpp +++ b/core/libs/prx/libc/include/SonyCrt.hpp @@ -64,23 +64,50 @@ inline void SonyInvokeConstraintHandler(const char* message, void* pointer, int if (handler) handler(message, pointer, error); } -inline char* SonyStrtokS(char* str, const char* delim, char** context) { - if (!delim || !context) return nullptr; - char* cursor = str ? str : *context; - if (!cursor) return nullptr; - cursor += std::strspn(cursor, delim); - if (*cursor == '\0') { - *context = cursor; +inline char* SonyStrtokS(char* str, std::size_t* remaining, const char* delimiters, char** context) { + if (!remaining || !context || !delimiters || *remaining > (SIZE_MAX >> 3) || (!str && !*context)) { + SonyInvokeConstraintHandler("strtok_s: bad argument", nullptr, EINVAL); return nullptr; } - char* token = cursor; - cursor += std::strcspn(cursor, delim); - if (*cursor != '\0') { - *cursor = '\0'; - *context = cursor + 1; - } else { - *context = cursor; + if (!str) str = *context; + const auto isDelimiter = [delimiters](char value) { + for (const char* delimiter = delimiters; *delimiter != '\0'; ++delimiter) + if (*delimiter == value) return true; + return false; + }; + const std::size_t available = *remaining; + std::size_t skipped = 0; + if (available == 0) { + SonyInvokeConstraintHandler("strtok_s: missing token", nullptr, EINVAL); + return nullptr; } + while (str[skipped] != '\0' && isDelimiter(str[skipped])) { + if (++skipped == available) { + SonyInvokeConstraintHandler("strtok_s: missing token", nullptr, EINVAL); + return nullptr; + } + } + char* const token = str + skipped; + const std::size_t left = available - skipped; + *remaining = left; + if (*token == '\0') { + *context = token; + return nullptr; + } + std::size_t length = 0; + while (token[length] != '\0' && !isDelimiter(token[length])) { + if (++length == left) { + SonyInvokeConstraintHandler("strtok_s: missing delimiter", nullptr, EINVAL); + return nullptr; + } + } + char* next = token + length; + *remaining = left - length; + if (*next != '\0') { + *next++ = '\0'; + *remaining = left - length - 1; + } + *context = next; return token; } diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index 810324ecb6..b90fbc0a07 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -900,8 +900,8 @@ size_t APS5_VABI strxfrm_nid_postfix(char* destination, const char* source, size return length; } -char* APS5_VABI strtok_s_nid_postfix(char* str, const char* delim, char** context) { - return SonyStrtokS(str, delim, context); +char* APS5_VABI strtok_s_nid_postfix(char* str, std::size_t* remaining, const char* delimiters, char** context) { + return SonyStrtokS(str, remaining, delimiters, context); } int APS5_VABI wmemcpy_s_nid_postfix(char16_t* dest, size_t destsz, const char16_t* src, size_t count) { diff --git a/core/libs/tests/GuestBoundsChecking.cpp b/core/libs/tests/GuestBoundsChecking.cpp index fb7977eaa8..a9bc1a3b38 100644 --- a/core/libs/tests/GuestBoundsChecking.cpp +++ b/core/libs/tests/GuestBoundsChecking.cpp @@ -116,6 +116,45 @@ void CheckWcstok() { RequireViolation(before, "wcstok_s: bad argument", 22); } +void CheckStrtok() { + char text[] = ",,ab;c,,;"; + std::size_t remaining = sizeof(text); + char* context = nullptr; + char* token = strtok_s_nid_postfix(text, &remaining, ",;", &context); + Require(token == text + 2 && std::strcmp(token, "ab") == 0 && remaining == 5 && context == text + 5, "strtok_s did not return the first token"); + token = strtok_s_nid_postfix(nullptr, &remaining, ",;", &context); + Require(token == text + 5 && std::strcmp(token, "c") == 0 && remaining == 3 && context == text + 7, "strtok_s did not return the second token"); + token = strtok_s_nid_postfix(nullptr, &remaining, ",;", &context); + Require(token == nullptr && remaining == 1 && context == text + 9, "strtok_s did not stop at the end of the string"); + + int before = constraintCalls; + char unterminated[] = "abc"; + remaining = 2; + Require(strtok_s_nid_postfix(unterminated, &remaining, ",;", &context) == nullptr, "strtok_s read past s1max"); + RequireViolation(before, "strtok_s: missing delimiter", 22); + before = constraintCalls; + char separators[] = ",,,x"; + remaining = 3; + Require(strtok_s_nid_postfix(separators, &remaining, ",;", &context) == nullptr, "strtok_s skipped past s1max"); + RequireViolation(before, "strtok_s: missing token", 22); + before = constraintCalls; + remaining = 0; + Require(strtok_s_nid_postfix(separators, &remaining, ",;", &context) == nullptr, "strtok_s accepted a zero s1max"); + RequireViolation(before, "strtok_s: missing token", 22); + before = constraintCalls; + remaining = tooLarge; + Require(strtok_s_nid_postfix(separators, &remaining, ",;", &context) == nullptr, "strtok_s accepted an oversized s1max"); + RequireViolation(before, "strtok_s: bad argument", 22); + before = constraintCalls; + context = nullptr; + remaining = 4; + Require(strtok_s_nid_postfix(nullptr, &remaining, ",;", &context) == nullptr, "strtok_s accepted a null context string"); + RequireViolation(before, "strtok_s: bad argument", 22); + before = constraintCalls; + Require(strtok_s_nid_postfix(separators, &remaining, nullptr, &context) == nullptr, "strtok_s accepted null delimiters"); + RequireViolation(before, "strtok_s: bad argument", 22); +} + void CheckWideToMultibyte() { int status = 7; char bytes[4] = {'x', 'x', 'x', 'x'}; @@ -240,6 +279,7 @@ int main(int argc, char** argv) { CheckBsearch(); CheckWcstok(); + CheckStrtok(); CheckWideToMultibyte(); CheckMultibyteToWide(); CheckGets(argv[1]); diff --git a/core/libs/tests/GuestCrt.cpp b/core/libs/tests/GuestCrt.cpp index 1e8f1887f5..885adae800 100644 --- a/core/libs/tests/GuestCrt.cpp +++ b/core/libs/tests/GuestCrt.cpp @@ -201,14 +201,17 @@ int main() { char line[] = "a,b,,c"; char* context = nullptr; - Require(std::strcmp(sceAmprStrtokS(line, ",", &context), "a") == 0); - Require(std::strcmp(sceAmprStrtokS(nullptr, ",", &context), "b") == 0); - Require(std::strcmp(sceAmprStrtokS(nullptr, ",", &context), "c") == 0); - Require(sceAmprStrtokS(nullptr, ",", &context) == nullptr); - Require(sceAmprStrtokS(line, nullptr, &context) == nullptr); + std::size_t remaining = sizeof(line); + Require(std::strcmp(sceAmprStrtokS(line, &remaining, ",", &context), "a") == 0 && remaining == 5); + Require(std::strcmp(sceAmprStrtokS(nullptr, &remaining, ",", &context), "b") == 0 && remaining == 3); + Require(std::strcmp(sceAmprStrtokS(nullptr, &remaining, ",", &context), "c") == 0 && remaining == 1); + Require(sceAmprStrtokS(nullptr, &remaining, ",", &context) == nullptr); + g_handlerCalls = 0; + Require(sceAmprStrtokS(line, &remaining, nullptr, &context) == nullptr && g_handlerCalls == 1); char again[] = "x y"; char* again_context = nullptr; - Require(std::strcmp(strtok_s_nid_postfix(again, " ", &again_context), "x") == 0); + std::size_t again_remaining = sizeof(again); + Require(std::strcmp(strtok_s_nid_postfix(again, &again_remaining, " ", &again_context), "x") == 0 && again_remaining == 2); std::uint16_t wide[8] = {}; Require(sceAmprMbstowcs(wide, "ABC", 8) == 3); From 65ac15cd8fbb66f0228cf094eadd1ff65a631d38 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Wed, 28 Oct 2026 19:02:44 -0400 Subject: [PATCH 090/612] test(libc): cover more strtok_s argument and boundary cases A null s1max or ptr, a token that fills s1max, the largest valid s1max, and unchanged state after missing delimiter and missing token. Every expected value matches titles' libc.prx (PPSA07923, PPSA05916). Suggested by rooklz in review. Co-Authored-By: Claude Opus 5.5 --- core/libs/tests/GuestBoundsChecking.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/core/libs/tests/GuestBoundsChecking.cpp b/core/libs/tests/GuestBoundsChecking.cpp index a9bc1a3b38..250f86914f 100644 --- a/core/libs/tests/GuestBoundsChecking.cpp +++ b/core/libs/tests/GuestBoundsChecking.cpp @@ -153,6 +153,25 @@ void CheckStrtok() { before = constraintCalls; Require(strtok_s_nid_postfix(separators, &remaining, nullptr, &context) == nullptr, "strtok_s accepted null delimiters"); RequireViolation(before, "strtok_s: bad argument", 22); + before = constraintCalls; + Require(strtok_s_nid_postfix(separators, nullptr, ",;", &context) == nullptr, "strtok_s accepted a null s1max"); + RequireViolation(before, "strtok_s: bad argument", 22); + before = constraintCalls; + Require(strtok_s_nid_postfix(separators, &remaining, ",;", nullptr) == nullptr && remaining == 4, "strtok_s accepted a null ptr"); + RequireViolation(before, "strtok_s: bad argument", 22); + before = constraintCalls; + char exact[] = "ab,"; + remaining = 2; + context = nullptr; + Require(strtok_s_nid_postfix(exact, &remaining, ",;", &context) == nullptr && remaining == 2 && context == nullptr, "strtok_s accepted a token that fills s1max"); + RequireViolation(before, "strtok_s: missing delimiter", 22); + before = constraintCalls; + char largest[] = ",x"; + remaining = tooLarge - 1; + Require(strtok_s_nid_postfix(largest, &remaining, ",;", &context) == largest + 1 && remaining == tooLarge - 3 && context == largest + 2 && constraintCalls == before, "strtok_s rejected the largest s1max"); + remaining = 3; + Require(strtok_s_nid_postfix(separators, &remaining, ",;", &context) == nullptr && remaining == 3 && context == largest + 2, "strtok_s changed its state on a missing token"); + RequireViolation(before, "strtok_s: missing token", 22); } void CheckWideToMultibyte() { From df74f29c46e465ab1efde4b4e08a94bcfba4863f Mon Sep 17 00:00:00 2001 From: Jamie <55252868+chitters2004@users.noreply.github.com> Date: Thu, 29 Oct 2026 02:48:07 +0000 Subject: [PATCH 091/612] fix(agc): relaunch the frame replay when a host region blocks a captured GPU range On Windows the startup thread stacks can sit inside a captured GPU-visible range, which the replay must map at its captured address. Start the replay again with a fresh address-space layout, up to 8 times, before mapping any memory. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceAgcDriver/tools/FrameReplay.cpp | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp index 0ddce118db..96742f9df0 100644 --- a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp @@ -714,9 +714,42 @@ FrameCheck compareFrames(const FrameCaptureReader& reader, const Options& option return check; } +#ifdef _WIN32 +void relaunchIfHostRegionBlocks(const FrameCaptureReader& reader) { + constexpr int Attempts = 8; + const auto& chunks = reader.Chunks(); + const auto blocked = std::find_if(chunks.begin(), chunks.end(), [](const FrameMemoryChunk& chunk) { + return chunk.kind == FrameMemoryKind::Range && chunk.rangeKind != FrameRangeKind::Host && (chunk.access & FrameRangeAccess::Gpu) != 0 && + GuestArena::GuestArenaHostRegionOverlaps_nid_postfix(static_cast(chunk.address), static_cast(chunk.bytes)); + }); + if (blocked == chunks.end()) return; + const char* const previous = std::getenv("APS5_FRAME_REPLAY_ATTEMPT"); + const int attempt = previous == nullptr ? 1 : std::atoi(previous) + 1; + if (attempt > Attempts) return; + std::fprintf(stderr, "[frame-replay] a host region sits in the captured GPU range 0x%llx+0x%llx; relaunching (%d of %d)\n", static_cast(blocked->address), + static_cast(blocked->bytes), attempt, Attempts); + std::fflush(stdout); + std::fflush(stderr); + _putenv_s("APS5_FRAME_REPLAY_ATTEMPT", std::to_string(attempt).c_str()); + std::wstring commandLine = GetCommandLineW(); + STARTUPINFOW startup{}; + startup.cb = sizeof(startup); + PROCESS_INFORMATION process{}; + if (!CreateProcessW(nullptr, commandLine.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup, &process)) throw std::runtime_error("frame replay: cannot relaunch, error " + std::to_string(GetLastError())); + CloseHandle(process.hThread); + WaitForSingleObject(process.hProcess, INFINITE); + DWORD code = 2; + GetExitCodeProcess(process.hProcess, &code); + std::_Exit(static_cast(code)); +} +#endif + int run(const Options& options) { const auto loadStart = Clock::now(); const FrameCaptureReader reader(options.directory); +#ifdef _WIN32 + relaunchIfHostRegionBlocks(reader); +#endif if (reader.Truncated()) std::fprintf(stderr, "[frame-replay] the capture is truncated; replaying the complete records\n"); auto skipped = restoreMemory(reader); const auto loaded = Clock::now(); From 89db2b5ae2b0b99fac51742f73cbab3513631bbf Mon Sep 17 00:00:00 2001 From: Jamie <55252868+chitters2004@users.noreply.github.com> Date: Thu, 29 Oct 2026 02:55:10 +0000 Subject: [PATCH 092/612] docs(env): list APS5_FRAME_REPLAY_ATTEMPT Co-Authored-By: Claude Opus 5.5 --- docs/dev/ENVIRONMENT.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index 05678c738d..c7f6898408 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -170,6 +170,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_FRAME_CAPTURE_AT_FLIP` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp` | 0 | frame boundary (CPU flip or submission that flips) at which the frame capture starts when no trigger file is set; 0 and 1 both start at the first boundary; a value that is not a number fails | | `APS5_FRAME_CAPTURE_FRAMES` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp` | 10 | number of frame boundaries the frame capture records | | `APS5_FRAME_CAPTURE_TRIGGER` | string | `core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp` | | file whose appearance starts the frame capture at the next frame boundary; a number in it replaces the frame count | +| `APS5_FRAME_REPLAY_ATTEMPT` | integer | `core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp` | 0 | number of times `agc_frame_replay` has started itself again on Windows because a host region sat in a captured GPU-visible range; set by the replay for the process it starts, which stops relaunching after 8 | | `APS5_FULL_BARRIERS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | records every barrier instead of merging redundant ones | | `APS5_GPU_CHECKPOINTS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | attaches device diagnostic checkpoints through VK_NV_device_diagnostic_checkpoints, or buffer markers through VK_AMD_buffer_marker on a device without it | | `APS5_GPU_COPY_MAX_KIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | 1024 | size cap in KiB on a descriptor sub-range copied through the GPU path, larger ones staying on the CPU | From 7a3cbd6b6e6aa1d07f129f335ab9c56e0382b0f2 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Fri, 23 Oct 2026 11:11:57 -0400 Subject: [PATCH 093/612] fix(libc): compute log with FreeBSD msun's algorithm, as titles' libc.prx does log returned the host's log, whose last bit differs from the titles' copy on Windows on 3,020,553 of 320 million random arguments. The titles' copy is FreeBSD msun's e_log.c, and it sets ERANGE for +-0 and EDOM for every argument with the sign bit set, msun's two error returns. log now computes that algorithm and sets errno there. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Math.cpp | 77 +++++++++++++++++++- core/libs/tests/GuestMath.cpp | 120 ++++++++++++++++++++++++++++++++ docs/dev/debt/libc.md | 1 + 3 files changed, 197 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 234c2b945f..48f8a4358f 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -620,7 +620,82 @@ double APS5_VABI exp_nid_postfix(double x) { return std::exp(x); } double APS5_VABI atan_nid_postfix(double x) { return std::atan(x); } double APS5_VABI tan_nid_postfix(double x) { return std::tan(x); } double APS5_VABI log2_nid_postfix(double x) { return std::log2(x); } -double APS5_VABI log_nid_postfix(double x) { return std::log(x); } +double APS5_VABI log_nid_postfix(double x) { + const double ln2_hi = 6.93147180369123816490e-01; + const double ln2_lo = 1.90821492927058770002e-10; + const double two54 = 1.80143985094819840000e+16; + const double Lg1 = 6.666666666666735130e-01; + const double Lg2 = 3.999999999940941908e-01; + const double Lg3 = 2.857142874366239149e-01; + const double Lg4 = 2.222219843214978396e-01; + const double Lg5 = 1.818357216161805012e-01; + const double Lg6 = 1.531383769920937332e-01; + const double Lg7 = 1.479819860511658591e-01; + static volatile double vzero = 0.0; + std::uint64_t bits = std::bit_cast(x); + std::int32_t hx = static_cast(bits >> 32); + const std::uint32_t lx = static_cast(bits); + std::int32_t k = 0; + if (hx < 0x00100000) { + if (((hx & 0x7fffffff) | lx) == 0) { + errno = ERANGE; + return -two54 / vzero; + } + if (hx < 0) { + errno = EDOM; + return (x - x) / 0.0; + } + k -= 54; + x *= two54; + bits = std::bit_cast(x); + hx = static_cast(bits >> 32); + } + if (hx >= 0x7ff00000) { + return x + x; + } + k += (hx >> 20) - 1023; + hx &= 0x000fffff; + std::int32_t i = (hx + 0x95f64) & 0x100000; + x = std::bit_cast((static_cast(static_cast(hx | (i ^ 0x3ff00000))) << 32) | (bits & 0xffffffffu)); + k += i >> 20; + const double f = x - 1.0; + if ((0x000fffff & (2 + hx)) < 3) { + if (f == 0.0) { + if (k == 0) { + return 0.0; + } + const double dk = k; + return dk * ln2_hi + dk * ln2_lo; + } + const double R = f * f * (0.5 - 0.33333333333333333 * f); + if (k == 0) { + return f - R; + } + const double dk = k; + return dk * ln2_hi - ((R - dk * ln2_lo) - f); + } + const double s = f / (2.0 + f); + const double dk = k; + const double z = s * s; + i = hx - 0x6147a; + const double w = z * z; + const std::int32_t j = 0x6b851 - hx; + const double t1 = w * (Lg2 + w * (Lg4 + w * Lg6)); + const double t2 = z * (Lg1 + w * (Lg3 + w * (Lg5 + w * Lg7))); + i |= j; + const double R = t2 + t1; + if (i > 0) { + const double hfsq = 0.5 * f * f; + if (k == 0) { + return f - (hfsq - s * (hfsq + R)); + } + return dk * ln2_hi - ((hfsq - (s * (hfsq + R) + dk * ln2_lo)) - f); + } + if (k == 0) { + return f - s * (f - R); + } + return dk * ln2_hi - ((s * (f - R) - dk * ln2_lo) - f); +} double APS5_VABI log1p_nid_postfix(double x) { const double ln2_hi = 6.93147180369123816490e-01; const double ln2_lo = 1.90821492927058770002e-10; diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index a738cc6992..34c26f37b8 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -37,6 +37,99 @@ extern SonyDconst _LSnan_nid_postfix; extern int signgam_nid_postfix; +float APS5_VABI logbf_nid_postfix(float); +int APS5_VABI ilogb_nid_postfix(double); +int APS5_VABI ilogbf_nid_postfix(float); +double APS5_VABI exp2_nid_postfix(double); +float APS5_VABI __powisf2_nid_postfix(float, int); +double APS5_VABI __powidf2_nid_postfix(double, int); +double APS5_VABI ldexp_nid_postfix(double, int); +double APS5_VABI scalbn_nid_postfix(double, int); +float APS5_VABI scalbnf_nid_postfix(float, int); +float APS5_VABI nextafterf_nid_postfix(float, float); +double APS5_VABI nextafter_nid_postfix(double, double); +double APS5_VABI frexp_nid_postfix(double, int*); +float APS5_VABI frexpf_nid_postfix(float, int*); +float APS5_VABI logf_nid_postfix(float); +std::int64_t APS5_VABI lround_nid_postfix(double); +std::div_t APS5_VABI div_nid_postfix(int, int); +double APS5_VABI sinh_nid_postfix(double); +double APS5_VABI cosh_nid_postfix(double); +double APS5_VABI _Cosh_nid_postfix(double, double); +float APS5_VABI coshf_nid_postfix(float); +float APS5_VABI sinhf_nid_postfix(float); +float APS5_VABI _FSinh_nid_postfix(float, float); +float APS5_VABI _FCosh_nid_postfix(float, float); +double APS5_VABI asinh_nid_postfix(double); +float APS5_VABI asinhf_nid_postfix(float); +double APS5_VABI acosh_nid_postfix(double); +float APS5_VABI acoshf_nid_postfix(float); +double APS5_VABI atanh_nid_postfix(double); +double APS5_VABI log_nid_postfix(double); +float APS5_VABI atanhf_nid_postfix(float); +std::int64_t APS5_VABI lroundf_nid_postfix(float); +std::int64_t APS5_VABI llround_nid_postfix(double); +std::int64_t APS5_VABI llroundf_nid_postfix(float); +std::int64_t APS5_VABI llroundl_nid_postfix(long double); +int APS5_VABI __isfinitef_nid_postfix(float); +int APS5_VABI __isfinitel_nid_postfix(long double); +int APS5_VABI __isnormal_nid_postfix(double); +int APS5_VABI __isnormalf_nid_postfix(float); +int APS5_VABI __isinff_nid_postfix(float); +int APS5_VABI __isinf_nid_postfix(double); +int APS5_VABI isnan_nid_postfix(double); +int APS5_VABI isinf_nid_postfix(double); +std::lldiv_t APS5_VABI lldiv_nid_postfix(long long, long long); +std::lldiv_t APS5_VABI imaxdiv_nid_postfix(std::int64_t, std::int64_t); +std::int64_t APS5_VABI labs_nid_postfix(std::int64_t); +std::int64_t APS5_VABI llabs_nid_postfix(std::int64_t); +std::int64_t APS5_VABI imaxabs_nid_postfix(std::int64_t); +long double APS5_VABI scalbnl_nid_postfix(long double, int); +float APS5_VABI significandf_nid_postfix(float); +double APS5_VABI significand_nid_postfix(double); +long double APS5_VABI acoshl_nid_postfix(long double); +long double APS5_VABI asinhl_nid_postfix(long double); +long double APS5_VABI atanhl_nid_postfix(long double); +long double APS5_VABI atan2l_nid_postfix(long double, long double); +long double APS5_VABI cbrtl_nid_postfix(long double); +long double APS5_VABI erfl_nid_postfix(long double); +long double APS5_VABI erfcl_nid_postfix(long double); +long double APS5_VABI expm1l_nid_postfix(long double); +long double APS5_VABI log1pl_nid_postfix(long double); +long double APS5_VABI log2l_nid_postfix(long double); +long double APS5_VABI lgammal_nid_postfix(long double); +long double APS5_VABI tgammal_nid_postfix(long double); +float APS5_VABI tgammaf_nid_postfix(float); +long double APS5_VABI hypotl_nid_postfix(long double, long double); +float APS5_VABI hypot3f_nid_postfix(float, float, float); +double APS5_VABI hypot3_nid_postfix(double, double, double); +long double APS5_VABI hypot3l_nid_postfix(long double, long double, long double); +float APS5_VABI j0f_nid_postfix(float); +float APS5_VABI j1f_nid_postfix(float); +float APS5_VABI jnf_nid_postfix(int, float); +float APS5_VABI y0f_nid_postfix(float); +float APS5_VABI y1f_nid_postfix(float); +float APS5_VABI ynf_nid_postfix(int, float); +std::lldiv_t APS5_VABI ldiv_nid_postfix(std::int64_t, std::int64_t); +float APS5_VABI nanf_nid_postfix(const char*); +double APS5_VABI nan_nid_postfix(const char*); +long double APS5_VABI atanl_nid_postfix(long double); +long double APS5_VABI sinl_nid_postfix(long double); +long double APS5_VABI cosl_nid_postfix(long double); +long double APS5_VABI tanl_nid_postfix(long double); +long double APS5_VABI sinhl_nid_postfix(long double); +long double APS5_VABI coshl_nid_postfix(long double); +long double APS5_VABI tanhl_nid_postfix(long double); +long double APS5_VABI expl_nid_postfix(long double); +long double APS5_VABI exp2l_nid_postfix(long double); +long double APS5_VABI logl_nid_postfix(long double); +long double APS5_VABI log10l_nid_postfix(long double); +long double APS5_VABI powl_nid_postfix(long double, long double); +long double APS5_VABI ldexpl_nid_postfix(long double, int); +long double APS5_VABI truncl_nid_postfix(long double); +long double APS5_VABI nearbyintl_nid_postfix(long double); +double APS5_VABI logb_nid_postfix(double); +double APS5_VABI remainder_nid_postfix(double, double); struct DinkumwareFloatCharacteristics { int digits; int mantissaDigits; @@ -1163,6 +1256,32 @@ static void CheckExpm1ScalingRounding() { } _mm_setcsr(savedCsr); } +static void CheckLogConsoleBits() { + constexpr struct { + std::uint64_t input; + std::uint64_t expected; + int error; + } cases[] = { + {0x0000000000000000ull, 0xfff0000000000000ull, ERANGE}, {0x8000000000000000ull, 0xfff0000000000000ull, ERANGE}, + {0x0000000000000001ull, 0xc0874385446d71c3ull, 0}, {0x000fffffffffffffull, 0xc086232bdd7abcd2ull, 0}, + {0x0010000000000000ull, 0xc086232bdd7abcd2ull, 0}, {0x3ff0000000000000ull, 0x0000000000000000ull, 0}, + {0x3fefffffffffffffull, 0xbca0000000000000ull, 0}, {0x3ff0000000000001ull, 0x3cafffffffffffffull, 0}, + {0x3ff00000fffffffeull, 0x3eaffffeffc00aafull, 0}, {0x3ff6a09e00000000ull, 0x3fd62e41dd1274ceull, 0}, + {0x3fe6a09e00000000ull, 0xbfd62e4420e1ff10ull, 0}, {0x4000000000000000ull, 0x3fe62e42fefa39efull, 0}, + {0x4024000000000000ull, 0x40026bb1bbb55516ull, 0}, {0x7fefffffffffffffull, 0x40862e42fefa39efull, 0}, + {0x7ff0000000000000ull, 0x7ff0000000000000ull, 0}, {0xbff0000000000000ull, 0xfff8000000000000ull, EDOM}, + {0xfff0000000000000ull, 0xfff8000000000000ull, EDOM}, {0xbfe0000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, 0}, {0xfff8000000000002ull, 0xfff8000000000002ull, EDOM}, + {0x3fcbf0cfff3ac077ull, 0xbff859e4e1f045dcull, 0}, {0x3f08b0c25bca18b0ull, 0xc023ed400c16d9eeull, 0}, + {0x402d5e682f6c8da9ull, 0x40057e89190b1e8eull, 0}, {0x40881f30b22704c7ull, 0x401a986d082edef0ull, 0}, + {0x4025398ccfd724ccull, 0x4002e56c809c7c6aull, 0}, {0x401231605052b9ceull, 0x3ff83c5b860d2a7eull, 0}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(log_nid_postfix(std::bit_cast(item.input))) == item.expected); + Require(errno == (item.error != 0 ? item.error : 1234)); + } +} static void CheckAsinl() { const struct { std::uint64_t inputMantissa; std::uint16_t inputSignExponent; std::uint64_t outputMantissa; std::uint16_t outputSignExponent; } cases[] = { @@ -2510,6 +2629,7 @@ int main() { CheckNextafterEnvironment(nextafterf_nid_postfix, 0x80000000u, 0x3f800000u, 0x00800000u, 0x7f7fffffu, 0x7f800000u); CheckNextafterEnvironment(nextafter_nid_postfix, std::uint64_t{0x8000000000000000ull}, std::uint64_t{0x3ff0000000000000ull}, std::uint64_t{0x0010000000000000ull}, std::uint64_t{0x7fefffffffffffffull}, std::uint64_t{0x7ff0000000000000ull}); CheckExpm1ScalingRounding(); + CheckLogConsoleBits(); CheckAsinl(); Require(scalbn_nid_postfix(0.75, -2) == 0.1875); Require(scalbnf_nid_postfix(0.75f, 4) == 12.f); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index c2c1f668a4..f28ce61ced 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -147,6 +147,7 @@ - [log1p](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`s_log1p.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_log1p.c). As the titles' `libc.prx` does, it sets `EDOM` at msun's NaN return (arguments below -1, `-inf` and negative NaNs) and leaves `errno` alone for `log1p(-1)`, where it returns `-inf`. It gives the same bits, NaN payloads included, and `errno` as the `log1p` bundled with PPSA07923 and PPSA05916 on 320 million random and 68,216 structured arguments on a Windows host. The FMA-contracted build bundled with PPSA13934 differs on 73,682 of the random ones. - [nextafter and nextafterf](../../../core/libs/prx/libc/src/Math.cpp) (libc) have no physical-console or unmasked-exception validation. A Linux CPU probe compares result bits, `errno` and masked floating-point flags with the unchanged math code in FAR: Changing Tides PPSA03766 bundled `libc.prx` (`nextafter` 0x51b30, `nextafterf` 0x51c80, `_Feraise` 0x11da0): 16,864 calls across both precisions, four rounding modes and all DAZ/FTZ combinations, not exhaustive inputs. Earlier Windows sampling of eleven bundled libraries compares only bits and `errno`; exception behavior outside the FAR module remains unverified. - [tanh](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`s_tanh.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_tanh.c) over this file's msun `Expm1`, with `tiny` read at run time; it sets no `errno`, as the titles' `libc.prx` does not. It gives the same bits as the `tanh` bundled with PPSA07923 and PPSA05916 on 16 million random and 68,211 structured arguments on a Windows host; the FMA-contracted build bundled with PPSA13934 differs on 1,054 of them. +- [log](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`e_log.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_log.c). As the titles' `libc.prx` does, it sets `ERANGE` for ±0 and `EDOM` for every argument with the sign bit set (negatives, `-inf` and negative NaNs), which are exactly msun's two error returns. It gives the same bits, NaN payloads included, and `errno` as the `log` bundled with PPSA07923 and PPSA05916 on 320 million random and 20,159 structured arguments on a Windows host. The FMA-contracted build bundled with PPSA13934 differs on 262,780 of the random ones. - [sinhf](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows the `sinhf` of the `libc.prx` bundled with PPSA28319 (disassembly): FreeBSD msun's [`e_sinhf.c`](https://github.com/freebsd/freebsd-src/blob/5aa554c7e5b6fd9042986df07f12ac5687e41392/lib/msun/src/e_sinhf.c) from before `__ldexp_expf`, which squares `expf(|x| / 2)` between `logf(FLT_MAX)` and the overflow threshold, calling msun's [`expm1f`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/s_expm1f.c) and [`expf`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/e_expf.c), whose paths for the arguments it passes are inlined here. Past the threshold Sony's error helper sets `errno` to `ERANGE` before returning `x * 1e37`; msun itself sets no `errno`. That code, run on a Windows host, gives the same bits as this implementation for every finite argument up to the threshold, of both signs; it was not run on Linux. That the system `libSceLibcInternal`, which Ruffle (PPSA68091) and RetroArch (PPSA99169) import while bundling a zero-return `libc.prx`, has the same code is assumed - [acoshf](../../../core/libs/prx/libc/src/Math.cpp) (libc) exact system `libSceLibcInternal` behavior and FMA-contracted copies remain unverified. Positive finite inputs use [msun](https://github.com/freebsd/freebsd-src/blob/5aa554c7e5b6fd9042986df07f12ac5687e41392/lib/msun/src/e_acoshf.c) range handling and this file's `logf`; the middle range divides -1 before addition under directed rounding. Bits, errno and SSE/x87 status and controls match the supplied PPSA03766/PPSA07923 copies on 236,592 positive finite calls (4,929 inputs, four rounding modes, DAZ/FTZ combinations and sticky flags); these samples are not exhaustive. Invalid-domain errno and SSE/x87 invalid flags are checked against two supplied bundled copies; those samples are not exhaustive either. - [acoshl, asinhl, atanhl, atan2l, cbrtl, erfl, erfcl, expm1l, hypotl, log1pl, log2l, lgammal, tgammal, j0f, j1f, jnf, y0f, y1f and ynf](../../../core/libs/prx/libc/src/Math.cpp) (libc) return the host C library's results (the float Bessel functions through its double ones), not those of the console's own code, so the last bits can differ (`y0f`, `y1f`, `ynf` follow the console at `+-0`). `tgammaf` delegates to the host's double `tgamma` as FreeBSD 11 does ([`s_tgammaf.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/msun/src/s_tgammaf.c)), then restores the sign of a zero result for negative non-integer inputs from the parity of `floor(-x)`; nine title-bundled `libc.prx` builds return that sign for underflowing inputs, where Windows UCRT can return the opposite sign ([#9707](https://github.com/boykopovar/AnyPS5/issues/9707)). As in the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x56410), `lgammal` does not store the sign in `signgam`; `hypotl` returns infinity for an infinite argument even when the other is a NaN, as C11 F.10.4.3 requires. [hypot3, hypot3f and hypot3l](../../../core/libs/prx/libc/src/Math.cpp) follow that module (0x51D30, 0x520D0, 0x52420): the first NaN of x, y and z is returned unchanged, any infinity gives positive infinity, a zero argument gives the two-argument hypot of the other two, and otherwise the largest magnitude is combined with the hypot of the other two From 1684101611cbe8d2db1d34f2acf0dad41eb6183e Mon Sep 17 00:00:00 2001 From: JJenkx Date: Wed, 28 Oct 2026 05:59:00 -0400 Subject: [PATCH 094/612] fix(libc): raise the titles' flags from log at its special inputs Titles' libc.prx raises FE_INVALID wherever log stores EDOM, a NaN argument included, where (x - x) / zero raised nothing for a NaN operand. The domain path now raises through _Feraise, which stores EDOM and raises the flag in both units. Results and errno are unchanged. CheckLogEdgeFlags covers it. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Math.cpp | 2 +- core/libs/tests/GuestMath.cpp | 63 +++++++++++++++++++-------------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 48f8a4358f..40d139ceaf 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -642,7 +642,7 @@ double APS5_VABI log_nid_postfix(double x) { return -two54 / vzero; } if (hx < 0) { - errno = EDOM; + _Feraise_nid_postfix(0x01); return (x - x) / 0.0; } k -= 54; diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 34c26f37b8..35cb0eadf1 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -859,6 +859,40 @@ static long double MakeExtended(std::uint64_t mantissa, std::uint16_t signExpone return value; } +static void CheckLogEdgeFlags() { + for (const double input : {-1.0, -std::numeric_limits::infinity(), -std::numeric_limits::quiet_NaN()}) { + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + Require(std::isnan(log_nid_postfix(input)) && errno == EDOM && std::fetestexcept(FE_INVALID) != 0); + } +} +static void CheckLogConsoleBits() { + constexpr struct { + std::uint64_t input; + std::uint64_t expected; + int error; + } cases[] = { + {0x0000000000000000ull, 0xfff0000000000000ull, ERANGE}, {0x8000000000000000ull, 0xfff0000000000000ull, ERANGE}, + {0x0000000000000001ull, 0xc0874385446d71c3ull, 0}, {0x000fffffffffffffull, 0xc086232bdd7abcd2ull, 0}, + {0x0010000000000000ull, 0xc086232bdd7abcd2ull, 0}, {0x3ff0000000000000ull, 0x0000000000000000ull, 0}, + {0x3fefffffffffffffull, 0xbca0000000000000ull, 0}, {0x3ff0000000000001ull, 0x3cafffffffffffffull, 0}, + {0x3ff00000fffffffeull, 0x3eaffffeffc00aafull, 0}, {0x3ff6a09e00000000ull, 0x3fd62e41dd1274ceull, 0}, + {0x3fe6a09e00000000ull, 0xbfd62e4420e1ff10ull, 0}, {0x4000000000000000ull, 0x3fe62e42fefa39efull, 0}, + {0x4024000000000000ull, 0x40026bb1bbb55516ull, 0}, {0x7fefffffffffffffull, 0x40862e42fefa39efull, 0}, + {0x7ff0000000000000ull, 0x7ff0000000000000ull, 0}, {0xbff0000000000000ull, 0xfff8000000000000ull, EDOM}, + {0xfff0000000000000ull, 0xfff8000000000000ull, EDOM}, {0xbfe0000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, 0}, {0xfff8000000000002ull, 0xfff8000000000002ull, EDOM}, + {0x3fcbf0cfff3ac077ull, 0xbff859e4e1f045dcull, 0}, {0x3f08b0c25bca18b0ull, 0xc023ed400c16d9eeull, 0}, + {0x402d5e682f6c8da9ull, 0x40057e89190b1e8eull, 0}, {0x40881f30b22704c7ull, 0x401a986d082edef0ull, 0}, + {0x4025398ccfd724ccull, 0x4002e56c809c7c6aull, 0}, {0x401231605052b9ceull, 0x3ff83c5b860d2a7eull, 0}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(log_nid_postfix(std::bit_cast(item.input))) == item.expected); + Require(errno == (item.error != 0 ? item.error : 1234)); + } +} + static void CheckExpm1ConsoleBits() { constexpr struct { std::uint64_t input; @@ -1256,32 +1290,7 @@ static void CheckExpm1ScalingRounding() { } _mm_setcsr(savedCsr); } -static void CheckLogConsoleBits() { - constexpr struct { - std::uint64_t input; - std::uint64_t expected; - int error; - } cases[] = { - {0x0000000000000000ull, 0xfff0000000000000ull, ERANGE}, {0x8000000000000000ull, 0xfff0000000000000ull, ERANGE}, - {0x0000000000000001ull, 0xc0874385446d71c3ull, 0}, {0x000fffffffffffffull, 0xc086232bdd7abcd2ull, 0}, - {0x0010000000000000ull, 0xc086232bdd7abcd2ull, 0}, {0x3ff0000000000000ull, 0x0000000000000000ull, 0}, - {0x3fefffffffffffffull, 0xbca0000000000000ull, 0}, {0x3ff0000000000001ull, 0x3cafffffffffffffull, 0}, - {0x3ff00000fffffffeull, 0x3eaffffeffc00aafull, 0}, {0x3ff6a09e00000000ull, 0x3fd62e41dd1274ceull, 0}, - {0x3fe6a09e00000000ull, 0xbfd62e4420e1ff10ull, 0}, {0x4000000000000000ull, 0x3fe62e42fefa39efull, 0}, - {0x4024000000000000ull, 0x40026bb1bbb55516ull, 0}, {0x7fefffffffffffffull, 0x40862e42fefa39efull, 0}, - {0x7ff0000000000000ull, 0x7ff0000000000000ull, 0}, {0xbff0000000000000ull, 0xfff8000000000000ull, EDOM}, - {0xfff0000000000000ull, 0xfff8000000000000ull, EDOM}, {0xbfe0000000000000ull, 0xfff8000000000000ull, EDOM}, - {0x7ff8000000000001ull, 0x7ff8000000000001ull, 0}, {0xfff8000000000002ull, 0xfff8000000000002ull, EDOM}, - {0x3fcbf0cfff3ac077ull, 0xbff859e4e1f045dcull, 0}, {0x3f08b0c25bca18b0ull, 0xc023ed400c16d9eeull, 0}, - {0x402d5e682f6c8da9ull, 0x40057e89190b1e8eull, 0}, {0x40881f30b22704c7ull, 0x401a986d082edef0ull, 0}, - {0x4025398ccfd724ccull, 0x4002e56c809c7c6aull, 0}, {0x401231605052b9ceull, 0x3ff83c5b860d2a7eull, 0}, - }; - for (const auto& item : cases) { - errno = 1234; - Require(std::bit_cast(log_nid_postfix(std::bit_cast(item.input))) == item.expected); - Require(errno == (item.error != 0 ? item.error : 1234)); - } -} + static void CheckAsinl() { const struct { std::uint64_t inputMantissa; std::uint16_t inputSignExponent; std::uint64_t outputMantissa; std::uint16_t outputSignExponent; } cases[] = { @@ -2615,6 +2624,8 @@ int main() { CheckLongDouble(); CheckNearbyintl(); CheckCbrtConsoleBits(); + CheckLogConsoleBits(); + CheckLogEdgeFlags(); CheckLogbConsoleBits(); CheckExpm1ConsoleBits(); CheckLog1pConsoleBits(); From 84c2f081cb19c8d39d6d0edc5f4f3df897b914f0 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Wed, 28 Oct 2026 12:12:18 -0400 Subject: [PATCH 095/612] test(libc): use canonical math export declarations --- core/libs/tests/GuestMath.cpp | 93 ----------------------------------- 1 file changed, 93 deletions(-) diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 35cb0eadf1..fb7c59d6e2 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -37,99 +37,6 @@ extern SonyDconst _LSnan_nid_postfix; extern int signgam_nid_postfix; -float APS5_VABI logbf_nid_postfix(float); -int APS5_VABI ilogb_nid_postfix(double); -int APS5_VABI ilogbf_nid_postfix(float); -double APS5_VABI exp2_nid_postfix(double); -float APS5_VABI __powisf2_nid_postfix(float, int); -double APS5_VABI __powidf2_nid_postfix(double, int); -double APS5_VABI ldexp_nid_postfix(double, int); -double APS5_VABI scalbn_nid_postfix(double, int); -float APS5_VABI scalbnf_nid_postfix(float, int); -float APS5_VABI nextafterf_nid_postfix(float, float); -double APS5_VABI nextafter_nid_postfix(double, double); -double APS5_VABI frexp_nid_postfix(double, int*); -float APS5_VABI frexpf_nid_postfix(float, int*); -float APS5_VABI logf_nid_postfix(float); -std::int64_t APS5_VABI lround_nid_postfix(double); -std::div_t APS5_VABI div_nid_postfix(int, int); -double APS5_VABI sinh_nid_postfix(double); -double APS5_VABI cosh_nid_postfix(double); -double APS5_VABI _Cosh_nid_postfix(double, double); -float APS5_VABI coshf_nid_postfix(float); -float APS5_VABI sinhf_nid_postfix(float); -float APS5_VABI _FSinh_nid_postfix(float, float); -float APS5_VABI _FCosh_nid_postfix(float, float); -double APS5_VABI asinh_nid_postfix(double); -float APS5_VABI asinhf_nid_postfix(float); -double APS5_VABI acosh_nid_postfix(double); -float APS5_VABI acoshf_nid_postfix(float); -double APS5_VABI atanh_nid_postfix(double); -double APS5_VABI log_nid_postfix(double); -float APS5_VABI atanhf_nid_postfix(float); -std::int64_t APS5_VABI lroundf_nid_postfix(float); -std::int64_t APS5_VABI llround_nid_postfix(double); -std::int64_t APS5_VABI llroundf_nid_postfix(float); -std::int64_t APS5_VABI llroundl_nid_postfix(long double); -int APS5_VABI __isfinitef_nid_postfix(float); -int APS5_VABI __isfinitel_nid_postfix(long double); -int APS5_VABI __isnormal_nid_postfix(double); -int APS5_VABI __isnormalf_nid_postfix(float); -int APS5_VABI __isinff_nid_postfix(float); -int APS5_VABI __isinf_nid_postfix(double); -int APS5_VABI isnan_nid_postfix(double); -int APS5_VABI isinf_nid_postfix(double); -std::lldiv_t APS5_VABI lldiv_nid_postfix(long long, long long); -std::lldiv_t APS5_VABI imaxdiv_nid_postfix(std::int64_t, std::int64_t); -std::int64_t APS5_VABI labs_nid_postfix(std::int64_t); -std::int64_t APS5_VABI llabs_nid_postfix(std::int64_t); -std::int64_t APS5_VABI imaxabs_nid_postfix(std::int64_t); -long double APS5_VABI scalbnl_nid_postfix(long double, int); -float APS5_VABI significandf_nid_postfix(float); -double APS5_VABI significand_nid_postfix(double); -long double APS5_VABI acoshl_nid_postfix(long double); -long double APS5_VABI asinhl_nid_postfix(long double); -long double APS5_VABI atanhl_nid_postfix(long double); -long double APS5_VABI atan2l_nid_postfix(long double, long double); -long double APS5_VABI cbrtl_nid_postfix(long double); -long double APS5_VABI erfl_nid_postfix(long double); -long double APS5_VABI erfcl_nid_postfix(long double); -long double APS5_VABI expm1l_nid_postfix(long double); -long double APS5_VABI log1pl_nid_postfix(long double); -long double APS5_VABI log2l_nid_postfix(long double); -long double APS5_VABI lgammal_nid_postfix(long double); -long double APS5_VABI tgammal_nid_postfix(long double); -float APS5_VABI tgammaf_nid_postfix(float); -long double APS5_VABI hypotl_nid_postfix(long double, long double); -float APS5_VABI hypot3f_nid_postfix(float, float, float); -double APS5_VABI hypot3_nid_postfix(double, double, double); -long double APS5_VABI hypot3l_nid_postfix(long double, long double, long double); -float APS5_VABI j0f_nid_postfix(float); -float APS5_VABI j1f_nid_postfix(float); -float APS5_VABI jnf_nid_postfix(int, float); -float APS5_VABI y0f_nid_postfix(float); -float APS5_VABI y1f_nid_postfix(float); -float APS5_VABI ynf_nid_postfix(int, float); -std::lldiv_t APS5_VABI ldiv_nid_postfix(std::int64_t, std::int64_t); -float APS5_VABI nanf_nid_postfix(const char*); -double APS5_VABI nan_nid_postfix(const char*); -long double APS5_VABI atanl_nid_postfix(long double); -long double APS5_VABI sinl_nid_postfix(long double); -long double APS5_VABI cosl_nid_postfix(long double); -long double APS5_VABI tanl_nid_postfix(long double); -long double APS5_VABI sinhl_nid_postfix(long double); -long double APS5_VABI coshl_nid_postfix(long double); -long double APS5_VABI tanhl_nid_postfix(long double); -long double APS5_VABI expl_nid_postfix(long double); -long double APS5_VABI exp2l_nid_postfix(long double); -long double APS5_VABI logl_nid_postfix(long double); -long double APS5_VABI log10l_nid_postfix(long double); -long double APS5_VABI powl_nid_postfix(long double, long double); -long double APS5_VABI ldexpl_nid_postfix(long double, int); -long double APS5_VABI truncl_nid_postfix(long double); -long double APS5_VABI nearbyintl_nid_postfix(long double); -double APS5_VABI logb_nid_postfix(double); -double APS5_VABI remainder_nid_postfix(double, double); struct DinkumwareFloatCharacteristics { int digits; int mantissaDigits; From 911827abb1f536098649cb5f6e1ec199cd48e93d Mon Sep 17 00:00:00 2001 From: JJenkx Date: Thu, 29 Oct 2026 08:54:43 -0400 Subject: [PATCH 096/612] docs(libc): qualify asinh after integrating the msun log port --- docs/dev/debt/libc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index f28ce61ced..ccc3a86ecf 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -182,7 +182,7 @@ - [wcsrtombs_s](../../../core/libs/prx/libc/src/Multibyte.cpp) (libc) follows the console's `libc.prx`, but a runtime-constraint violation only returns its error: the default handler there writes the message and a newline to `stderr` before returning, which is not done here. Conversion uses the C locale of `wcrtomb` (`MB_CUR_MAX` 1), so the path where a multibyte character does not fit the remaining space is not reached - [c16rtomb, c32rtomb, mbrtoc16 and mbrtoc32](../../../core/libs/prx/libc/src/Multibyte.cpp) (libSceLibcInternal, implemented in libc) - a null state, which the console dereferences, throws. Otherwise they follow the console's `libSceLibcInternal.sprx` (0x7D480 to 0x7D920), read from disassembly: UTF-8 whatever the locale, up to six bytes per sequence, with no surrogate or 0x10FFFF check when encoding. The state holds the value in its first eight bytes and the expected byte count (0x10 for a pending low surrogate, 1 for a stored high one) as 16 bits at 0xA. `mbrtoc16` builds a low surrogate from the value the state last held, `c16rtomb` returns -1 without `errno` for an unpaired high surrogate, and `EILSEQ` is 86. On a PS5 (13.60) the system library's default locale converts UTF-8 in `wcrtomb`, `wctomb`, `mbrtowc` and `mblen`; a title's bundled `libc.prx` does too until `setlocale` installs a table ([disassembly](https://github.com/boykopovar/AnyPS5/pull/8855#issuecomment-6335267758)) - [The narrow printf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) follows a payload process on a PS5 (firmware 13.60): each 16-bit unit of a `%ls` or `%lc` argument is encoded alone, so a surrogate pair gives two 3-byte sequences; a NaN keeps its sign and the `+` and space flags, and `0` pads it with spaces; argument positions above 99 work (up to 400 measured; above 65535 throw here); an unknown conversion such as `%m` or `%k` prints `%` and its letter, padded to the width, without taking an argument. The guest's `wcrtomb` and locales are not used. On Linux the formats without those conversions or a floating-point conversion go to glibc, which prints NaN the same way. The `_s` forms throw for an unknown conversion, `%m` included, which was not measured -- [asinh](../../../core/libs/prx/libc/src/Math.cpp) (libc) is FreeBSD msun's [`s_asinh.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_asinh.c): below 2^-28 and up to |x| = 2 it runs over this file's msun `log1p` and gives msun's bits (0 of 2,000,000 random arguments in each of those ranges differ from a locally built msun on a Windows host). Above 2 it calls this file's `log`, which is still the host's, so those results can differ from msun in the last bit until `log` follows msun: 38,719 of 2,000,000 random arguments in (2, 2^28] differ with the Windows UCRT, none above 2^28 there (not measured with glibc). It sets no `errno`, as msun's does not, and was compared with msun built on the host, not with a console: the titles' `libc.prx` and the system `libSceLibcInternal` are unmeasured. +- [asinh](../../../core/libs/prx/libc/src/Math.cpp) (libc) has unverified floating-point exceptions and system `libSceLibcInternal` behavior; sampled comparisons do not establish every input or SDK build. It follows FreeBSD msun's [`s_asinh.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_asinh.c), calling this file's msun `log1p` up to |x| = 2 and msun `log` above 2, and sets no `errno`. Earlier Windows comparisons with locally built msun matched 2,000,000 random arguments below 2^-28 and 2,000,000 up to |x| = 2. With the msun `log` port, 100,000 random arguments match the result bits of PPSA07923's bundled `libc.prx`, measured natively on a Windows host, not on a console; the previous host-log path differed on 233 of those arguments. - [%a and %A](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) format a finite double as the `libc.prx` that titles ship does, instead of through the host: a subnormal is normalised (`0x1p-1074`), a shorter precision rounds half up on the dropped hexadecimal digits and a carry stays in the leading digit (`%.3a` of `0x0.fffffffffffffp-1022` is `0x2.000p-1023`), and `-` overrides `0`. Checked by running the copies of PPSA13934, PPSA07923 and PPSA05916 side by side on 7 million random formats, not measured on a console. A `long double` is normalised the same way, with 16 hexadecimal digits (`0x1.999999999999999ap-4` for 0.1L). Infinities still go to the host; NaN keeps its sign and flags through the formatter on current main. - [%e, %f and %g](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) format a finite `double` or `long double` as the `libc.prx` that titles ship does, instead of through the host. It keeps the exact decimal digits down to position `4 * floor(k / 4) - 20`, where `k` is the `frexp` exponent times 30103 / 100000 truncated, and prints zeros after them; a shorter precision rounds half up on the kept digits, except that an exact tie rounds down when `k` is at least 8 (`%.0f` of 67108865.5 is `67108865`). The console computes the kept digits in extended precision, so about 7% of outputs longer than 17 significant digits end one unit lower than here (`%f` of 1e22 is `9999999999999999999999.000000`). Checked by running the copies of PPSA07923, PPSA05916 and PPSA13934 side by side, not measured on a console. The wide printf family uses the same digits, as the titles' `swprintf` gives the same text as their `snprintf`. - [%p and %lp](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) follow the console's `libSceLibcInternal.sprx` measured from a homebrew payload on firmware 12.70 ([#7549](https://github.com/boykopovar/AnyPS5/issues/7549)), not FreeBSD: bare lowercase hex with no `0x` prefix even under `#`, `0` for a null pointer, `+` and the space flag ignored, the width counting only the digits, `-` padding on the right, `0` padding with zeros when there is no precision, the precision as the minimum number of digits, nothing for a null pointer under `%.0p`, and `l` accepted. Only `snprintf` and `swprintf` of that module were measured, from a payload process rather than a title process, so a width beside `%.0p`, the `0` flag beside a precision (which C17 ignores) and `%.0p` of a non-null pointer follow this formatter's own rules and are not measured; `swprintf` was not run with `%lp`, so the wide family still refuses that conversion and returns -1. Linux routes `%p` and `%lp` to this formatter because glibc prints the FreeBSD form and `(nil)` for a null pointer, which means a format that combines `%p` with a conversion this formatter refuses, such as an argument position above 99, throws there instead of reaching glibc From b8411fd0701407ab3c66b275dade36226ada9328 Mon Sep 17 00:00:00 2001 From: KernelPanicNT Date: Thu, 29 Oct 2026 15:20:48 +0000 Subject: [PATCH 097/612] feat(libSceCesCs): implement sceCesJiscsToUcs and sceCesUcsToJiscs Convert between Unicode and JIS codes for the eight JIS profiles, as measured as a payload process on a device: single bytes and rows and cells of the profile's table one way, and bytes, 0x03PPRRCC and 0x0400RRCC codes the other way, with the measured error codes. Forms, planes and profiles that were not measured throw and are listed in the technical debt, including the type-prefixed codes the encoder returns. --- core/libs/prx/libSceCesCs/CMakeLists.txt | 8 +- core/libs/prx/libSceCesCs/Export.cpp | 105 +++++++++++++ core/libs/prx/libSceCesCs/PendingExports.cpp | 10 -- core/libs/prx/libSceCesCs/include/Api.hpp | 4 +- core/libs/tests/GuestCesJiscs.cpp | 156 +++++++++++++++++++ docs/dev/debt/libSceCesCs.md | 1 + 6 files changed, 269 insertions(+), 15 deletions(-) create mode 100644 core/libs/tests/GuestCesJiscs.cpp diff --git a/core/libs/prx/libSceCesCs/CMakeLists.txt b/core/libs/prx/libSceCesCs/CMakeLists.txt index 36a84feff4..8ce4d2cfaf 100644 --- a/core/libs/prx/libSceCesCs/CMakeLists.txt +++ b/core/libs/prx/libSceCesCs/CMakeLists.txt @@ -25,8 +25,10 @@ if(BUILD_TESTING) endif() if(BUILD_TESTING) - foreach(variant IN ITEMS unicode unicode_module japanese japanese_module wide wide_module chinese chinese_module mbc mbc_module jis2004 jis2004_module iso646 iso646_module) - if(variant MATCHES "^jis2004") + foreach(variant IN ITEMS unicode unicode_module japanese japanese_module wide wide_module chinese chinese_module mbc mbc_module jis2004 jis2004_module iso646 iso646_module jiscs jiscs_module) + if(variant MATCHES "^jiscs") + add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesJiscs.cpp) + elseif(variant MATCHES "^jis2004") add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesJis2004.cpp) elseif(variant MATCHES "^iso646") add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesIso646.cpp) @@ -51,7 +53,7 @@ if(BUILD_TESTING) add_test(NAME guest_ces_${variant} COMMAND guest_ces_${variant}_tests) endforeach() if(WIN32) - set_tests_properties(guest_ces_unicode guest_ces_unicode_module guest_ces_japanese guest_ces_japanese_module guest_ces_wide guest_ces_wide_module guest_ces_chinese guest_ces_chinese_module guest_ces_mbc guest_ces_mbc_module guest_ces_jis2004 guest_ces_jis2004_module guest_ces_iso646 guest_ces_iso646_module PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_ces_unicode guest_ces_unicode_module guest_ces_japanese guest_ces_japanese_module guest_ces_wide guest_ces_wide_module guest_ces_chinese guest_ces_chinese_module guest_ces_mbc guest_ces_mbc_module guest_ces_jis2004 guest_ces_jis2004_module guest_ces_iso646 guest_ces_iso646_module guest_ces_jiscs guest_ces_jiscs_module PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libSceCesCs/Export.cpp b/core/libs/prx/libSceCesCs/Export.cpp index 7e1ee734ef..8b3740412d 100644 --- a/core/libs/prx/libSceCesCs/Export.cpp +++ b/core/libs/prx/libSceCesCs/Export.cpp @@ -3,10 +3,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include "SceTypes.hpp" @@ -1389,6 +1391,81 @@ int utf32ToSbc(const uint32_t* utf32, const uint8_t* profile, uint8_t* sbc) { return scalarToSbc(loadUnit(utf32), profile, sbc); } +std::string hexCode(uint32_t value) { + char digits[8]; + const auto end = std::to_chars(digits, digits + sizeof digits, value, 16).ptr; + return "0x" + std::string(digits, end); +} + +constexpr uint32_t JISCS_PLANE1 = 0x03010000; +constexpr uint32_t JISCS_X0213_PLANE2 = 0x03020000; +constexpr uint32_t JISCS_X0212 = 0x04000000; + +bool isJiscsProfile(const void* profile) { + return isSjis(profile) || isEucJp(profile) || isJis2004(profile); +} + +bool isJisGraphic(uint32_t byte) { + return byte >= 0x21 && byte <= 0x7E; +} + +bool isMeasuredJiscs(uint32_t jiscs) { + return jiscs <= 0xFF || (jiscs >> 16 <= 2 && isJisGraphic(jiscs >> 8 & 0xFF) && isJisGraphic(jiscs & 0xFF)); +} + +JisStrict jisX0208Strictness(const void* profile) { + return isSjisX0208(profile) ? JisStrict::Sjis : isEucJpX0208(profile) ? JisStrict::Euc : JisStrict::None; +} + +uint32_t jiscsCell(uint32_t base, uint32_t cell) { + return base | (cell / 94 + 1) << 8 | (cell % 94 + 1); +} + +int jiscsToUnicode(const void* profile, uint32_t jiscs, uint32_t& code) { + if (jiscs <= 0xFF) { + const auto byte = static_cast(jiscs); + if (byte <= 0x9F) code = isSjisX0208(profile) ? sjisX0208Single(byte) : byte; + else if (byte >= 0xA1 && byte <= 0xDF && hasSs2(profile)) code = 0xFF61 + (byte - 0xA1); + else return CES_ERROR_UNASSIGNED_CODE; + return 0; + } + if (jiscs >> 16 != 0) return CES_ERROR_ILLEGAL_CODE; + const uint32_t index = jisIndex(static_cast(jiscs)); + code = isJis2004(profile) ? JisX0213::PLANE1[index] : jisX0208ToUnicode(jisX0208Strictness(profile), index); + return code != 0 ? 0 : CES_ERROR_UNASSIGNED_CODE; +} + +int unicodeToJiscs(const void* profile, uint32_t code, uint32_t& jiscs) { + const bool strictSjis = isSjisX0208(profile); + if (strictSjis && (code == 0x00A5 || code == 0x203E)) { + jiscs = code == 0x00A5 ? 0x5C : 0x7E; + return 0; + } + if (code <= 0x9F && !(strictSjis && (code == 0x5C || code == 0x7E))) { + jiscs = code; + return 0; + } + if (code >= 0xFF61 && code <= 0xFF9F) { + if (!hasSs2(profile)) return CES_ERROR_UNASSIGNED_CODE; + jiscs = 0xA1 + (code - 0xFF61); + return 0; + } + if (isJis2004(profile)) { + const auto& reverse = jis2004Reverse(); + const auto found = std::lower_bound(reverse.begin(), reverse.end(), code, [](const Jis2004Cell& entry, uint32_t value) { return entry.code < value; }); + if (found == reverse.end() || found->code != code || (found->plane == JIS_X0212_PLANE && isSjis2004(profile))) return CES_ERROR_UNASSIGNED_CODE; + jiscs = jiscsCell(found->plane == JIS_X0212_PLANE ? JISCS_X0212 : found->plane == 2 ? JISCS_X0213_PLANE2 : JISCS_PLANE1, found->cell); + return 0; + } + const JisStrict strict = jisX0208Strictness(profile); + if (strict == JisStrict::None) code = jisAlias(code); + const auto& reverse = jisReverse(strict); + const auto found = std::lower_bound(reverse.begin(), reverse.end(), static_cast(code), [](const JisCell& entry, uint16_t value) { return entry.code < value; }); + if (found == reverse.end() || found->code != code || (found->supplementary && !hasSs3(profile))) return CES_ERROR_UNASSIGNED_CODE; + jiscs = jiscsCell(found->supplementary ? JISCS_X0212 : JISCS_PLANE1, found->cell); + return 0; +} + } extern "C" { @@ -2447,4 +2524,32 @@ void* APS5_VABI sceCesUcsProfileInitSJis2004X0213(void* sheet) { return sheet; } +int APS5_VABI sceCesJiscsToUcs(const void* profile, uint32_t jiscs, uint32_t* ucs) { + if (!isMeasuredJiscs(jiscs)) throw std::runtime_error("sceCesJiscsToUcs: code " + hexCode(jiscs) + " is neither a single byte nor a row and cell 0x21-0x7E of planes 0-2; its form is not implemented"); + if (ucs) *ucs = 0; + if (!profile) return CES_ERROR_INVALID_PROFILE; + if (!isJiscsProfile(profile)) throw std::runtime_error("sceCesJiscsToUcs: a profile other than the eight JIS profiles is not implemented"); + uint32_t code = 0; + const int result = jiscsToUnicode(profile, jiscs, code); + if (result != 0) return result; + if (!ucs) return CES_ERROR_INVALID_DST_BUFFER; + *ucs = code; + return 0; +} + +int APS5_VABI sceCesUcsToJiscs(uint32_t ucs, const void* profile, uint32_t* jiscs) { + if (jiscs) *jiscs = 0; + if (!isScalar(ucs)) return CES_ERROR_ILLEGAL_CODE; + if (ucs > 0xFFFF && ucs >> 16 != 2) throw std::runtime_error("sceCesUcsToJiscs: code point " + hexCode(ucs) + " outside the BMP and plane 2 is not implemented"); + if (!profile) return CES_ERROR_INVALID_PROFILE; + if (!isJiscsProfile(profile)) throw std::runtime_error("sceCesUcsToJiscs: a profile other than the eight JIS profiles is not implemented"); + if (ucs > 0xFFFF && !isJis2004(profile)) throw std::runtime_error("sceCesUcsToJiscs: plane 2 with a JIS X 0208 profile is not implemented"); + uint32_t code = 0; + const int result = unicodeToJiscs(profile, ucs, code); + if (result != 0) return result; + if (!jiscs) return CES_ERROR_INVALID_DST_BUFFER; + *jiscs = code; + return 0; +} + } diff --git a/core/libs/prx/libSceCesCs/PendingExports.cpp b/core/libs/prx/libSceCesCs/PendingExports.cpp index 9089f429b9..6f2fc8db42 100644 --- a/core/libs/prx/libSceCesCs/PendingExports.cpp +++ b/core/libs/prx/libSceCesCs/PendingExports.cpp @@ -93,11 +93,6 @@ int APS5_VABI sceCesJisGetLevel(void) { return 0; } -int APS5_VABI sceCesJiscsToUcs(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesMbcsUcsSetMbcsReplacementCharCode(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -198,11 +193,6 @@ int APS5_VABI sceCesUcsProfileInitIso2022JpRfc1468(void) { return 0; } -int APS5_VABI sceCesUcsToJiscs(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesUhcUcsProfileSetSbcs(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceCesCs/include/Api.hpp b/core/libs/prx/libSceCesCs/include/Api.hpp index 868da5a976..c1df2a2430 100644 --- a/core/libs/prx/libSceCesCs/include/Api.hpp +++ b/core/libs/prx/libSceCesCs/include/Api.hpp @@ -226,7 +226,7 @@ int APS5_VABI sceCesIso2022StrToUtf8Str(void); int APS5_VABI sceCesIso2022UcsContextInit(void); int APS5_VABI sceCesIso2022UcsContextInitCopy(void); int APS5_VABI sceCesJisGetLevel(void); -int APS5_VABI sceCesJiscsToUcs(void); +int APS5_VABI sceCesJiscsToUcs(const void* profile, uint32_t jiscs, uint32_t* ucs); int APS5_VABI sceCesMbcsUcsSetMbcsReplacementCharCode(void); int APS5_VABI sceCesMbcsUcsSetMbcsReplacementCharUCode(void); const uint8_t* APS5_VABI sceCesRefersUcsProfileAscii(void); @@ -262,7 +262,7 @@ int APS5_VABI sceCesUcsProfileInitIso2022JpCp50221(void); int APS5_VABI sceCesUcsProfileInitIso2022JpCp50222(void); int APS5_VABI sceCesUcsProfileInitIso2022JpRfc1468(void); void* APS5_VABI sceCesUcsProfileInitSJis2004X0213(void* sheet); -int APS5_VABI sceCesUcsToJiscs(void); +int APS5_VABI sceCesUcsToJiscs(uint32_t ucs, const void* profile, uint32_t* jiscs); int APS5_VABI sceCesUhcUcsProfileSetSbcs(void); int APS5_VABI sceCesUtf16StrGetIso2022Len(void); int APS5_VABI sceCesUtf16StrToIso2022Str(void); diff --git a/core/libs/tests/GuestCesJiscs.cpp b/core/libs/tests/GuestCesJiscs.cpp new file mode 100644 index 0000000000..2b4dec5a91 --- /dev/null +++ b/core/libs/tests/GuestCesJiscs.cpp @@ -0,0 +1,156 @@ +#include "prx/libSceCesCs/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +namespace { + +constexpr int invalidProfile = static_cast(0x805C0004); +constexpr int illegalCode = static_cast(0x805C0015); +constexpr int unassignedCode = static_cast(0x805C0020); +constexpr int invalidDstBuffer = static_cast(0x805C0030); +constexpr std::uint64_t fnvBasis = 0xCBF29CE484222325ULL; +constexpr std::uint32_t poison = 0xDEADBEEF; + +void Require(bool value) { if (!value) std::abort(); } + +template +void RequireThrows(Action action) { + try { action(); } catch (const std::runtime_error&) { return; } + std::abort(); +} + +std::uint64_t Fnv(std::uint64_t hash, std::uint64_t value, int bytes) { + for (int i = 0; i < bytes; ++i) { + hash ^= value >> (8 * i) & 0xFF; + hash *= 0x100000001B3ULL; + } + return hash; +} + +std::uint64_t Pair(std::uint64_t hash, int result, std::uint32_t out) { + return Fnv(Fnv(hash, static_cast(result), 4), out, 4); +} + +std::uint64_t JisRows(const void* profile) { + std::uint64_t fold = fnvBasis; + for (std::uint32_t plane = 0; plane < 3; ++plane) { + for (std::uint32_t row = 0x21; row <= 0x7E; ++row) { + std::uint64_t hash = fnvBasis; + for (std::uint32_t cell = 0x21; cell <= 0x7E; ++cell) { + std::uint32_t out = poison; + const int result = sceCesJiscsToUcs(profile, plane << 16 | row << 8 | cell, &out); + hash = Pair(hash, result, out); + } + fold = Fnv(fold, hash, 8); + } + } + return fold; +} + +std::uint64_t UcsBlocks(const void* profile, std::uint32_t plane) { + std::uint64_t fold = fnvBasis; + for (std::uint32_t base = plane << 16; base < (plane + 1) << 16; base += 256) { + std::uint64_t hash = fnvBasis; + for (std::uint32_t code = base; code < base + 256; ++code) { + std::uint32_t out = poison; + const int result = sceCesUcsToJiscs(code, profile, &out); + hash = Pair(hash, result, out); + } + fold = Fnv(fold, hash, 8); + } + return fold; +} + +std::uint64_t LowScan(const void* profile, bool fromUcs) { + std::uint64_t hash = fnvBasis; + for (std::uint32_t value = 0; value < 256; ++value) { + std::uint32_t out = poison; + const int result = fromUcs ? sceCesUcsToJiscs(value, profile, &out) : sceCesJiscsToUcs(profile, value, &out); + hash = Pair(hash, result, out); + } + return hash; +} + +using ProfileInit = void* (APS5_VABI*)(void*); + +struct Expected { + ProfileInit init; + std::uint64_t rows; + std::uint64_t bmp; + std::uint64_t bytes; + std::uint64_t low; +}; + +const Expected expected[8] = { + {sceCesUcsProfileInitSJis1997X0208, 0x0A43EFAC27D27F0BULL, 0xC95A209A94B4733DULL, 0xF318D248701074E5ULL, 0xFE1F5C05D3553969ULL}, + {sceCesUcsProfileInitSJis1997Cp932, 0xB41491CD924A5628ULL, 0x7B9BC09A31B566BBULL, 0x6FBF52A74848866CULL, 0xC3FEB7F6573CCCE8ULL}, + {sceCesUcsProfileInitEucJpX0208, 0x6039B027421C7715ULL, 0xB808FE1D63F85985ULL, 0xF30BF109ED45C325ULL, 0xC3FEB7F6573CCCE8ULL}, + {sceCesUcsProfileInitEucJpX0208Ss2, 0x6039B027421C7715ULL, 0xD35C4BD66A76E55BULL, 0x6FBF52A74848866CULL, 0xC3FEB7F6573CCCE8ULL}, + {sceCesUcsProfileInitEucJpX0208Ss2Ss3, 0x6039B027421C7715ULL, 0x0775772C00CF90CCULL, 0x6FBF52A74848866CULL, 0x43D15427FBD6742FULL}, + {sceCesUcsProfileInitEucJpCp51932, 0xB41491CD924A5628ULL, 0x7B9BC09A31B566BBULL, 0x6FBF52A74848866CULL, 0xC3FEB7F6573CCCE8ULL}, + {sceCesUcsProfileInitSJis2004X0213, 0x2D347DA414591DB8ULL, 0x5E9E46E4D0A6B7E1ULL, 0x6FBF52A74848866CULL, 0x1D71EC505E9C63E2ULL}, + {sceCesUcsProfileInitEucJis2004, 0x2D347DA414591DB8ULL, 0xC5C280A43F74E4ECULL, 0x6FBF52A74848866CULL, 0x1D71EC505E9C63E2ULL}, +}; + +constexpr std::uint64_t jis2004Plane2 = 0xBE5A8D1261719CCAULL; + +void RequireDecode(const void* profile, std::uint32_t jiscs, int error, std::uint32_t code) { + std::uint32_t out = poison; + Require(sceCesJiscsToUcs(profile, jiscs, &out) == error && out == code); +} + +void RequireEncode(const void* profile, std::uint32_t code, int error, std::uint32_t jiscs) { + std::uint32_t out = poison; + Require(sceCesUcsToJiscs(code, profile, &out) == error && out == jiscs); +} + +} + +int main() { + alignas(8) static std::uint8_t sheets[8][64] = {}; + const void* profiles[8] = {}; + for (int i = 0; i < 8; ++i) { + profiles[i] = expected[i].init(sheets[i]); + Require(profiles[i] == sheets[i]); + Require(JisRows(profiles[i]) == expected[i].rows); + Require(UcsBlocks(profiles[i], 0) == expected[i].bmp); + Require(LowScan(profiles[i], false) == expected[i].bytes); + Require(LowScan(profiles[i], true) == expected[i].low); + } + Require(UcsBlocks(profiles[6], 2) == jis2004Plane2); + Require(UcsBlocks(profiles[7], 2) == jis2004Plane2); + + RequireDecode(profiles[0], 0x2422, 0, 0x3042); + RequireDecode(profiles[0], 0x2140, 0, 0x005C); + RequireDecode(profiles[1], 0x7921, 0, 0x7E8A); + RequireDecode(profiles[6], 0x7E7E, 0, 0x7E6B); + RequireDecode(profiles[0], 0x7E7E, unassignedCode, 0); + RequireDecode(profiles[7], 0x12121, illegalCode, 0); + RequireEncode(profiles[0], 0x3042, 0, 0x03010402); + RequireEncode(profiles[0], 0x00A5, 0, 0x5C); + RequireEncode(profiles[1], 0x2160, 0, 0x03010D15); + RequireEncode(profiles[4], 0x4E02, 0, 0x04001001); + RequireEncode(profiles[7], 0x4E02, 0, 0x03020102); + RequireEncode(profiles[6], 0x20B9F, 0, 0x03012F34); + RequireEncode(profiles[2], 0xFF61, unassignedCode, 0); + RequireEncode(profiles[0], 0xD800, illegalCode, 0); + RequireEncode(profiles[0], 0x110000, illegalCode, 0); + + RequireDecode(nullptr, 0x2422, invalidProfile, 0); + RequireDecode(nullptr, 0x12121, invalidProfile, 0); + RequireEncode(nullptr, 0x3042, invalidProfile, 0); + RequireEncode(nullptr, 0x20B9F, invalidProfile, 0); + RequireEncode(nullptr, 0xFFFFFFFF, illegalCode, 0); + Require(sceCesJiscsToUcs(profiles[0], 0x2422, nullptr) == invalidDstBuffer); + Require(sceCesUcsToJiscs(0x3042, profiles[0], nullptr) == invalidDstBuffer); + Require(sceCesJiscsToUcs(nullptr, 0x2422, nullptr) == invalidProfile); + + RequireThrows([&] { std::uint32_t out = 0; sceCesJiscsToUcs(profiles[0], 0x82A0, &out); }); + RequireThrows([&] { std::uint32_t out = 0; sceCesJiscsToUcs(profiles[4], 0x8FA2AF, &out); }); + RequireThrows([&] { std::uint32_t out = 0; sceCesUcsToJiscs(0x1F600, profiles[6], &out); }); + RequireThrows([&] { std::uint32_t out = 0; sceCesUcsToJiscs(0x20B9F, profiles[0], &out); }); + RequireThrows([&] { std::uint32_t out = 0; sceCesUcsToJiscs(0x3042, sceCesRefersUcsProfileCp1252(), &out); }); + return 0; +} diff --git a/docs/dev/debt/libSceCesCs.md b/docs/dev/debt/libSceCesCs.md index e32d7f346f..20acd19b46 100644 --- a/docs/dev/debt/libSceCesCs.md +++ b/docs/dev/debt/libSceCesCs.md @@ -11,6 +11,7 @@ - [Unicode scalar conversions](../../../core/libs/prx/libSceCesCs/Unicode.cpp) (libSceCesCs) - console error precedence, optional length pointers and length units are unverified. Signatures follow the [Orbital type database](https://github.com/AlexAltea/orbital/blob/b321fcdbd700cc02da1cb1ad25412781d5ec73c6/tools/ida/db_types.json); names are in the aerolib table pinned by `tools/nid_names.py`. Encoding follows [Unicode section 3.9](https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-3/). Each call converts one scalar, including zero and U+FEFF, without a terminator or byte-order detection; lengths count bytes for UTF-8 and 16-bit units for UTF-16. Native forms use host byte order (supported hosts are little-endian). Errors follow the existing CES converters, not console measurements, and failure leaves outputs unchanged. No console or title comparison has been run - [sceCesEucJpToUtf8 and sceCesEucJpToUcs2](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) with the CP51932 profile follow a payload process on a PS5 (firmware 13.60): every two-byte input and every `0x8F` three-byte input gives the console's result, length and output (the test pins a hash of both sweeps). Bytes below 0xA0 other than 0x0E, 0x8E and 0x8F convert to themselves; JIS X 0212 is not decoded, so `0x8F` forms return `0x805C0020` in rows X 0212 leaves empty and `0x805C0014` elsewhere; a null profile returns `0x805C0004`, and the lengths are cleared first and then set as the console sets them. Their signatures follow the Orbital type database linked above. The console reads any other profile pointer as a sheet, where these return the invalid-parameter error. - [Shift-JIS and EUC-JP scalar conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - errors, their precedence and the optional length pointers of `sceCesSJisTo*`, `sceCesEucJpTo{Utf16,Utf32}*` and `sceCes{Ucs2,Utf8,Utf16,Utf32}*To{SJis,EucJp}` are unverified against the console. Signatures follow the Orbital type database linked above; the CP932 and CP51932 table and profile assumptions above apply. A profile of another encoding answers `INVALID_PARAMETER`, a surrogate or a value above U+10FFFF `INVALID_ENCODE`, a code point outside the BMP `UNASSIGNED_CODE`. UTF-8 input is decoded as `sceCesUtf8ToSbc` decodes it, so overlong forms answer `0x805C0015`. `sceCesSJisCodeToJisX0208` and `sceCesJisX0208ToSJisCode` do not check that the cell is assigned; their `INVALID_ENCODE` for rows or cells outside 1-94 and `INVALID_DST_BUFFER` for a null output are not console measurements +- [sceCesJiscsToUcs and sceCesUcsToJiscs](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - throw where nothing was measured: JIS codes other than a byte or a row and cell `0x21`-`0x7E` below `0x30000`, among them native Shift-JIS and EUC-JP codes and the `0x0200RRCC`, `0x03PPRRCC` and `0x0400RRCC` forms with rows and cells from 1 that the encoder returns (`0x02002422` decoded as row 36, cell 34), so encoded output does not convert back; code points outside the BMP and plane 2; plane 2 with a JIS X 0208 profile; profiles other than the eight JIS ones. A null profile returned `0x805C0004` with those codes except `0x8FA2AF`, which faulted. Unverified: a conversion error answering before `0x805C0030` for a null output (seen only on a zero-filled sheet), `0x805C0015` for every value above U+10FFFF (two samples), and profiles changed by `sce*UcsProfileSetSbcs`, which are not distinguished. Signatures follow Orbital - [Strict JIS X 0208 profiles](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - the tables were measured on a PS5 (`sceCesMbcToUtf32` and `sceCesUtf32ToMbc` over every one- and two-byte sequence and every scalar); the errors follow the other JIS profiles, a missing SS2 or SS3 answering `INVALID_ENCODE`. The table is CP932's without rows 13 and 89-92 and with seven cells changed (`0x213D` U+2014 and the six of [JIS0208.TXT](https://unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/JIS0208.TXT)). Cell `0x2140` is U+005C in `sceCesUcsProfileInitSJis1997X0208`, whose bytes `0x5C` and `0x7E` are U+00A5 and U+203E, and U+FF3C in the EUC-JP profiles, which keep ASCII and also encode U+00A5 and U+203E as `0xA1EF` and `0xA1B1`. Per [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Text/Ces.cs#L57-L67), `X0208Ss2` adds SS2 katakana and `X0208Ss2Ss3` SS3 JIS X 0212 - [UTF-16 and UTF-32 forms of the single-byte and MBCS string conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - errors, their precedence and the optional length pointers of `sceCesSbcTo{Ucs2,Utf16,Utf32}*`, `sceCes{Ucs2,Utf16,Utf32}*ToSbc`, `sceCesMbcsStr{To,Get}{Utf16,Utf32}*` and `sceCes{Utf16,Utf32}Str{To,Get}Mbcs*` are unverified against the console. Signatures follow the Orbital type database linked above. They follow `sceCesSbcToUtf8`, `sceCesUtf8ToSbc` and the UTF-8 MBCS string forms with the profiles and tables listed above. A surrogate or a value above U+10FFFF answers `INVALID_ENCODE`, a valid code point no profile maps `UNASSIGNED_CODE`. The string forms stop at a zero code unit, write no terminator and count code units - [GBK (CP936) and EUC-CN conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - `sceCesUcsProfileInitGbkCp936` and `sceCesUcsProfileInitEucCnGb2312` mark the caller's sheet with an 8-byte tag; the console's profile layout is not known. Both use one table ([Cp936.hpp](../../../core/libs/prx/libSceCesCs/Cp936.hpp)) read from Microsoft's [CP936.TXT](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP936.TXT); the EUC-CN profile accepts only its `0xA1`-`0xFE` lead and trail bytes, so it includes the GBK cells inside that range and the CP936 forms of `0xA1A4` (U+00B7) and `0xA1AA` (U+2014) instead of GB 2312's. That the console's GB 2312 and CP936 tables equal it is not measured. User-defined areas and `sceCesGbUcsProfileSet*` are not handled. Errors, their precedence and the length pointers follow the Shift-JIS forms above and are unverified From aba6ac20ccb90e42bbe4b3c3c02fc971ad3310eb Mon Sep 17 00:00:00 2001 From: Cloudy <52116030+xdCloudy@users.noreply.github.com> Date: Thu, 29 Oct 2026 08:46:25 -0700 Subject: [PATCH 098/612] feat(libSceFreeTypeOt): implement disabled trace queries Reconcile typed FreeType trace exports and focused regression tests with the current main branch while preserving other FreeType implementations and updating the unknown-export debt inventory. --- core/libs/prx/libSceFreeTypeOt/CMakeLists.txt | 14 ++++++++++++ core/libs/prx/libSceFreeTypeOt/Export.cpp | 16 +++++++------- .../prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp | 5 +++++ core/libs/tests/GuestFreeTypeOtConstants.cpp | 22 +++++++++++++++++++ docs/dev/debt/libSceFreeTypeOt.md | 5 +++-- 5 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 core/libs/tests/GuestFreeTypeOtConstants.cpp diff --git a/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt b/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt index 4d34176d1f..81b2437438 100644 --- a/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt +++ b/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt @@ -4,6 +4,20 @@ include("${CMAKE_CURRENT_LIST_DIR}/Stream.cmake") configure_windows_unwind(libSceFreeTypeOt) if(BUILD_TESTING) + add_test_executable(guest_freetype_ot_constants_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeOtConstants.cpp) + target_include_directories(guest_freetype_ot_constants_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_ot_constants_tests PRIVATE libSceFreeTypeOt) + if(WIN32) + target_link_libraries(guest_freetype_ot_constants_tests PRIVATE libc) + endif() + configure_windows_unwind(guest_freetype_ot_constants_tests) + add_test(NAME guest_freetype_ot_constants COMMAND guest_freetype_ot_constants_tests) + set_tests_properties(guest_freetype_ot_constants PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_freetype_ot_constants PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_freetype_corner_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeCorner.cpp) target_include_directories(guest_freetype_corner_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_freetype_corner_tests PRIVATE libSceFreeTypeOt) diff --git a/core/libs/prx/libSceFreeTypeOt/Export.cpp b/core/libs/prx/libSceFreeTypeOt/Export.cpp index 8669bd9fe2..22878dcea8 100644 --- a/core/libs/prx/libSceFreeTypeOt/Export.cpp +++ b/core/libs/prx/libSceFreeTypeOt/Export.cpp @@ -1,10 +1,9 @@ -#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp" #include #include #include #include #include "SceTypes.hpp" -#include "prx/libSceFreeType/include/Api.hpp" #include "prx/libSceFreeTypeOt/include/Calc.hpp" #include "prx/libc/include/General.hpp" @@ -470,15 +469,16 @@ int APS5_VABI sceFreeTypeOtTan_nid_no_patch() { } APS5_EXPORT("dojyaLKzKN0", sceFreeTypeOtTan_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_08_mhD_AAds_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI FT_Trace_Get_Count_nid_no_patch() { + return 0; } -APS5_EXPORT("08-mhD-AAds", sceFreeTypeOtUnknown_08_mhD_AAds_nid_no_patch); +APS5_EXPORT("08-mhD-AAds", FT_Trace_Get_Count_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_64XMq6sDxUo_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +const char* APS5_VABI FT_Trace_Get_Name_nid_no_patch(int idx) { + static_cast(idx); + return nullptr; } -APS5_EXPORT("64XMq6sDxUo", sceFreeTypeOtUnknown_64XMq6sDxUo_nid_no_patch); +APS5_EXPORT("64XMq6sDxUo", FT_Trace_Get_Name_nid_no_patch); int APS5_VABI sceFreeTypeOtUnknown_4xG87W9dTSg_nid_no_patch() { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp b/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp index 00e2da7277..8382b07864 100644 --- a/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp +++ b/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp @@ -3,3 +3,8 @@ #include "prx/libSceFreeTypeOt/include/Stream.hpp" #include "prx/libSceFreeType/include/Api.hpp" #include "prx/libSceFreeTypeOt/include/Calc.hpp" + +extern "C" { +int APS5_VABI FT_Trace_Get_Count_nid_no_patch(); +const char* APS5_VABI FT_Trace_Get_Name_nid_no_patch(int idx); +} diff --git a/core/libs/tests/GuestFreeTypeOtConstants.cpp b/core/libs/tests/GuestFreeTypeOtConstants.cpp new file mode 100644 index 0000000000..80ef6dbaca --- /dev/null +++ b/core/libs/tests/GuestFreeTypeOtConstants.cpp @@ -0,0 +1,22 @@ +#include "prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp" +#include +#include + +namespace { + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "FreeType OpenType constants: %s\n", message); + std::exit(1); +} + +} + +int main() { + Require(FT_Trace_Get_Count_nid_no_patch() == 0, "FT_Trace_Get_Count returns zero"); + Require(FT_Trace_Get_Name_nid_no_patch(-1) == nullptr, "FT_Trace_Get_Name returns null for negative index"); + Require(FT_Trace_Get_Name_nid_no_patch(0) == nullptr, "FT_Trace_Get_Name returns null for zero index"); + Require(FT_Trace_Get_Name_nid_no_patch(128) == nullptr, "FT_Trace_Get_Name returns null for positive index"); + std::puts("Guest FreeType OpenType constant tests passed"); + return 0; +} diff --git a/docs/dev/debt/libSceFreeTypeOt.md b/docs/dev/debt/libSceFreeTypeOt.md index 7013351fc5..8a26ac2cc5 100644 --- a/docs/dev/debt/libSceFreeTypeOt.md +++ b/docs/dev/debt/libSceFreeTypeOt.md @@ -1,14 +1,15 @@ # libSceFreeTypeOt technical debt - The FT_Outline_Get_Orientation, FT_Request_Size, FT_Sin, FT_Tan, FT_Vector_Unit, FT_New_Size, FT_Outline_Copy, FT_Outline_Decompose, FT_Stroker_New, FT_Stroker_Done, FT_Stroker_GetCounts, FT_Stroker_BeginSubPath, FT_Stroker_LineTo, FT_Stroker_EndSubPath, FT_Stroker_Set, FT_Stroker_Rewind, FT_Stroker_GetBorderCounts, FT_Stroker_ConicTo and FT_Stroker_CubicTo NIDs in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) are identified, but their provider-specific prototypes and behavior remain unverified. Their named helpers retain the explicit inventory aliases and throw; the separate libSceFreeType implementations do not supply this provider. -- The 120 `sceFreeTypeOtUnknown_*_nid_no_patch` exports in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) are present in the PS5 3.20 export inventory from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). Their prototypes and behavior are unverified. Each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. +- 81 `sceFreeTypeOtUnknown_*_nid_no_patch` exports in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) still throw. The PS5 3.20 export inventory supplies their NIDs, but their provider-specific prototypes and behavior remain unverified. Each uses a no-parameter import-resolution placeholder that makes no ABI claim. +- Console runtime and other firmware versions are unverified for [FT_Trace_Get_Count and FT_Trace_Get_Name](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp). The [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFreeTypeOt.c#L2708) identifies NIDs `08-mhD-AAds` and `64XMq6sDxUo`; FreeType 2.13.3 declares an integer count and a `const char*` name lookup taking an integer index. Firmware 13.60 shows `xor eax,eax; ret` at `0x10410` and `0x10420`, matching the disabled-tracing return values 0 and null. Both implementations are fixed constants independent of host tracing. ### Unknown function info - [FT_Outline_GetInsideBorder, FT_Outline_GetOutsideBorder](../../../core/libs/prx/libSceFreeTypeOt/CMakeLists.txt) (libSceFreeTypeOl, libSceFreeTypeOt) - console prototypes, enum/layout equivalence, FreeType configuration and module/library versions are unverified. The existing LP64 outline layout and unsigned 16-bit counts follow bundled FreeType 2.13.3; border selection follows [ftstroke.c](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftstroke.c), with the shifted polygon area from [ftoutln.c](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftoutln.c) accumulated in 64 bits. Null positive-count arrays and out-of-range or non-increasing contour endpoints throw when reached; their console behavior is unknown. Tags and flags are not read, and input arrays remain caller-owned. - [FT_Stream calls](../../../core/libs/prx/libSceFreeTypeOt/Stream.cpp) (libSceFreeTypeOt) - console signatures and FreeType version are unverified. The [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFreeTypeOt.c) has both the signed readers FreeType removed in 2.4.5 and their unsigned replacements; the 36 calls follow 2.4.12 [ftstream.c](https://github.com/freetype/freetype/blob/cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9/src/base/ftstream.c) and [ftobjs.c](https://github.com/freetype/freetype/blob/cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9/src/base/ftobjs.c#L205-L219), the signed readers 2.4.4, on the 80-byte LP64 record with guest callbacks. A null pointer FreeType would dereference throws before any allocation, read or frame is left behind, as does `FT_Stream_ReadFields` with a null stream, whose error changed in 2.5.4. `FT_Stream_New` and `FT_Stream_Open` still throw. -- The import-resolution helpers in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) retain the PS5 3.20 NIDs from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs), including identified names and still-unidentified exports. Their provider-specific prototypes and behavior are unverified. Each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. +- The remaining unimplemented import-resolution helpers in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) retain their PS5 3.20 NIDs. Their provider-specific prototypes and behavior are unverified; their no-parameter placeholder signatures make no ABI claim and their bodies throw through `NotImplemented_nid_no_patch`. - [ft_corner_is_flat, ft_corner_orientation](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) (libSceFreeTypeOt) - console prototypes and FreeType version equivalence are unverified. The LP64 signatures and 64-bit coordinate calculations follow bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftcalc.c): orientation returns +1 for counter-clockwise turns, -1 for clockwise turns and 0 for collinear inputs using 64-bit coordinate cross-products; flatness compares hypotenuse approximations via the alpha-max-plus-beta-min metric and returns 1 when the corner angle deviation does not exceed the threshold, 0 otherwise. From 1e6c1f6ded7b0f9aab76b01dbd7679e03a225c11 Mon Sep 17 00:00:00 2001 From: xdCloudy <52116030+xdCloudy@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:18:52 +0000 Subject: [PATCH 099/612] feat(libSceLibreSsl): add file and socket BIO adapters --- core/libs/prx/libSceLibreSsl/Export.cpp | 15 -- core/libs/prx/libSceLibreSsl/MemoryBio.cmake | 3 +- core/libs/prx/libSceLibreSsl/include/Api.hpp | 6 +- .../prx/libSceLibreSsl/src/BioFileSocket.cpp | 156 ++++++++++++++++++ core/libs/tests/GuestLibreSslBio.cpp | 42 +++++ docs/dev/debt/libSceLibreSsl.md | 2 + 6 files changed, 205 insertions(+), 19 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index b681d78655..9fdee2c890 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -43,26 +43,11 @@ int APS5_VABI BIO_f_ssl_nid_postfix(void) { return 0; } -int APS5_VABI BIO_new_fp_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BIO_new_socket_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI BIO_printf_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI BIO_s_file_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI BIO_write_nid_postfix(LibreSsl::Bio* bio, const void* source, int size) { return LibreSslBioWrite_nid_no_patch(bio, source, size); } diff --git a/core/libs/prx/libSceLibreSsl/MemoryBio.cmake b/core/libs/prx/libSceLibreSsl/MemoryBio.cmake index a9ed5d36c8..86da987864 100644 --- a/core/libs/prx/libSceLibreSsl/MemoryBio.cmake +++ b/core/libs/prx/libSceLibreSsl/MemoryBio.cmake @@ -1,4 +1,5 @@ -target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/Bio.cpp) +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/Bio.cpp ${CMAKE_CURRENT_LIST_DIR}/src/BioFileSocket.cpp) +target_link_libraries(libSceLibreSsl PRIVATE libc libSceNet) if(BUILD_TESTING) add_test_executable(guest_libressl_bio_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslBio.cpp) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 63e220c7be..4435d2c76f 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -170,12 +170,12 @@ std::int64_t APS5_VABI BIO_ctrl_nid_postfix(LibreSsl::Bio* bio, int command, std int APS5_VABI BIO_f_ssl_nid_postfix(void); int APS5_VABI BIO_free_nid_postfix(LibreSsl::Bio* bio); LibreSsl::Bio* APS5_VABI BIO_new_nid_postfix(const LibreSsl::BioMethod* method); -int APS5_VABI BIO_new_fp_nid_postfix(void); +LibreSsl::Bio* APS5_VABI BIO_new_fp_nid_postfix(void* stream, int closeFlag); LibreSsl::Bio* APS5_VABI BIO_new_mem_buf_nid_postfix(const void* source, int size); -int APS5_VABI BIO_new_socket_nid_postfix(void); +LibreSsl::Bio* APS5_VABI BIO_new_socket_nid_postfix(int socket, int closeFlag); int APS5_VABI BIO_printf_nid_postfix(void); int APS5_VABI BIO_puts_nid_postfix(LibreSsl::Bio* bio, const char* source); -int APS5_VABI BIO_s_file_nid_postfix(void); +const LibreSsl::BioMethod* APS5_VABI BIO_s_file_nid_postfix(); const LibreSsl::BioMethod* APS5_VABI BIO_s_mem_nid_postfix(); int APS5_VABI BIO_write_nid_postfix(LibreSsl::Bio* bio, const void* source, int size); int APS5_VABI BN_CTX_end_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp b/core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp new file mode 100644 index 0000000000..d329bba547 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp @@ -0,0 +1,156 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceNet/include/Net.hpp" +#include +#include +#include + +namespace { + +using LibreSsl::Bio; +using LibreSsl::BioMethod; + +int APS5_VABI FileWrite(Bio* bio, const char* data, int size) { + if (!bio->data || size < 0 || (size && !data)) return -1; + return static_cast(fwrite_nid_postfix(data, 1, static_cast(size), static_cast(bio->data))); +} + +int APS5_VABI FileRead(Bio* bio, char* data, int size) { + if (!bio->data || size < 0 || (size && !data)) return -1; + return static_cast(fread_nid_postfix(data, 1, static_cast(size), static_cast(bio->data))); +} + +int APS5_VABI FilePuts(Bio* bio, const char* data) { + if (!data) return -1; + const std::size_t size = std::strlen(data); + if (size > INT_MAX) return -1; + return FileWrite(bio, data, static_cast(size)); +} + +int APS5_VABI FileGets(Bio* bio, char* data, int size) { + if (!data || size <= 0) return 0; + int count = 0; + while (count < size - 1) { + char character = 0; + if (FileRead(bio, &character, 1) != 1) break; + data[count++] = character; + if (character == '\n') break; + } + data[count] = '\0'; + return count; +} + +std::int64_t APS5_VABI FileControl(Bio* bio, int command, std::int64_t argument, void* pointer) { + auto* stream = static_cast(bio->data); + switch (command) { + case 1: + return stream && fseek_nid_postfix(stream, 0, SEEK_SET) == 0; + case 2: + return stream ? feof_nid_postfix(stream) : 1; + case 8: + return bio->shutdown; + case 9: + bio->shutdown = argument != 0; + return 1; + case 10: + return 0; + case 11: + return stream && fflush_nid_postfix(stream) == 0; + case 12: + return 1; + case 107: + if (pointer) *static_cast(pointer) = stream; + return 1; + default: + return 0; + } +} + +int APS5_VABI FileDestroy(Bio* bio) { + if (bio->shutdown && bio->initialized && bio->data) + fclose_nid_postfix(static_cast(bio->data)); + bio->data = nullptr; + bio->initialized = 0; + return 1; +} + +int APS5_VABI SocketWrite(Bio* bio, const char* data, int size) { + if (bio->number < 0 || size < 0 || (size && !data)) return -1; + const auto result = sceNetSend(bio->number, data, static_cast(size), 0); + return result >= 0 && result <= INT_MAX ? static_cast(result) : -1; +} + +int APS5_VABI SocketRead(Bio* bio, char* data, int size) { + if (bio->number < 0 || size < 0 || (size && !data)) return -1; + const auto result = sceNetRecv(bio->number, data, static_cast(size), 0); + return result >= 0 && result <= INT_MAX ? static_cast(result) : -1; +} + +int APS5_VABI SocketPuts(Bio* bio, const char* data) { + if (!data) return -1; + const std::size_t size = std::strlen(data); + if (size > INT_MAX) return -1; + return SocketWrite(bio, data, static_cast(size)); +} + +std::int64_t APS5_VABI SocketControl(Bio* bio, int command, std::int64_t argument, void*) { + switch (command) { + case 1: + return 0; + case 2: + return 0; + case 8: + return bio->shutdown; + case 9: + bio->shutdown = argument != 0; + return 1; + case 10: + return 0; + case 11: + case 12: + return 1; + default: + return 0; + } +} + +int APS5_VABI SocketDestroy(Bio* bio) { + if (bio->shutdown && bio->initialized && bio->number >= 0) + sceNetSocketClose(bio->number); + bio->number = -1; + bio->initialized = 0; + return 1; +} + +const BioMethod FileMethod{0x402, "FILE pointer", FileWrite, FileRead, FilePuts, FileGets, FileControl, nullptr, FileDestroy, nullptr}; +const BioMethod SocketMethod{0x405, "socket", SocketWrite, SocketRead, SocketPuts, nullptr, SocketControl, nullptr, SocketDestroy, nullptr}; + +} // namespace + +extern "C" { + +const LibreSsl::BioMethod* APS5_VABI BIO_s_file_nid_postfix() { + return &FileMethod; +} + +LibreSsl::Bio* APS5_VABI BIO_new_fp_nid_postfix(void* value, int closeFlag) { + if (!value) return nullptr; + auto* bio = BIO_new_nid_postfix(&FileMethod); + if (!bio) return nullptr; + bio->data = value; + bio->shutdown = closeFlag != 0; + bio->initialized = 1; + return bio; +} + +LibreSsl::Bio* APS5_VABI BIO_new_socket_nid_postfix(int socket, int closeFlag) { + if (socket < 0) return nullptr; + auto* bio = BIO_new_nid_postfix(&SocketMethod); + if (!bio) return nullptr; + bio->number = socket; + bio->shutdown = closeFlag != 0; + bio->initialized = 1; + return bio; +} + +} diff --git a/core/libs/tests/GuestLibreSslBio.cpp b/core/libs/tests/GuestLibreSslBio.cpp index 0fbade7a4e..8e2ea18316 100644 --- a/core/libs/tests/GuestLibreSslBio.cpp +++ b/core/libs/tests/GuestLibreSslBio.cpp @@ -1,5 +1,6 @@ #include "prx/libSceLibreSsl/libSceLibreSsl.hpp" #include "prx/libc/include/Api.hpp" +#include "prx/libc/include/FileStream.hpp" #include "prx/libSceLibreSsl/ErrorQueue.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include @@ -544,6 +545,44 @@ void TestUnsupported() { Require(BIO_free_nid_postfix(bio) == 1, "free after method errors"); } +#ifndef ANYPS5_BIO_DYNAMIC +void TestFileAndSocket() { + const BioMethod* fileMethod = BIO_s_file_nid_postfix(); + Require(fileMethod && fileMethod->type == 0x402, "file BIO method descriptor"); + Require(BIO_new_fp_nid_postfix(nullptr, 0) == nullptr, "reject null file BIO"); + std::FILE* native = std::tmpfile(); + Require(native != nullptr, "open temporary file for BIO"); + FileStream stream(native); + Bio* file = BIO_new_fp_nid_postfix(&stream, 0); + Require(file && file->method == fileMethod && file->data == &stream, "file BIO guest stream"); + Require(file->initialized && BIO_ctrl_nid_postfix(file, 8, 0, nullptr) == 0, "file BIO no-close flag"); + Require(BIO_write_nid_postfix(file, "abc", 3) == 3, "write guest file through BIO"); + Require(BIO_ctrl_nid_postfix(file, 11, 0, nullptr) == 1, "flush guest file through BIO"); + Require(BIO_ctrl_nid_postfix(file, 1, 0, nullptr) == 1, "rewind guest file through BIO"); + char buffer[8]{}; + Require(file->method->read(file, buffer, 3) == 3 && std::memcmp(buffer, "abc", 3) == 0, "read guest file through BIO"); + FileStream* queried = nullptr; + Require(BIO_ctrl_nid_postfix(file, 107, 0, &queried) == 1 && queried == &stream, "get guest file pointer"); + Require(BIO_free_nid_postfix(file) == 1, "free BIO without closing file"); + Require(fseek_nid_postfix(&stream, 0, SEEK_SET) == 0, "borrowed guest stream remains open"); + Require(fclose_nid_postfix(&stream) == 0, "close borrowed guest stream"); + + std::FILE* ownedNative = std::tmpfile(); + Require(ownedNative != nullptr, "open owned temporary BIO file"); + FileStream owned(ownedNative); + Bio* ownedBio = BIO_new_fp_nid_postfix(&owned, 1); + Require(ownedBio && BIO_ctrl_nid_postfix(ownedBio, 8, 0, nullptr) == 1, "owned guest file BIO"); + Require(BIO_free_nid_postfix(ownedBio) == 1 && !owned.IsOpen(), "owned BIO closes guest file"); + + Require(BIO_new_socket_nid_postfix(-1, 0) == nullptr, "reject negative guest socket"); + Bio* socket = BIO_new_socket_nid_postfix(42, 0); + Require(socket && socket->initialized && socket->number == 42, "guest socket BIO descriptor"); + Require(socket->method && socket->method->type == 0x405, "socket BIO method descriptor"); + Require(BIO_ctrl_nid_postfix(socket, 8, 0, nullptr) == 0, "socket BIO no-close flag"); + Require(BIO_free_nid_postfix(socket) == 1, "free unowned guest socket BIO"); +} +#endif + } // namespace int main(int argc, char** argv) { @@ -563,6 +602,9 @@ int main(int argc, char** argv) { TestWriteBoundaries(); TestDispatchAndReferences(); TestUnsupported(); +#ifndef ANYPS5_BIO_DYNAMIC + TestFileAndSocket(); +#endif Require(allocations == 0, "BIO tests leaked guest heap storage"); Require(ERR_get_error_nid_postfix() == 0, "BIO tests left an unexpected error"); } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index f5c0f81cdf..f8409997ea 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,6 +2,8 @@ ### Functional +- `BIO_s_file`, `BIO_new_fp` and `BIO_new_socket` extend the guest-ABI-compatible memory BIO implementation from #9536 using guest libc `FileStream` and `libSceNet` descriptors. File and socket BIOs have not been compared against the firmware or exercised on a console. Socket retry flags, socket pending-byte queries and extended file/socket control commands are not implemented; unsupported method controls return 0. File and socket ownership follows the caller's close flag. + - [SSL_select_next_proto](../../../core/libs/prx/libSceLibreSsl/src/ProtocolSelection.cpp) leaves empty lists, zero-length names and truncated encodings unsupported and throws instead of following the reported firmware behavior. Null outputs also throw; their firmware behavior is unmeasured. [Independent firmware 13.42 evidence](https://github.com/boykopovar/AnyPS5/pull/9785#issuecomment-6406712390) reports host-PC execution of `libSceLibreSsl.sprx` at `0xda6d0`, with matching six-argument valid-list selection, status, borrowed pointer and length. That report also observes negotiation of zero-length entries, unchecked fallback lengths and an empty-supported-list read; these paths remain unsupported here. Null outputs and console-hardware behavior remain unmeasured. The author-sourced signature and valid-list contract follow LibreSSL 3.4.1. ### Unknown function info From bb735ca894d19ca3e3f94a8b430f49f0a06ec07f Mon Sep 17 00:00:00 2001 From: Cloudy <52116030+xdCloudy@users.noreply.github.com> Date: Thu, 29 Oct 2026 09:27:22 -0700 Subject: [PATCH 100/612] docs(libSceLibreSsl): link file and socket BIO technical debt --- docs/dev/debt/libSceLibreSsl.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index f8409997ea..4842aaa6cd 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Functional -- `BIO_s_file`, `BIO_new_fp` and `BIO_new_socket` extend the guest-ABI-compatible memory BIO implementation from #9536 using guest libc `FileStream` and `libSceNet` descriptors. File and socket BIOs have not been compared against the firmware or exercised on a console. Socket retry flags, socket pending-byte queries and extended file/socket control commands are not implemented; unsupported method controls return 0. File and socket ownership follows the caller's close flag. +- [File and socket BIO adapters](../../../core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp) (`BIO_s_file`, `BIO_new_fp`, `BIO_new_socket`) extend the guest-ABI-compatible memory BIO implementation from #9536 using guest libc `FileStream` and `libSceNet` descriptors. File and socket BIOs have not been compared against the firmware or exercised on a console. Socket retry flags, socket pending-byte queries and extended file/socket control commands are not implemented; unsupported method controls return 0. File and socket ownership follows the caller's close flag. - [SSL_select_next_proto](../../../core/libs/prx/libSceLibreSsl/src/ProtocolSelection.cpp) leaves empty lists, zero-length names and truncated encodings unsupported and throws instead of following the reported firmware behavior. Null outputs also throw; their firmware behavior is unmeasured. [Independent firmware 13.42 evidence](https://github.com/boykopovar/AnyPS5/pull/9785#issuecomment-6406712390) reports host-PC execution of `libSceLibreSsl.sprx` at `0xda6d0`, with matching six-argument valid-list selection, status, borrowed pointer and length. That report also observes negotiation of zero-length entries, unchecked fallback lengths and an empty-supported-list read; these paths remain unsupported here. Null outputs and console-hardware behavior remain unmeasured. The author-sourced signature and valid-list contract follow LibreSSL 3.4.1. @@ -27,7 +27,7 @@ ### Functional -- [Memory BIOs](../../../core/libs/prx/libSceLibreSsl/src/Bio.cpp) have no console execution verification. [Firmware 13.42 module comparisons on a PC](https://github.com/boykopovar/AnyPS5/pull/9536#issuecomment-6407192928) cover 1,048 sequences with stand-in allocators, including null/zero writes, callbacks and growth wiping. They support dispatching nonnegative writes to the method even with null source or zero length; LibreSSL 3.4.1 instead guards those calls. Negative public lengths retain that source's early return because the module report did not measure them. The 104-byte BIO, 80-byte method and 24-byte buffer use LP64 layouts. Allocator padding is zeroed rather than retaining the module allocator's fill. Other firmware and console imports remain unverified. CRYPTO ex-data, file, socket and SSL BIOs remain unsupported. +- [Memory BIOs](../../../core/libs/prx/libSceLibreSsl/src/Bio.cpp) have no console execution verification. [Firmware 13.42 module comparisons on a PC](https://github.com/boykopovar/AnyPS5/pull/9536#issuecomment-6407192928) cover 1,048 sequences with stand-in allocators, including null/zero writes, callbacks and growth wiping. They support dispatching nonnegative writes to the method even with null source or zero length; LibreSSL 3.4.1 instead guards those calls. Negative public lengths retain that source's early return because the module report did not measure them. The 104-byte BIO, 80-byte method and 24-byte buffer use LP64 layouts. Allocator padding is zeroed rather than retaining the module allocator's fill. Other firmware and console imports remain unverified. CRYPTO ex-data and SSL BIOs remain unsupported; the separate file and socket BIO adapters have the limitations noted above. - [Memory BIO unchecked inputs](../../../core/libs/prx/libSceLibreSsl/src/Bio.cpp) throw for null method or raw line-buffer pointers, negative raw method write sizes, an overflowing raw line-buffer size, inconsistent read-only buffer lengths, and memory write lengths that overflow size_t. Upstream does not validate these inputs. An unrepresentable string length throws instead of narrowing into an unchecked write. From 6d99775d11cfe71695077b24725b4ec28db231e2 Mon Sep 17 00:00:00 2001 From: Cloudy <52116030+xdCloudy@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:56:11 -0700 Subject: [PATCH 101/612] fix(libSceLibreSsl): preserve guest FILE read error and reset status --- core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp b/core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp index d329bba547..66e72c84f0 100644 --- a/core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp +++ b/core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp @@ -17,7 +17,9 @@ int APS5_VABI FileWrite(Bio* bio, const char* data, int size) { int APS5_VABI FileRead(Bio* bio, char* data, int size) { if (!bio->data || size < 0 || (size && !data)) return -1; - return static_cast(fread_nid_postfix(data, 1, static_cast(size), static_cast(bio->data))); + auto* stream = static_cast(bio->data); + const auto count = fread_nid_postfix(data, 1, static_cast(size), stream); + return size > 0 && count == 0 && ferror_nid_postfix(stream) != 0 ? -1 : static_cast(count); } int APS5_VABI FilePuts(Bio* bio, const char* data) { @@ -44,7 +46,7 @@ std::int64_t APS5_VABI FileControl(Bio* bio, int command, std::int64_t argument, auto* stream = static_cast(bio->data); switch (command) { case 1: - return stream && fseek_nid_postfix(stream, 0, SEEK_SET) == 0; + return stream ? fseek_nid_postfix(stream, 0, SEEK_SET) : -1; case 2: return stream ? feof_nid_postfix(stream) : 1; case 8: From bfaa8f50c10ef533f175880431ccee6d3a080104 Mon Sep 17 00:00:00 2001 From: Cloudy <52116030+xdCloudy@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:56:29 -0700 Subject: [PATCH 102/612] test(libSceLibreSsl): cover file BIO EOF, read errors, and reset status --- core/libs/tests/GuestLibreSslBio.cpp | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/core/libs/tests/GuestLibreSslBio.cpp b/core/libs/tests/GuestLibreSslBio.cpp index 8e2ea18316..d1ffe23b05 100644 --- a/core/libs/tests/GuestLibreSslBio.cpp +++ b/core/libs/tests/GuestLibreSslBio.cpp @@ -16,6 +16,13 @@ #include #include +#ifdef _WIN32 +#include +#include +#else +#include +#endif + #ifdef ANYPS5_BIO_DYNAMIC #ifdef _WIN32 #include @@ -561,6 +568,10 @@ void TestFileAndSocket() { Require(BIO_ctrl_nid_postfix(file, 1, 0, nullptr) == 1, "rewind guest file through BIO"); char buffer[8]{}; Require(file->method->read(file, buffer, 3) == 3 && std::memcmp(buffer, "abc", 3) == 0, "read guest file through BIO"); + Require(file->method->read(file, buffer, 1) == 0, "clean guest file EOF returns zero"); + Require(feof_nid_postfix(&stream) != 0 && ferror_nid_postfix(&stream) == 0, "clean EOF does not set stream error"); + Require(BIO_ctrl_nid_postfix(file, 1, 0, nullptr) == 0, "file reset returns fseek success status"); + Require(file->method->read(file, buffer, 3) == 3 && std::memcmp(buffer, "abc", 3) == 0, "read after file BIO reset"); FileStream* queried = nullptr; Require(BIO_ctrl_nid_postfix(file, 107, 0, &queried) == 1 && queried == &stream, "get guest file pointer"); Require(BIO_free_nid_postfix(file) == 1, "free BIO without closing file"); @@ -574,6 +585,29 @@ void TestFileAndSocket() { Require(ownedBio && BIO_ctrl_nid_postfix(ownedBio, 8, 0, nullptr) == 1, "owned guest file BIO"); Require(BIO_free_nid_postfix(ownedBio) == 1 && !owned.IsOpen(), "owned BIO closes guest file"); + int descriptors[2]{-1, -1}; +#ifdef _WIN32 + Require(_pipe(descriptors, 4096, _O_BINARY) == 0, "create non-seekable guest file pipe"); + std::FILE* writeOnly = _fdopen(descriptors[1], "wb"); +#else + Require(::pipe(descriptors) == 0, "create non-seekable guest file pipe"); + std::FILE* writeOnly = ::fdopen(descriptors[1], "wb"); +#endif + Require(writeOnly != nullptr, "wrap pipe writer in host stream"); + FileStream pipeStream(writeOnly); + Bio* pipeBio = BIO_new_fp_nid_postfix(&pipeStream, 0); + Require(pipeBio != nullptr, "construct guest file BIO around pipe writer"); + Require(pipeBio->method->read(pipeBio, buffer, 1) == -1, "guest file read error must not report clean EOF"); + Require(ferror_nid_postfix(&pipeStream) != 0 && feof_nid_postfix(&pipeStream) == 0, "pipe read failure sets error, not EOF"); + Require(BIO_ctrl_nid_postfix(pipeBio, 1, 0, nullptr) == -1, "file reset preserves non-seekable stream failure"); + Require(BIO_free_nid_postfix(pipeBio) == 1, "free borrowed pipe BIO"); + Require(fclose_nid_postfix(&pipeStream) == 0, "close borrowed pipe writer"); +#ifdef _WIN32 + Require(_close(descriptors[0]) == 0, "close pipe reader"); +#else + Require(::close(descriptors[0]) == 0, "close pipe reader"); +#endif + Require(BIO_new_socket_nid_postfix(-1, 0) == nullptr, "reject negative guest socket"); Bio* socket = BIO_new_socket_nid_postfix(42, 0); Require(socket && socket->initialized && socket->number == 42, "guest socket BIO descriptor"); From d01ba02d9b11bd54fe4e74d5445f6d08af8932d1 Mon Sep 17 00:00:00 2001 From: Cloudy <52116030+xdCloudy@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:58:07 -0700 Subject: [PATCH 103/612] fix(libSceLibreSsl): queue SYS and BIO errors for failed file reads --- core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp b/core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp index 66e72c84f0..086430f9a7 100644 --- a/core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp +++ b/core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp @@ -1,6 +1,8 @@ #include "prx/libSceLibreSsl/libSceLibreSsl.hpp" #include "prx/libc/include/Api.hpp" #include "prx/libSceNet/include/Net.hpp" +#include "prx/libSceLibreSsl/include/Error.hpp" +#include #include #include #include @@ -19,7 +21,13 @@ int APS5_VABI FileRead(Bio* bio, char* data, int size) { if (!bio->data || size < 0 || (size && !data)) return -1; auto* stream = static_cast(bio->data); const auto count = fread_nid_postfix(data, 1, static_cast(size), stream); - return size > 0 && count == 0 && ferror_nid_postfix(stream) != 0 ? -1 : static_cast(count); + const int readError = errno; + if (size > 0 && count == 0 && ferror_nid_postfix(stream) != 0) { + LibreSslPutError_nid_no_patch(2, 0, readError, __FILE__, __LINE__); + LibreSslPutError_nid_no_patch(32, 0, 2, __FILE__, __LINE__); + return -1; + } + return static_cast(count); } int APS5_VABI FilePuts(Bio* bio, const char* data) { From 2acdb2b42bbf24ac01aa46cd167e25eadbf22ca8 Mon Sep 17 00:00:00 2001 From: Cloudy <52116030+xdCloudy@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:58:24 -0700 Subject: [PATCH 104/612] test(libSceLibreSsl): assert read-error queue and clean EOF --- core/libs/tests/GuestLibreSslBio.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/libs/tests/GuestLibreSslBio.cpp b/core/libs/tests/GuestLibreSslBio.cpp index d1ffe23b05..224bb3adeb 100644 --- a/core/libs/tests/GuestLibreSslBio.cpp +++ b/core/libs/tests/GuestLibreSslBio.cpp @@ -570,6 +570,7 @@ void TestFileAndSocket() { Require(file->method->read(file, buffer, 3) == 3 && std::memcmp(buffer, "abc", 3) == 0, "read guest file through BIO"); Require(file->method->read(file, buffer, 1) == 0, "clean guest file EOF returns zero"); Require(feof_nid_postfix(&stream) != 0 && ferror_nid_postfix(&stream) == 0, "clean EOF does not set stream error"); + Require(ERR_get_error_nid_postfix() == 0, "clean EOF does not queue LibreSSL errors"); Require(BIO_ctrl_nid_postfix(file, 1, 0, nullptr) == 0, "file reset returns fseek success status"); Require(file->method->read(file, buffer, 3) == 3 && std::memcmp(buffer, "abc", 3) == 0, "read after file BIO reset"); FileStream* queried = nullptr; @@ -599,6 +600,11 @@ void TestFileAndSocket() { Require(pipeBio != nullptr, "construct guest file BIO around pipe writer"); Require(pipeBio->method->read(pipeBio, buffer, 1) == -1, "guest file read error must not report clean EOF"); Require(ferror_nid_postfix(&pipeStream) != 0 && feof_nid_postfix(&pipeStream) == 0, "pipe read failure sets error, not EOF"); + const auto systemError = ERR_get_error_nid_postfix(); + const auto bioError = ERR_get_error_nid_postfix(); + Require((systemError >> 24) == 2 && (bioError >> 24) == 32 && (bioError & 0xfff) == 2, + "failed file BIO read queues system and BIO errors"); + Require(ERR_get_error_nid_postfix() == 0, "file read queues exactly two errors"); Require(BIO_ctrl_nid_postfix(pipeBio, 1, 0, nullptr) == -1, "file reset preserves non-seekable stream failure"); Require(BIO_free_nid_postfix(pipeBio) == 1, "free borrowed pipe BIO"); Require(fclose_nid_postfix(&pipeStream) == 0, "close borrowed pipe writer"); From 62c2b20a099218dcda4fcf194fcfbe6e8986512d Mon Sep 17 00:00:00 2001 From: Cloudy <52116030+xdCloudy@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:58:35 -0700 Subject: [PATCH 105/612] test(libSceLibreSsl): update existing file reset assertion --- core/libs/tests/GuestLibreSslBio.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/libs/tests/GuestLibreSslBio.cpp b/core/libs/tests/GuestLibreSslBio.cpp index 224bb3adeb..b9c324b9a9 100644 --- a/core/libs/tests/GuestLibreSslBio.cpp +++ b/core/libs/tests/GuestLibreSslBio.cpp @@ -565,7 +565,7 @@ void TestFileAndSocket() { Require(file->initialized && BIO_ctrl_nid_postfix(file, 8, 0, nullptr) == 0, "file BIO no-close flag"); Require(BIO_write_nid_postfix(file, "abc", 3) == 3, "write guest file through BIO"); Require(BIO_ctrl_nid_postfix(file, 11, 0, nullptr) == 1, "flush guest file through BIO"); - Require(BIO_ctrl_nid_postfix(file, 1, 0, nullptr) == 1, "rewind guest file through BIO"); + Require(BIO_ctrl_nid_postfix(file, 1, 0, nullptr) == 0, "rewind guest file through BIO"); char buffer[8]{}; Require(file->method->read(file, buffer, 3) == 3 && std::memcmp(buffer, "abc", 3) == 0, "read guest file through BIO"); Require(file->method->read(file, buffer, 1) == 0, "clean guest file EOF returns zero"); From 8acf2c4809361d2dbc2c05689bd2fea5a77dfe0a Mon Sep 17 00:00:00 2001 From: hypermoist Date: Fri, 30 Oct 2026 16:42:16 +1300 Subject: [PATCH 106/612] feat(libSceNpCommon): implement sce::np::Mutex and sce::np::Cond Co-Authored-By: Claude Opus 5.5 --- .../libSceNpCommon/PendingExportsRound7.cpp | 25 --- core/libs/prx/libSceNpCommon/Runtime.cpp | 153 ++++++++++++++++++ core/libs/prx/libSceNpCommon/include/Api.hpp | 43 ++++- core/libs/tests/GuestNpCommonRuntime.cpp | 99 ++++++++++++ docs/dev/debt/libSceNpCommon.md | 6 +- 5 files changed, 291 insertions(+), 35 deletions(-) diff --git a/core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp b/core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp index b1f8e70073..096429f030 100644 --- a/core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp +++ b/core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp @@ -16,11 +16,6 @@ int APS5_VABI sceNpUtilXmlEscape(void) { return 0; } -int APS5_VABI _ZN3sce2np4CondD0Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _sceNpAllocatorExRealloc(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -66,11 +61,6 @@ int APS5_VABI _ZN3sce2np10EventQueueC2EP16SceNpAllocatorEx(void) { return 0; } -int APS5_VABI _ZN3sce2np4Cond4ctorEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce2np16StreamReadBufferD1Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -216,11 +206,6 @@ int APS5_VABI _ZN3sce2np3ipc10IpmiClient7ConnectEPKvm(void) { return 0; } -int APS5_VABI _ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient10DisconnectEv(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -501,11 +486,6 @@ int APS5_VABI _ZN3sce2np14CalloutContextD1Ev(void) { return 0; } -int APS5_VABI _ZN3sce2np5Mutex7DestroyEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce2np14JsonDocBuilderC1ERKNS0_9JsonValueE(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -651,11 +631,6 @@ int APS5_VABI _ZN3sce2np12StreamReader7ReadAllEPNS0_6HandleEPvmPm(void) { return 0; } -int APS5_VABI _ZN3sce2np4CondD1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce2np9EventFlag7DestroyEv(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceNpCommon/Runtime.cpp b/core/libs/prx/libSceNpCommon/Runtime.cpp index b298b975ad..b266db65f8 100644 --- a/core/libs/prx/libSceNpCommon/Runtime.cpp +++ b/core/libs/prx/libSceNpCommon/Runtime.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -201,6 +202,26 @@ NpAllocator MemoryHeapAllocator(NpMemoryHeap* heap) { constexpr std::size_t NP_OBJECT_HEADER_SIZE = 16; +constexpr int NP_ERROR_OBJECT_ALREADY_INITIALIZED = static_cast(0x80559E03); + +void* const NpMutexVtable[] = { + nullptr, nullptr, + reinterpret_cast(&_ZN3sce2np5MutexD1Ev), reinterpret_cast(&_ZN3sce2np5MutexD0Ev), +}; + +void* const NpCondVtable[] = { + nullptr, nullptr, + reinterpret_cast(&_ZN3sce2np4CondD1Ev), reinterpret_cast(&_ZN3sce2np4CondD0Ev), +}; + +void RequireMutexInit(const NpMutexObject* self, const char* function) { + if (!self->isInit) throw std::invalid_argument(std::string(function) + ": the mutex is not initialized"); +} + +void RequireMutexResult(int result, const char* function) { + if (result != 0) throw std::runtime_error(std::string(function) + ": the kernel mutex call failed"); +} + void* AllocateObject(std::size_t size, NpAllocator* allocator) { auto* header = static_cast(allocator->allocate(size + NP_OBJECT_HEADER_SIZE, allocator->context)); if (header == nullptr) return nullptr; @@ -828,4 +849,136 @@ void APS5_VABI sceNpDbgDumpBinary(const void* data, std::size_t size) { std::putchar('\n'); } +void APS5_VABI _ZN3sce2np5MutexC1Ev(NpMutexObject* self) { + self->vtable = &NpMutexVtable[2]; + self->mutex = nullptr; + self->isInit = false; +} + +void APS5_VABI _ZN3sce2np5MutexC2Ev(NpMutexObject* self) { + _ZN3sce2np5MutexC1Ev(self); +} + +void APS5_VABI _ZN3sce2np5Mutex4dtorEv(NpMutexObject* self) { + if (self->isInit) scePthreadMutexDestroy(&self->mutex); +} + +void APS5_VABI _ZN3sce2np5MutexD1Ev(NpMutexObject* self) { + self->vtable = &NpMutexVtable[2]; + _ZN3sce2np5Mutex4dtorEv(self); +} + +void APS5_VABI _ZN3sce2np5MutexD2Ev(NpMutexObject* self) { + _ZN3sce2np5MutexD1Ev(self); +} + +void APS5_VABI _ZN3sce2np5MutexD0Ev(NpMutexObject* self) { + self->vtable = &NpMutexVtable[2]; + if (self->isInit) sceNpMutexDestroy(&self->mutex); + _ZN3sce2np6ObjectdlEPv(self); +} + +void APS5_VABI _ZN3sce2np5Mutex4ctorEv(NpMutexObject* self) { + self->isInit = false; +} + +int APS5_VABI _ZN3sce2np5Mutex4InitEPKcj(NpMutexObject* self, const char* name, std::uint32_t flags) { + if (self->isInit) return NP_ERROR_OBJECT_ALREADY_INITIALIZED; + const int result = sceNpMutexInit(&self->mutex, name, flags & 1); + if (result < 0) return result; + self->isInit = true; + return 0; +} + +void APS5_VABI _ZN3sce2np5Mutex7DestroyEv(NpMutexObject* self) { + if (self->isInit) sceNpMutexDestroy(&self->mutex); + self->isInit = false; +} + +bool APS5_VABI _ZN3sce2np5Mutex7TryLockEv(NpMutexObject* self) { + RequireMutexInit(self, "sce::np::Mutex::TryLock"); + const int result = sceNpMutexTryLock(&self->mutex); + if (result == NP_ERROR_MUTEX_BUSY) return false; + RequireMutexResult(result, "sce::np::Mutex::TryLock"); + return true; +} + +void APS5_VABI _ZN3sce2np5Mutex4LockEv(NpMutexObject* self) { + RequireMutexInit(self, "sce::np::Mutex::Lock"); + RequireMutexResult(sceNpMutexLock(&self->mutex), "sce::np::Mutex::Lock"); +} + +void APS5_VABI _ZN3sce2np5Mutex6UnlockEv(NpMutexObject* self) { + RequireMutexInit(self, "sce::np::Mutex::Unlock"); + RequireMutexResult(sceNpMutexUnlock(&self->mutex), "sce::np::Mutex::Unlock"); +} + +void APS5_VABI _ZN3sce2np4CondC1Ev(NpCondObject* self) { + self->vtable = &NpCondVtable[2]; + self->cond = nullptr; + self->mutex = nullptr; +} + +void APS5_VABI _ZN3sce2np4CondC2Ev(NpCondObject* self) { + _ZN3sce2np4CondC1Ev(self); +} + +void APS5_VABI _ZN3sce2np4Cond4dtorEv(NpCondObject* self) { + if (self->mutex != nullptr) scePthreadCondDestroy(&self->cond); +} + +void APS5_VABI _ZN3sce2np4CondD1Ev(NpCondObject* self) { + self->vtable = &NpCondVtable[2]; + _ZN3sce2np4Cond4dtorEv(self); +} + +void APS5_VABI _ZN3sce2np4CondD2Ev(NpCondObject* self) { + _ZN3sce2np4CondD1Ev(self); +} + +void APS5_VABI _ZN3sce2np4CondD0Ev(NpCondObject* self) { + self->vtable = &NpCondVtable[2]; + if (self->mutex != nullptr) sceNpCondDestroy(&self->cond); + _ZN3sce2np6ObjectdlEPv(self); +} + +void APS5_VABI _ZN3sce2np4Cond4ctorEv(NpCondObject* self) { + self->cond = nullptr; + self->mutex = nullptr; +} + +bool APS5_VABI _ZNK3sce2np4Cond6IsInitEv(const NpCondObject* self) { + return self->mutex != nullptr; +} + +int APS5_VABI _ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(NpCondObject* self, const char* name, NpMutexObject* mutex) { + if (self->mutex != nullptr) return NP_ERROR_OBJECT_ALREADY_INITIALIZED; + const int result = sceNpCondInit(&self->cond, name, 0); + if (result < 0) return result; + self->mutex = reinterpret_cast(reinterpret_cast(mutex) + offsetof(NpMutexObject, mutex)); + return 0; +} + +void APS5_VABI _ZN3sce2np4Cond7DestroyEv(NpCondObject* self) { + if (self->mutex != nullptr) sceNpCondDestroy(&self->cond); + self->cond = nullptr; + self->mutex = nullptr; +} + +int APS5_VABI _ZN3sce2np4Cond4WaitEj(NpCondObject* self, std::uint32_t usec) { + return std::min(sceNpCondTimedwait(&self->cond, self->mutex, usec), 0); +} + +int APS5_VABI _ZN3sce2np4Cond6SignalEv(NpCondObject* self) { + return std::min(sceNpCondSignal(&self->cond), 0); +} + +int APS5_VABI _ZN3sce2np4Cond9SignalAllEv(NpCondObject* self) { + return std::min(sceNpCondSignalAll(&self->cond), 0); +} + +int APS5_VABI _ZN3sce2np4Cond8SignalToEP7pthread(NpCondObject* self, Pthread thread) { + return std::min(sceNpCondSignalTo(&self->cond, thread), 0); +} + } diff --git a/core/libs/prx/libSceNpCommon/include/Api.hpp b/core/libs/prx/libSceNpCommon/include/Api.hpp index d9dbdc9723..e899707a8c 100644 --- a/core/libs/prx/libSceNpCommon/include/Api.hpp +++ b/core/libs/prx/libSceNpCommon/include/Api.hpp @@ -64,6 +64,18 @@ struct NpTitleIdParts { std::uint8_t index; }; +struct NpMutexObject { + const void* const* vtable; + PthreadMutex mutex; + bool isInit; +}; + +struct NpCondObject { + const void* const* vtable; + PthreadCond cond; + PthreadMutex* mutex; +}; + extern "C" { int APS5_VABI sceNpCmpOnlineId(const NpOnlineId* onlineId1, const NpOnlineId* onlineId2); @@ -229,7 +241,6 @@ int APS5_VABI sceNpUtilHttpUrlEncode(char* dst, std::size_t dstSize, const char* int APS5_VABI _ZN3sce2np10CancelableC2Ev(void); int APS5_VABI sceNpUtilXmlEscape(void); -int APS5_VABI _ZN3sce2np4CondD0Ev(void); int APS5_VABI _sceNpAllocatorExRealloc(void); int APS5_VABI _ZN3sce2np8WorkItem6CancelEi(void); int APS5_VABI _ZN3sce2np9WorkQueue11GetItemByIdEi(void); @@ -239,7 +250,6 @@ int APS5_VABI _ZN3sce2np7RingBuf4dtorEv(void); int APS5_VABI _ZThn16_N3sce2np9HttpTransD0Ev(void); int APS5_VABI _ZN3sce2np10MemoryFileD0Ev(void); int APS5_VABI _ZN3sce2np10EventQueueC2EP16SceNpAllocatorEx(void); -int APS5_VABI _ZN3sce2np4Cond4ctorEv(void); int APS5_VABI _ZN3sce2np16StreamReadBufferD1Ev(void); int APS5_VABI _ZNK3sce2np9EventFlag6IsInitEv(void); int APS5_VABI _ZN3sce2np10CancelLockC2Ev(void); @@ -269,7 +279,6 @@ int APS5_VABI _ZN3sce2np12StreamReader4ReadEPNS0_6HandleEPNS0_9StreamCtxEPvmPm(v int APS5_VABI _ZN3sce2np9NpTitleId5ParseEPS1_PKcm(void); int APS5_VABI _ZN3sce2np20BufferedStreamReaderD1Ev(void); int APS5_VABI _ZN3sce2np3ipc10IpmiClient7ConnectEPKvm(void); -int APS5_VABI _ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(void); int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient10DisconnectEv(void); int APS5_VABI _ZN3sce2np3ipc10IpmiClient11IsConnectedEv(void); int APS5_VABI _ZN3sce2np12HttpTemplate19SetAuthInfoCallbackEPFii15SceHttpAuthTypePKcPcS5_iPPhPmPiPvESA_(void); @@ -326,7 +335,6 @@ int APS5_VABI _ZN3sce2np9HttpTrans21GetResponseStatusCodeEPNS0_6HandleEPi(void); int APS5_VABI _ZN3sce2np12StreamReader8SkipDataEPNS0_6HandleElPl(void); int APS5_VABI _ZN3sce2np13NpTitleSecretC2ERKS1_(void); int APS5_VABI _ZN3sce2np14CalloutContextD1Ev(void); -int APS5_VABI _ZN3sce2np5Mutex7DestroyEv(void); int APS5_VABI _ZN3sce2np14JsonDocBuilderC1ERKNS0_9JsonValueE(void); int APS5_VABI _ZN3sce2np9HttpTransD2Ev(void); int APS5_VABI _ZN3sce2np12HttpTemplateC2Ev(void); @@ -356,7 +364,6 @@ int APS5_VABI _ZN3sce2np9LocalFileC1Ev(void); int APS5_VABI _ZN3sce2np7RingBufD2Ev(void); int APS5_VABI _ZN3sce2np4PathD2Ev(void); int APS5_VABI _ZN3sce2np12StreamReader7ReadAllEPNS0_6HandleEPvmPm(void); -int APS5_VABI _ZN3sce2np4CondD1Ev(void); int APS5_VABI _ZN3sce2np9EventFlag7DestroyEv(void); int APS5_VABI _ZN3sce2np9HttpTransC1EP16SceNpAllocatorEx(void); int APS5_VABI _ZN3sce2np6Thread4dtorEv(void); @@ -373,6 +380,32 @@ int APS5_VABI _ZN3sce2np4Time12GetUserClockEPS1_(void); int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient12AbortRequestEii(void); int APS5_VABI _ZNK3sce2np5Mutex6IsInitEv(void); +void APS5_VABI _ZN3sce2np5MutexC1Ev(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5MutexC2Ev(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5MutexD1Ev(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5MutexD2Ev(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5MutexD0Ev(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5Mutex4ctorEv(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5Mutex4dtorEv(NpMutexObject* self); +int APS5_VABI _ZN3sce2np5Mutex4InitEPKcj(NpMutexObject* self, const char* name, std::uint32_t flags); +void APS5_VABI _ZN3sce2np5Mutex7DestroyEv(NpMutexObject* self); +bool APS5_VABI _ZN3sce2np5Mutex7TryLockEv(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5Mutex4LockEv(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5Mutex6UnlockEv(NpMutexObject* self); +void APS5_VABI _ZN3sce2np4CondC1Ev(NpCondObject* self); +void APS5_VABI _ZN3sce2np4CondC2Ev(NpCondObject* self); +void APS5_VABI _ZN3sce2np4CondD1Ev(NpCondObject* self); +void APS5_VABI _ZN3sce2np4CondD2Ev(NpCondObject* self); +void APS5_VABI _ZN3sce2np4CondD0Ev(NpCondObject* self); +void APS5_VABI _ZN3sce2np4Cond4ctorEv(NpCondObject* self); +void APS5_VABI _ZN3sce2np4Cond4dtorEv(NpCondObject* self); +bool APS5_VABI _ZNK3sce2np4Cond6IsInitEv(const NpCondObject* self); +int APS5_VABI _ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(NpCondObject* self, const char* name, NpMutexObject* mutex); +void APS5_VABI _ZN3sce2np4Cond7DestroyEv(NpCondObject* self); +int APS5_VABI _ZN3sce2np4Cond4WaitEj(NpCondObject* self, std::uint32_t usec); +int APS5_VABI _ZN3sce2np4Cond6SignalEv(NpCondObject* self); +int APS5_VABI _ZN3sce2np4Cond9SignalAllEv(NpCondObject* self); +int APS5_VABI _ZN3sce2np4Cond8SignalToEP7pthread(NpCondObject* self, Pthread thread); [[noreturn]] void APS5_VABI sceNpPanic(void); } diff --git a/core/libs/tests/GuestNpCommonRuntime.cpp b/core/libs/tests/GuestNpCommonRuntime.cpp index 28fb5ab918..5c3d53e2a4 100644 --- a/core/libs/tests/GuestNpCommonRuntime.cpp +++ b/core/libs/tests/GuestNpCommonRuntime.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace { @@ -31,6 +32,8 @@ constexpr int NpOutOfRange = static_cast(0x8055800E); constexpr int NpInvalidArgument = static_cast(0x80558009); constexpr int NpEventFlagBusy = static_cast(0x8055800C); constexpr int NpSemaBusy = static_cast(0x8055800D); +constexpr int NpObjectAlreadyInitialized = static_cast(0x80559E03); +constexpr int NpCondTimedOut = static_cast(0x8055800B); constexpr int KernelNotPermitted = static_cast(0x80020001); constexpr int KernelNoSuchObject = static_cast(0x80020003); constexpr std::uint32_t EventFlagWaitAnd = 1; @@ -242,6 +245,101 @@ void TestObjectsThroughStoredAllocator() { "a failed array allocation returns null"); } +struct CondWaiter { + NpMutexObject* mutex; + NpCondObject* cond; + std::atomic stage {0}; + int result = -1; +}; + +void* APS5_VABI WaitOnCond(void* arg) { + auto* waiter = static_cast(arg); + _ZN3sce2np5Mutex4LockEv(waiter->mutex); + waiter->stage = 1; + while (waiter->stage == 1) waiter->result = _ZN3sce2np4Cond4WaitEj(waiter->cond, 5000000); + _ZN3sce2np5Mutex6UnlockEv(waiter->mutex); + return nullptr; +} + +bool WakeWaiter(NpMutexObject* mutex, NpCondObject* cond, bool targeted) { + CondWaiter waiter {mutex, cond}; + Pthread thread {}; + if (sceNpCreateThread(&thread, WaitOnCond, &waiter, 0, 0x10000, 0, "NpCondWaiter") != 0) return false; + while (waiter.stage != 1) std::this_thread::yield(); + _ZN3sce2np5Mutex4LockEv(mutex); + waiter.stage = 2; + const int signalled = targeted ? _ZN3sce2np4Cond8SignalToEP7pthread(cond, thread) : _ZN3sce2np4Cond6SignalEv(cond); + _ZN3sce2np5Mutex6UnlockEv(mutex); + void* value = nullptr; + return signalled == 0 && sceNpJoinThread(thread, &value) == 0 && waiter.result == 0; +} + +template +bool RefusedBeforeInit(Call call) { + try { + call(); + } catch (const std::invalid_argument&) { + return true; + } + return false; +} + +void TestMutexAndCondObjects() { + NpMutexObject mutex {}; + _ZN3sce2np5MutexC1Ev(&mutex); + Require(mutex.vtable != nullptr && !mutex.isInit, "a constructed mutex is not initialized"); + Require(RefusedBeforeInit([&] { _ZN3sce2np5Mutex4LockEv(&mutex); }) && RefusedBeforeInit([&] { _ZN3sce2np5Mutex7TryLockEv(&mutex); }) + && RefusedBeforeInit([&] { _ZN3sce2np5Mutex6UnlockEv(&mutex); }), "locking asserts that the mutex is initialized"); + Require(_ZN3sce2np5Mutex4InitEPKcj(&mutex, "NpMutexObject", 0) == 0 && mutex.isInit, "a mutex is initialized"); + Require(_ZN3sce2np5Mutex4InitEPKcj(&mutex, "NpMutexObject", 0) == NpObjectAlreadyInitialized, "a second Init is refused"); + Require(_ZN3sce2np5Mutex7TryLockEv(&mutex) && !_ZN3sce2np5Mutex7TryLockEv(&mutex), "a held plain mutex cannot be taken again"); + _ZN3sce2np5Mutex6UnlockEv(&mutex); + + NpMutexObject recursive {}; + _ZN3sce2np5MutexC1Ev(&recursive); + Require(_ZN3sce2np5Mutex4InitEPKcj(&recursive, "NpMutexRecursive", 3) == 0, "a recursive mutex is initialized"); + Require(_ZN3sce2np5Mutex7TryLockEv(&recursive) && _ZN3sce2np5Mutex7TryLockEv(&recursive), "bit 0 of the flags makes the mutex recursive"); + _ZN3sce2np5Mutex6UnlockEv(&recursive); + _ZN3sce2np5Mutex6UnlockEv(&recursive); + _ZN3sce2np5Mutex7DestroyEv(&recursive); + _ZN3sce2np5Mutex7DestroyEv(&recursive); + Require(!recursive.isInit, "Destroy leaves the mutex uninitialized and may be repeated"); + + NpCondObject cond {}; + _ZN3sce2np4CondC1Ev(&cond); + Require(cond.vtable != nullptr && !_ZNK3sce2np4Cond6IsInitEv(&cond), "a constructed condition is not initialized"); + Require(_ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(&cond, "NpCondObject", &mutex) == 0 && _ZNK3sce2np4Cond6IsInitEv(&cond) && cond.mutex == &mutex.mutex, + "a condition keeps the kernel mutex of its sce::np::Mutex"); + Require(_ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(&cond, "NpCondObject", &mutex) == NpObjectAlreadyInitialized, "a second Init is refused"); + _ZN3sce2np5Mutex4LockEv(&mutex); + Require(_ZN3sce2np4Cond4WaitEj(&cond, 2000) == NpCondTimedOut, "a timed wait reports the timeout"); + _ZN3sce2np5Mutex6UnlockEv(&mutex); + Require(_ZN3sce2np4Cond6SignalEv(&cond) == 0 && _ZN3sce2np4Cond9SignalAllEv(&cond) == 0, "signalling without waiters succeeds"); + Require(WakeWaiter(&mutex, &cond, false), "Signal wakes a waiter"); + Require(WakeWaiter(&mutex, &cond, true), "SignalTo wakes the given waiter"); + _ZN3sce2np4Cond7DestroyEv(&cond); + Require(!_ZNK3sce2np4Cond6IsInitEv(&cond) && cond.cond == nullptr, "Destroy clears the condition"); + Require(_ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(&cond, "NpCondNullMutex", nullptr) == 0 && _ZNK3sce2np4Cond6IsInitEv(&cond), + "Init with a null mutex stores its member address and counts as initialized"); + _ZN3sce2np4Cond7DestroyEv(&cond); + _ZN3sce2np5Mutex7DestroyEv(&mutex); + + AllocatorRecord record {}; + NpAllocator allocator {RecordAllocate, nullptr, RecordRelease, &record}; + using MutexDestructor = void APS5_VABI (*)(NpMutexObject*); + using CondDestructor = void APS5_VABI (*)(NpCondObject*); + auto* ownedMutex = static_cast(_ZN3sce2np6ObjectnwEmR14SceNpAllocator(sizeof(NpMutexObject), &allocator)); + _ZN3sce2np5MutexC1Ev(ownedMutex); + Require(_ZN3sce2np5Mutex4InitEPKcj(ownedMutex, "NpMutexOwned", 0) == 0, "an allocated mutex is initialized"); + reinterpret_cast(ownedMutex->vtable[1])(ownedMutex); + Require(record.released == record.block.data(), "the mutex deleting destructor frees through the stored allocator"); + record.released = nullptr; + auto* ownedCond = static_cast(_ZN3sce2np6ObjectnwEmR14SceNpAllocator(sizeof(NpCondObject), &allocator)); + _ZN3sce2np4CondC1Ev(ownedCond); + reinterpret_cast(ownedCond->vtable[1])(ownedCond); + Require(record.released == record.block.data(), "the condition deleting destructor frees through the stored allocator"); +} + struct CalloutRecord { std::atomic order {0}; std::atomic first {0}; @@ -523,6 +621,7 @@ int main() { TestHeaps(); TestObjects(); TestObjectsThroughStoredAllocator(); + TestMutexAndCondObjects(); TestBase64(); TestNumbers(); TestHexAndCopies(); diff --git a/docs/dev/debt/libSceNpCommon.md b/docs/dev/debt/libSceNpCommon.md index bfecff7481..17d812a595 100644 --- a/docs/dev/debt/libSceNpCommon.md +++ b/docs/dev/debt/libSceNpCommon.md @@ -46,6 +46,7 @@ Pending import declarations are matched to names in the local libSceNpCommon.spr - [sceNpGetSdkVersion](../../../core/libs/prx/libSceNpCommon/Export.cpp) (libSceNpCommon) - writes the libkernel system software version as `major.minor` into an 8-byte buffer without checking it, as shadPS4 does; that version is the fixed `1.00`, not the SDK version a title was built with. - [The exports in Runtime.cpp](../../../core/libs/prx/libSceNpCommon/Runtime.cpp) and [Strings.cpp](../../../core/libs/prx/libSceNpCommon/Strings.cpp) follow `libSceNpCommon.sprx` from firmware 13.60, read from disassembly and not measured on a console. - [The sce::np::Object new and delete operators](../../../core/libs/prx/libSceNpCommon/Runtime.cpp) (libSceNpCommon) - do not check that the size plus their 16-byte header fits, and free through the allocator stored in that header, ignoring the allocator passed to the delete forms, as `libSceNpCommon.sprx` of firmware 13.60 does, read from disassembly, not measured on a console. The extended forms keep the address of the `SceNpAllocatorEx` itself, whose first member is the allocator. +- [sce::np::Mutex and sce::np::Cond](../../../core/libs/prx/libSceNpCommon/Runtime.cpp) (libSceNpCommon) - throw `std::invalid_argument` when `Mutex::Lock`, `TryLock` or `Unlock` is called before `Init`, and `std::runtime_error` when the kernel call fails (other than busy for `TryLock`), where `libSceNpCommon.sprx` of firmware 13.60 asserts `IsInit()` and `ret == 0` and halts, read from disassembly, not measured on a console. `Cond::Wait` does not check `IsInit` and passes a null mutex, and `Cond::Init` with a null `Mutex` stores the address 8 and counts as initialized, as the module does. - [sceNpCreateThread](../../../core/libs/prx/libSceNpCommon/Runtime.cpp) always applies an explicit priority when one is given; the firmware picks between explicit and inherited scheduling from the title's compiled SDK version, which is not available here. - [sceNpCalloutStartOnCtx and sceNpCalloutStartOnCtx64](../../../core/libs/prx/libSceNpCommon/Runtime.cpp) look for the callout in the queue before writing it and return `0x80558006` with the entry and the queue unchanged. Firmware 13.60, read from disassembly and not measured on a console, clears and fills the entry before it takes the lock and checks the queue, so restarting a queued callout returns the same error but drops every callout queued after it. - [sceNpArchInit and sceNpArchTerm](../../../core/libs/prx/libSceNpCommon/Runtime.cpp) keep a reference count under a lock and create the global heap once. Firmware 13.60, read from disassembly and not measured on a console, uses no lock and never sets its count on the first call, so every call made while the count is zero creates the heap again, and a call to sceNpArchTerm at zero destroys the heap and leaves the count negative. @@ -59,7 +60,6 @@ Pending import declarations are matched to names in the local libSceNpCommon.spr - [sceNpJsonParse, sceNpJsonParseInit, sceNpJsonParseBuf, sceNpJsonParseBufInit, sceNpJsonParseEx, sceNpJsonParseExInit and the sceNpServerErrorJson exports](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) are a streaming, schema-checked JSON parser whose event and error rules are not yet specified; they throw. - [_ZN3sce2np10CancelableC2Ev](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `vqekW3s-eFg`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilXmlEscape](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `5bBPLZV49kY`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [_ZN3sce2np4CondD0Ev](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `AKiHGWhC2KU`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_sceNpAllocatorExRealloc](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `kKF3w-XkCWA`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np8WorkItem6CancelEi](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `Oq5aepLkEWg`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np9WorkQueue11GetItemByIdEi](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `F2umEBpQFHc`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. @@ -69,7 +69,6 @@ Pending import declarations are matched to names in the local libSceNpCommon.spr - [_ZThn16_N3sce2np9HttpTransD0Ev](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `zWIFe+d77PU`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np10MemoryFileD0Ev](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `6Vst7HqJMXU`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np10EventQueueC2EP16SceNpAllocatorEx](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `esiO4He2WTU`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [_ZN3sce2np4Cond4ctorEv](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `n9pzAHeCCVU`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np16StreamReadBufferD1Ev](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `ZufKqNXItD0`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZNK3sce2np9EventFlag6IsInitEv](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `st-oQvV7HVI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np10CancelLockC2Ev](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `MecB8wAHCfE`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. @@ -99,7 +98,6 @@ Pending import declarations are matched to names in the local libSceNpCommon.spr - [_ZN3sce2np9NpTitleId5ParseEPS1_PKcm](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `-swgMjedLUQ`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np20BufferedStreamReaderD1Ev](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `Tgr66MThOxA`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np3ipc10IpmiClient7ConnectEPKvm](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `CGKtxL26XqI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [_ZN3sce2np4Cond4InitEPKcPNS0_5MutexE](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `wWTqVcTnep8`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np3ipc17ServiceIpmiClient10DisconnectEv](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `uvYTUK5xYG8`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np3ipc10IpmiClient11IsConnectedEv](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `prj9aMR74bA`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np12HttpTemplate19SetAuthInfoCallbackEPFii15SceHttpAuthTypePKcPcS5_iPPhPmPiPvESA_](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `ezJnmv7hkAg`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. @@ -156,7 +154,6 @@ Pending import declarations are matched to names in the local libSceNpCommon.spr - [_ZN3sce2np12StreamReader8SkipDataEPNS0_6HandleElPl](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `MSMPXUL5AuM`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np13NpTitleSecretC2ERKS1_](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `JHG9CTmkdQw`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np14CalloutContextD1Ev](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `S+a+rgnGX8A`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [_ZN3sce2np5Mutex7DestroyEv](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `RgGW4f0ox1g`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np14JsonDocBuilderC1ERKNS0_9JsonValueE](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `ikFI73f3hP4`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np9HttpTransD2Ev](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `GSVe-aaTiEg`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np12HttpTemplateC2Ev](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `hw-UPUK9T+w`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. @@ -186,7 +183,6 @@ Pending import declarations are matched to names in the local libSceNpCommon.spr - [_ZN3sce2np7RingBufD2Ev](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `9+NmoosRoBA`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np4PathD2Ev](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `1nF0eXrBZYM`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np12StreamReader7ReadAllEPNS0_6HandleEPvmPm](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `f1ncwa-JXlA`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [_ZN3sce2np4CondD1Ev](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `yX9ISVXv+0M`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np9EventFlag7DestroyEv](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `QlaBcxSFPZI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np9HttpTransC1EP16SceNpAllocatorEx](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `sqNxD6H5ZOQ`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [_ZN3sce2np6Thread4dtorEv](../../../core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp) (NID `-3gV5N2u-sc`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. From a1080ca83331a0990ce20fc1592065490c6bfe69 Mon Sep 17 00:00:00 2001 From: iAbdulrrahman Date: Fri, 30 Oct 2026 11:16:00 +0300 Subject: [PATCH 107/612] feat(libkernel_sys): implement __sys_nanosleep export --- core/libs/CMakeLists.txt | 10 ++ .../prx/libkernel_sys/AdditionalExports.cpp | 13 ++- .../include/AdditionalExports.hpp | 2 +- core/libs/tests/GuestKernelSysNanosleep.cpp | 104 ++++++++++++++++++ docs/dev/debt/libkernel_sys.md | 6 +- 5 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 core/libs/tests/GuestKernelSysNanosleep.cpp diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 910a26689d..53e6bb7b1f 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -453,6 +453,16 @@ if(BUILD_TESTING) configure_windows_unwind(guest_kernel_sys_barrier_tests) add_test(NAME guest_kernel_sys_barrier COMMAND guest_kernel_sys_barrier_tests) set_tests_properties(guest_kernel_sys_barrier PROPERTIES TIMEOUT 20) + add_test_executable(guest_kernel_sys_nanosleep_tests tests/GuestKernelSysNanosleep.cpp) + target_include_directories(guest_kernel_sys_nanosleep_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_nanosleep_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_nanosleep_tests) + add_test(NAME guest_kernel_sys_nanosleep COMMAND guest_kernel_sys_nanosleep_tests) + set_tests_properties(guest_kernel_sys_nanosleep PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_nanosleep PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_kernel_sys_pthread_tests tests/GuestKernelSysPthread.cpp) target_include_directories(guest_kernel_sys_pthread_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_kernel_sys_pthread_tests PRIVATE libkernel_sys libkernel libc) diff --git a/core/libs/prx/libkernel_sys/AdditionalExports.cpp b/core/libs/prx/libkernel_sys/AdditionalExports.cpp index a22aa86a2c..f61557ff30 100644 --- a/core/libs/prx/libkernel_sys/AdditionalExports.cpp +++ b/core/libs/prx/libkernel_sys/AdditionalExports.cpp @@ -1,5 +1,8 @@ #include "prx/libkernel_sys/include/AdditionalExports.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" extern "C" { @@ -83,8 +86,14 @@ int APS5_VABI __sys_namedobj_delete_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI __sys_nanosleep_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI __sys_nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp) { + int oldCancelState = 0; + scePthreadSetcancelstate(1, &oldCancelState); + const int result = sceKernelNanosleep(rqtp, rmtp); + scePthreadSetcancelstate(oldCancelState, nullptr); + if (result == 0) return 0; + *__error_nid_postfix() = result & 0xFFFF; + return -1; } int APS5_VABI __sys_netabort_nid_postfix() { diff --git a/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp b/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp index e5634b6744..716ba22097 100644 --- a/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp +++ b/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp @@ -24,7 +24,7 @@ int APS5_VABI __sys_is_development_mode_nid_postfix(); int APS5_VABI __sys_kqueueex_nid_postfix(); int APS5_VABI __sys_namedobj_create_nid_postfix(); int APS5_VABI __sys_namedobj_delete_nid_postfix(); -int APS5_VABI __sys_nanosleep_nid_postfix(); +int APS5_VABI __sys_nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp); int APS5_VABI __sys_netabort_nid_postfix(); int APS5_VABI __sys_netcontrol_nid_postfix(); int APS5_VABI __sys_netgetiflist_nid_postfix(); diff --git a/core/libs/tests/GuestKernelSysNanosleep.cpp b/core/libs/tests/GuestKernelSysNanosleep.cpp new file mode 100644 index 0000000000..9000072010 --- /dev/null +++ b/core/libs/tests/GuestKernelSysNanosleep.cpp @@ -0,0 +1,104 @@ +#include "prx/libkernel_sys/include/AdditionalExports.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +static constexpr int SCE_OK = 0; +static constexpr int GUEST_EACCES = 13; +static constexpr int GUEST_EFAULT = 14; +static constexpr int GUEST_EINVAL = 22; +static constexpr std::int64_t NANOS_PER_SECOND = 1000000000LL; +static constexpr std::int64_t NANOS_PER_MILLISECOND = 1000000LL; +static constexpr std::int64_t SLEEP_NANOS = 20 * NANOS_PER_MILLISECOND; + +static std::atomic cancelThreadReady{false}; +static std::atomic cancelThreadRelease{false}; +static std::atomic cancelSleepReturned{false}; + +static void Require(bool value) { + if (!value) std::abort(); +} + +static void PosixRejects(const KernelTimespec* request, int error) { + *__error_nid_postfix() = GUEST_EACCES; + Require(__sys_nanosleep_nid_postfix(request, nullptr) == -1); + Require(*__error_nid_postfix() == error); +} + +static void TestRejectsInvalidRequests() { + const KernelTimespec negativeNanos{0, -1}; + const KernelTimespec wholeSecondOfNanos{0, NANOS_PER_SECOND}; + const KernelTimespec negativeBoth{-1, -1}; + PosixRejects(&negativeNanos, GUEST_EINVAL); + PosixRejects(&wholeSecondOfNanos, GUEST_EINVAL); + PosixRejects(&negativeBoth, GUEST_EINVAL); + PosixRejects(nullptr, GUEST_EFAULT); +} + +static void TestNegativeAndZeroSeconds() { + *__error_nid_postfix() = GUEST_EACCES; + KernelTimespec request{-1, 0}; + Require(__sys_nanosleep_nid_postfix(&request, nullptr) == SCE_OK); + Require(*__error_nid_postfix() == GUEST_EACCES); + + request = {0, 0}; + Require(__sys_nanosleep_nid_postfix(&request, nullptr) == SCE_OK); + Require(*__error_nid_postfix() == GUEST_EACCES); +} + +static void TestValidSleep() { + const KernelTimespec request{0, SLEEP_NANOS}; + KernelTimespec remaining{5, 7}; + *__error_nid_postfix() = GUEST_EACCES; + const auto start = std::chrono::steady_clock::now(); + Require(__sys_nanosleep_nid_postfix(&request, &remaining) == SCE_OK); + const auto elapsed = std::chrono::steady_clock::now() - start; + Require(*__error_nid_postfix() == GUEST_EACCES); + Require(remaining.tv_sec == 5 && remaining.tv_nsec == 7); + Require(elapsed >= std::chrono::milliseconds(10)); +} + +static void* APS5_VABI PendingCancelWorker(void*) { + cancelThreadReady.store(true); + cancelThreadReady.notify_all(); + while (!cancelThreadRelease.load()) { + std::this_thread::yield(); + } + const KernelTimespec request{0, 20 * NANOS_PER_MILLISECOND}; + Require(__sys_nanosleep_nid_postfix(&request, nullptr) == SCE_OK); + cancelSleepReturned.store(true); + return nullptr; +} + +static void TestPendingCancelDoesNotFire() { + cancelThreadReady.store(false); + cancelThreadRelease.store(false); + cancelSleepReturned.store(false); + + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, PendingCancelWorker, nullptr, "nanosleep_cancel_test") == SCE_OK); + while (!cancelThreadReady.load()) { + std::this_thread::yield(); + } + Require(scePthreadCancel(thread) == SCE_OK); + cancelThreadRelease.store(true); + cancelThreadRelease.notify_all(); + + void* result = nullptr; + Require(scePthreadJoin(thread, &result) == SCE_OK); + Require(cancelSleepReturned.load()); + Require(result == nullptr); +} + +int main() { + TestRejectsInvalidRequests(); + TestNegativeAndZeroSeconds(); + TestValidSleep(); + TestPendingCancelDoesNotFire(); + return 0; +} diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 770ec9cc22..abd5cd1206 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -6,7 +6,7 @@ ### Unknown function info -- [libkernel_sys additional exports](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) - 328 callable exports have import-resolution declarations only. Parameter lists, return types and behavior remain unverified. Parameterless `int` declarations are placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. Unresolved names use literal NID aliases; no runtime functionality or data objects are fabricated. Existing providers and exports supplied by open pull requests are excluded. Kernel variant imports already resolve through `libkernel`, then `libc`; existing providers remain unchanged. Six native data exports (`errno`, `__stack_chk_guard`, `_sigintr`, `__progname`, `environ`, and NID `AxgW94umKqU`) require separate storage, layout, TLS and ownership validation and are not added as callable aliases or fabricated globals. +- [libkernel_sys additional exports](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) - 327 callable exports have import-resolution declarations only. Parameter lists, return types and behavior remain unverified. Parameterless `int` declarations are placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. Unresolved names use literal NID aliases; no runtime functionality or data objects are fabricated. Existing providers and exports supplied by open pull requests are excluded. Kernel variant imports already resolve through `libkernel`, then `libc`; existing providers remain unchanged. Six native data exports (`errno`, `__stack_chk_guard`, `_sigintr`, `__progname`, `environ`, and NID `AxgW94umKqU`) require separate storage, layout, TLS and ownership validation and are not added as callable aliases or fabricated globals. | Name | NID | | --- | --- | @@ -30,7 +30,6 @@ | `__sys_kqueueex` | `2M+dFM8Wmq4` | | `__sys_namedobj_create` | `3CNY4Z0Luc8` | | `__sys_namedobj_delete` | `E7CmfLfeSuQ` | -| `__sys_nanosleep` | `msfSXXy-8H8` | | `__sys_netabort` | `UTR6wAkajxk` | | `__sys_netcontrol` | `zsTvhCDrOjQ` | | `__sys_netgetiflist` | `X86Q0qQJ1m0` | @@ -378,6 +377,7 @@ - [pthread_set_defaultstacksize_np and scePthreadSetDefaultstacksize](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) set the stack size that `scePthreadAttrInit` and a thread created without attributes use, as [fpPS4's `pthread_set_defaultstacksize_np`](https://github.com/red-prig/fpPS4/blob/04cefd4/kernel/ps4_pthread_attr.pas#L89-L102) does: a size of 16384 bytes or less fails with `EINVAL` (`0x80020016` from the sce form) and there is no upper limit, so a default the host cannot allocate fails only when a thread is created. Attributes initialised earlier keep their size. Not checked on a PS5 - [sceKernelTitleWorkaroundIsEnabled](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - not measured on a console; the `{version, align, ids[2]}` layout, the bit test and the limit of 0x3A bits follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/kernel/kernel.cpp), which notes that the limit changes between firmwares. A negative bit is `EINVAL` here; shadPS4 compares it signed and reads outside `ids`. Titles usually pass the `title_workaround` block of `sceKernelGetAppInfo`, which is written as zeros here; where `title_workaround` sits in the 0x58-byte structure is unmeasured, and with the shadPS4 offsets (0x30 to 0x48) it falls in the zeroed bytes, so its bits read 0; the console's values in a game are unmeasured - [sceKernelGetProcessType](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - not measured on a console; as in shadPS4, the title's own pid, -1 and 0 are type 0 (big application) and any other pid is `ESRCH` +- [__sys_nanosleep](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) follows FreeBSD 11.4 libc's [`nanosleep.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/libc/sys/nanosleep.c) as the raw system call underneath `nanosleep`, executing without cancellation checks through `sceKernelNanosleep` and mapping errors to errno and -1. It inherits libkernel's sleep limits: `rmtp` is never written, `EINTR` is not produced, seconds are capped at `INT32_MAX / 2`, [short sleeps can wake early](libkernel.md#short-sleeps-ended-by-gpu-and-flip-completions), and console behaviour is unverified. - [cpuset, cpuset_getid, cpuset_setid](../../../core/libs/prx/libkernel_sys/Cpuset.cpp) (libkernel_sys) - console root IDs and inheritance are unverified; non-process scopes (TID, CPUSET, JAIL) throw as unmodelled as per-thread cpusets and hierarchy visibility are not tracked. CPU set assignment and queries follow FreeBSD 11.4 [kern_cpuset.c](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_cpuset.c): cpuset allocates sequential IDs from 2; cpuset_setid supports CPU_WHICH_PID for known IDs (or root 0, default 1); dynamic sets are single-process referenced and unlinked when vacated, returning ESRCH on lookup; cpuset_getid returns root 0 for CPU_LEVEL_ROOT and the assigned ID for CPU_LEVEL_CPUSET. Output writes use GuestCopyOut_nid_no_patch (EFAULT on bad pointers), with level/which/id errors returned before copyout; levels outside 1 to 3 return EINVAL where FreeBSD returns 0 with the base set ID; other which return EINVAL, unknown pids return ESRCH - [sceKernelSettimeofday](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - forwards to `settimeofday` and returns its result, or `0x80020000` plus `errno` for a failure, as shadPS4's [`sceKernelSettimeofday`](https://github.com/shadps4-emu/shadPS4/blob/7ea677e56d75bf3d90ace2b0a5144a665d1f38b6/src/core/libraries/kernel/time.cpp#L565-L571) maps its call through `ErrnoToSceKernelError`; that branch passes the call's return value, and shadPS4's own `settimeofday` never fails, so the errno form the other sce wrappers here use is taken. Nothing is measured on the console, and a valid time inherits `settimeofday`'s refusal to set the host clock @@ -394,7 +394,7 @@ The NID set is from the inspected `libkernel_sys` ELF function-export table (defined `STT_FUNC` entries), not the older 3.20 inventory. The module-parameter words at `0x10` and `0x14` are `0x13590001` and `0x13600007`; the earlier 13.60 label refers to SDK-formatted metadata, not independently authenticated retail firmware or an introduction date. The [reproducible static audit](https://github.com/boykopovar/AnyPS5/pull/12367#issuecomment-6470603298) finds 1551 unique function NIDs with set digest `5f1668cc9c69e2950722a83151f31cf7aa94fb1552ab883e5067f43c94d63b16` and confirms all 328 proposed entries. The digest hashes sorted unique ASCII NIDs, each followed by LF. Named declarations are candidate spellings whose repository-computed NID exactly matches an observed function NID; the hashed table establishes neither plaintext spelling nor C ABI. -The 35 formerly literal aliases are named by the [repository-pinned aerolib table](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). There are now 168 named declarations and 160 unresolved NID aliases; every one of the 328 entries remains a throwing placeholder. +The 35 formerly literal aliases are named by the [repository-pinned aerolib table](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). There are now 167 named declarations and 160 unresolved NID aliases; every one of the 327 entries remains a throwing placeholder. The following eight candidate spellings are absent from that aerolib revision. Their name/NID pairs occur in the [public ps5rs identity database at commit `2304be5131221bae0dac24b9767cabffda33f747`](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv) and independently recompute to the function NIDs in the inspected table. This database supports candidate spelling only; it does not establish firmware membership, introduction version or ABI. From 802656fbca0f20514911e8c2d8234a5cb8c911f3 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Fri, 23 Oct 2026 14:01:55 -0400 Subject: [PATCH 108/612] fix(libc): set EDOM in fmod and fmodf where titles' libc.prx does fmod and fmodf preserved errno, following FreeBSD msun, but the libc.prx of eleven titles sets EDOM exactly where msun returns (x*y)/(x*y): a zero divisor, an infinite or NaN dividend, or a NaN divisor. There a NaN divisor is returned quieted, then a NaN dividend, then the default NaN. Both functions now do the same; finite results still come from the host. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Math.cpp | 28 +++++++++++++-- core/libs/tests/GuestMath.cpp | 63 +++++++++++++++++++++++++++++++-- docs/dev/debt/libc.md | 2 +- 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 7b6f8ff4ca..2de7f4cf80 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -428,6 +428,18 @@ std::int64_t APS5_VABI imaxabs_nid_postfix(std::int64_t x) { } float APS5_VABI fmodf_nid_postfix(float x, float y) { + const std::uint32_t ix = std::bit_cast(x) & 0x7fffffffu; + const std::uint32_t iy = std::bit_cast(y) & 0x7fffffffu; + if (iy == 0 || ix >= 0x7f800000u || iy > 0x7f800000u) { + errno = EDOM; + if (iy > 0x7f800000u) { + return y + y; + } + if (ix > 0x7f800000u) { + return x + x; + } + return (x * y) / (x * y); + } const int saved_errno = errno; const float result = std::fmod(x, y); errno = saved_errno; @@ -1786,8 +1798,20 @@ float APS5_VABI ldexpf_nid_postfix(float x, int exp) { return result; } double APS5_VABI fmod_nid_postfix(double x, double y) { - int saved_errno = errno; - double result = std::fmod(x, y); + const std::uint64_t ix = std::bit_cast(x) & 0x7fffffffffffffffull; + const std::uint64_t iy = std::bit_cast(y) & 0x7fffffffffffffffull; + if (iy == 0 || ix >= 0x7ff0000000000000ull || iy > 0x7ff0000000000000ull) { + errno = EDOM; + if (iy > 0x7ff0000000000000ull) { + return y + y; + } + if (ix > 0x7ff0000000000000ull) { + return x + x; + } + return (x * y) / (x * y); + } + const int saved_errno = errno; + const double result = std::fmod(x, y); errno = saved_errno; return result; } diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 58c693c4c5..7d4c7c0762 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -761,7 +761,7 @@ static void CheckFmodfErrno() { const std::uint32_t bits = std::bit_cast(result); const int invalid = std::fetestexcept(FE_INVALID) != 0; const bool nan = std::isnan(result); - if (nan != test.nan || (!nan && bits != test.expected) || invalid != test.invalid || *__error_nid_postfix() != 73) { + if (nan != test.nan || (!nan && bits != test.expected) || invalid != test.invalid || *__error_nid_postfix() != (test.nan ? EDOM : 73)) { std::fprintf(stderr, "Guest fmodf failed for %08x and %08x: got %08x, invalid %d, errno %d\n", test.dividend, test.divisor, bits, invalid, *__error_nid_postfix()); std::abort(); @@ -771,7 +771,7 @@ static void CheckFmodfErrno() { std::feraiseexcept(FE_DIVBYZERO); *__error_nid_postfix() = 73; (void)fmodf_nid_postfix(1.0f, 0.0f); - if (*__error_nid_postfix() != 73 || std::fetestexcept(FE_DIVBYZERO) == 0) { + if (*__error_nid_postfix() != EDOM || std::fetestexcept(FE_DIVBYZERO) == 0) { std::fprintf(stderr, "Guest fmodf dropped the floating-point environment\n"); std::abort(); } @@ -852,7 +852,7 @@ static void CheckNearbyintl() { static void CheckFmodErrno() { errno = 0; (void)fmod_nid_postfix(1.0, 0.0); - Require(errno == 0); + Require(errno == EDOM); Require(fmod_nid_postfix(1e300, 3.0) == std::fmod(1e300, 3.0)); } @@ -1262,6 +1262,62 @@ static void CheckExpm1ScalingRounding() { } _mm_setcsr(savedCsr); } +static void CheckFmodConsoleBits() { + constexpr struct { + std::uint64_t x; + std::uint64_t y; + std::uint64_t expected; + int expectedErrno; + } cases[] = { + {0x3ff0000000000000ull, 0x0000000000000000ull, 0xfff8000000000000ull, EDOM}, + {0xbff0000000000000ull, 0x8000000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff0000000000000ull, 0x4000000000000000ull, 0xfff8000000000000ull, EDOM}, + {0xfff0000000000000ull, 0x7ff0000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff8000000000123ull, 0x4000000000000000ull, 0x7ff8000000000123ull, EDOM}, + {0xfff8000000000123ull, 0x0000000000000000ull, 0xfff8000000000123ull, EDOM}, + {0x3ff0000000000000ull, 0x7ff8000000000456ull, 0x7ff8000000000456ull, EDOM}, + {0x7ff8000000000123ull, 0xfff8000000000456ull, 0xfff8000000000456ull, EDOM}, + {0x7ff4000000000000ull, 0x4000000000000000ull, 0x7ffc000000000000ull, EDOM}, + {0x3ff0000000000000ull, 0x7ff4000000000000ull, 0x7ffc000000000000ull, EDOM}, + {0x7ff0000000000000ull, 0x7ff8000000000456ull, 0x7ff8000000000456ull, EDOM}, + {0x3ff0000000000000ull, 0x7ff0000000000000ull, 0x3ff0000000000000ull, 0}, + {0x4016000000000000ull, 0x4000000000000000ull, 0x3ff8000000000000ull, 0}, + {0x0000000000000000ull, 0x8000000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x0000000000000003ull, 0x0000000000000002ull, 0x0000000000000001ull, 0}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(fmod_nid_postfix(std::bit_cast(item.x), std::bit_cast(item.y))) == item.expected); + Require(errno == (item.expectedErrno != 0 ? item.expectedErrno : 1234)); + } + constexpr struct { + std::uint32_t x; + std::uint32_t y; + std::uint32_t expected; + int expectedErrno; + } floatCases[] = { + {0x3f800000u, 0x00000000u, 0xffc00000u, EDOM}, + {0xbf800000u, 0x80000000u, 0xffc00000u, EDOM}, + {0x7f800000u, 0x40000000u, 0xffc00000u, EDOM}, + {0xff800000u, 0x7f800000u, 0xffc00000u, EDOM}, + {0x7fc00123u, 0x40000000u, 0x7fc00123u, EDOM}, + {0xffc00123u, 0x00000000u, 0xffc00123u, EDOM}, + {0x3f800000u, 0x7fc00456u, 0x7fc00456u, EDOM}, + {0x7fc00123u, 0xffc00456u, 0xffc00456u, EDOM}, + {0x7fa00000u, 0x40000000u, 0x7fe00000u, EDOM}, + {0x3f800000u, 0x7fa00000u, 0x7fe00000u, EDOM}, + {0x7f800000u, 0x7fc00456u, 0x7fc00456u, EDOM}, + {0x3f800000u, 0x7f800000u, 0x3f800000u, 0}, + {0x40b00000u, 0x40000000u, 0x3fc00000u, 0}, + {0x00000000u, 0x80000000u, 0xffc00000u, EDOM}, + {0x00000003u, 0x00000002u, 0x00000001u, 0}, + }; + for (const auto& item : floatCases) { + errno = 1234; + Require(std::bit_cast(fmodf_nid_postfix(std::bit_cast(item.x), std::bit_cast(item.y))) == item.expected); + Require(errno == (item.expectedErrno != 0 ? item.expectedErrno : 1234)); + } +} static void CheckAsinl() { const struct { std::uint64_t inputMantissa; std::uint16_t inputSignExponent; std::uint64_t outputMantissa; std::uint16_t outputSignExponent; } cases[] = { @@ -2610,6 +2666,7 @@ int main() { CheckNextafterEnvironment(nextafterf_nid_postfix, 0x80000000u, 0x3f800000u, 0x00800000u, 0x7f7fffffu, 0x7f800000u); CheckNextafterEnvironment(nextafter_nid_postfix, std::uint64_t{0x8000000000000000ull}, std::uint64_t{0x3ff0000000000000ull}, std::uint64_t{0x0010000000000000ull}, std::uint64_t{0x7fefffffffffffffull}, std::uint64_t{0x7ff0000000000000ull}); CheckExpm1ScalingRounding(); + CheckFmodConsoleBits(); CheckAsinl(); Require(scalbn_nid_postfix(0.75, -2) == 0.1875); Require(scalbnf_nid_postfix(0.75f, 4) == 12.f); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 0da74d906f..4810ab2c02 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -103,7 +103,6 @@ - [Math.cpp](../../../core/libs/prx/libc/src/Math.cpp) (libc) and [SignalList.cpp](../../../core/libs/prx/libSceLibcInternal/SignalList.cpp) (libSceLibcInternal) start with comments that keep the upstream FreeBSD, Sun Microsystems and University of California licence notices of the code they are derived from. The licences require the notices to stay with the code, so they are comments although they are not technical debt, and the conventions check cannot tell them from other comments. Closes when: the conventions check accepts licence notices without a debt entry - [_Cosh](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows the two-argument Dinkumware CRT helper used by `std::complex`, including a scaled large-finite-input path; the signature, exact result and floating-point exception behavior are not verified against the console. - [std::thread::hardware_concurrency](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `CHrhwd8QSBs`) - the starting mask of a title's process, and so the value a title sees, are not measured, and neither is whether the console's count follows a process-wide `cpuset_setaffinity`. This returns 13, the number of CPUs in libkernel's default thread mask (`0x1FFF`), and a process-wide `cpuset_setaffinity` does not change it. In payload processes whose threads started with mask `0xea00` it returned 5 on PS5 firmware 13.60 and 12.70, while `hw.ncpu` read 16 and `sceKernelGetAvailableCpumask` `0x1fff` (both on 13.60). On 12.70 it still returned 5 when called first after `scePthreadSetaffinity` had narrowed the calling thread to one CPU, after the mask was restored, and in a thread created with a two-CPU mask. PPSA34015 imports it. -- [fmodf](../../../core/libs/prx/libc/src/Math.cpp) (libc) - console `errno`, exception flags and NaN payloads remain unverified. The wrapper preserves `errno` like the existing double `fmod`, following FreeBSD 11.4 [`e_fmodf.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_fmodf.c); host `std::fmod` still supplies the result and exception flags. - [_LCosh and _LSinh](../../../core/libs/prx/libc/src/Math.cpp) (libc) follow the two-argument Dinkumware CRT helpers `y * cosh(x)` and `y * sinh(x)`, including scaled large-finite-input paths; the signatures are confirmed by Dinkumware's public `ymath.h`, but the exact console results, range-error thresholds and floating-point exception behavior are not verified. - [modf and modff](../../../core/libs/prx/libc/src/Math.cpp) (libc) - native PS5 execution, Windows and unmasked exceptions remain unverified. Linux comparisons with bundled libraries from PPSA03766 and PPSA07923 cover result and integral bits, errno, SSE/x87 flags and control state. Signaling NaNs retain their payload and quiet bit; the measured float path raises SSE invalid while the double path leaves flags unchanged. - [_FSinh and _FCosh](../../../core/libs/prx/libc/src/Math.cpp) (libc) finite results are not verified bit for bit against the console: the scaled product uses the host's binary64 hyperbolic function before rounding to binary32, rather than the float rational and `_FExp` sequence in PRAGMATA's bundled `libc.prx`. NaN preference and payloads, signed zeros, infinite operands and the range-error thresholds follow that binary's instructions; the system `libSceLibcInternal` implementation and nondefault flush-to-zero modes are unverified. Against `libSceLibcInternal.sprx` on a PS5 (firmware 13.60), 12 of 36 measured results differ by one unit in the last place (`_FSinh(0.5, y)` and `_FCosh(1, y)`, `_FCosh(10, y)` for y of 0.5, 1 and 2); zeros, the overflow to infinity at 89 and 90 and the tiny-argument results matched @@ -112,6 +111,7 @@ - [Host stderr preservation](../../../core/libs/prx/libc/src/General.cpp) (WebAssembly) uses descriptor 2 directly, so host diagnostics do not retain an independent stream across guest stderr redirection - [Guest arena](../../../core/libs/prx/libc/src/GuestArena.cpp) (WebAssembly) is limited to an 8 GiB reservation, WebAssembly linear memory does not enforce guest page read, write or execute protections, and host region release throws because host virtual-address reservations are unavailable - [Guest long double conversions](../../../core/libs/prx/libc/include/GuestLongDouble.hpp) (WebAssembly) - storing a binary128 value discards fraction bits beyond x87 precision instead of rounding them; noncanonical x87 encodings are unverified +- [fmod and fmodf](../../../core/libs/prx/libc/src/Math.cpp) (libc) take their finite results and exception flags from the host `std::fmod`; the flags were not compared with the console. They set `EDOM` exactly where FreeBSD 11.4 [`e_fmod.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_fmod.c) returns `(x*y)/(x*y)`: a zero divisor, an infinite or NaN dividend, or a NaN divisor. There a NaN divisor is returned quieted, then a NaN dividend, then the default NaN. The `libc.prx` bundled with eleven titles (PPSA04158 to PPSA35169) gives the same bits and `errno` on 4,000,256 sampled pairs per function on a Windows host. - [sscanf_s and vsscanf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) return `EOF` when a reached conversion has a null destination, stopping before further assignments, following C11 K.3.5.3.7 and K.3.5.3.14. Earlier assignments are retained. The constraint handler is not invoked, and console behaviour is unverified. - [memmem and memccpy](../../../core/libs/prx/libc/src/Strings.cpp) (libc) - `memmem` searches for needle occurrences in memory buffers and `memccpy` copies buffers until a character match, following FreeBSD and POSIX specifications; behavior is unverified on console. Neither is exported by `libSceLibcInternal.sprx` on a PS5 (firmware 13.60), so a payload process cannot call them there. From 6215d19689c83c8bd0d33239a341e7394cac215e Mon Sep 17 00:00:00 2001 From: JJenkx Date: Wed, 28 Oct 2026 07:06:19 -0400 Subject: [PATCH 109/612] fix(libc): raise FE_INVALID from fmod and fmodf for a NaN argument Titles' libc.prx raises FE_INVALID wherever fmod and fmodf store EDOM, a quiet NaN argument included, where returning x + x or y + y raised nothing. The domain paths now raise through _Feraise, which stores EDOM and raises the flag in both units. Results and errno are unchanged. CheckFmodEdgeFlags covers both functions. CheckFmodfErrno expected no FE_INVALID for its three quiet NaN rows, as C specifies; PPSA07923's and PPSA13934's libc.prx raise it there, so those rows now expect it. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Math.cpp | 4 ++-- core/libs/tests/GuestMath.cpp | 22 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 2de7f4cf80..6fdac889ec 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -431,7 +431,7 @@ float APS5_VABI fmodf_nid_postfix(float x, float y) { const std::uint32_t ix = std::bit_cast(x) & 0x7fffffffu; const std::uint32_t iy = std::bit_cast(y) & 0x7fffffffu; if (iy == 0 || ix >= 0x7f800000u || iy > 0x7f800000u) { - errno = EDOM; + _Feraise_nid_postfix(0x01); if (iy > 0x7f800000u) { return y + y; } @@ -1801,7 +1801,7 @@ double APS5_VABI fmod_nid_postfix(double x, double y) { const std::uint64_t ix = std::bit_cast(x) & 0x7fffffffffffffffull; const std::uint64_t iy = std::bit_cast(y) & 0x7fffffffffffffffull; if (iy == 0 || ix >= 0x7ff0000000000000ull || iy > 0x7ff0000000000000ull) { - errno = EDOM; + _Feraise_nid_postfix(0x01); if (iy > 0x7ff0000000000000ull) { return y + y; } diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 7d4c7c0762..3624319726 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -740,14 +740,14 @@ static void CheckFmodfErrno() { const RestoreFloatingPointState saved; const struct { std::uint32_t dividend; std::uint32_t divisor; std::uint32_t expected; bool invalid; bool nan; } cases[] = { {0x3f800000u, 0x00000000u, 0u, true, true}, {0x3f800000u, 0x80000000u, 0u, true, true}, - {0x7f800000u, 0x40000000u, 0u, true, true}, {0x7fc00000u, 0x40000000u, 0u, false, true}, + {0x7f800000u, 0x40000000u, 0u, true, true}, {0x7fc00000u, 0x40000000u, 0u, true, true}, {0x3f800000u, 0x7f800000u, 0x3f800000u, false, false}, {0xbf800000u, 0x7f800000u, 0xbf800000u, false, false}, {0x41280000u, 0x40000000u, 0x3f000000u, false, false}, {0xc1280000u, 0x40000000u, 0xbf000000u, false, false}, {0x41280000u, 0xc0000000u, 0x3f000000u, false, false}, {0xc1280000u, 0xc0000000u, 0xbf000000u, false, false}, {0x00000000u, 0x40000000u, 0x00000000u, false, false}, {0x80000000u, 0x40000000u, 0x80000000u, false, false}, {0x00000000u, 0x80000000u, 0u, true, true}, - {0x3f800000u, 0x7fc00001u, 0u, false, true}, - {0x00000000u, 0x7fc00001u, 0u, false, true}, + {0x3f800000u, 0x7fc00001u, 0u, true, true}, + {0x00000000u, 0x7fc00001u, 0u, true, true}, {0x7f800001u, 0x40000000u, 0u, true, true}, {0x3f800000u, 0x7f800001u, 0u, true, true}, {0x00000003u, 0x00000002u, 0x00000001u, false, false}, @@ -1262,6 +1262,21 @@ static void CheckExpm1ScalingRounding() { } _mm_setcsr(savedCsr); } +static void CheckFmodEdgeFlags() { + const double nan = std::numeric_limits::quiet_NaN(); + const double infinity = std::numeric_limits::infinity(); + constexpr double one = 1.0; + const std::pair cases[] = {{0.0, nan}, {one, -nan}, {nan, one}, {-nan, 0.0}, {one, 0.0}, {infinity, one}, {-infinity, -2.0}}; + for (const auto& [x, y] : cases) { + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + Require(std::isnan(fmod_nid_postfix(x, y)) && errno == EDOM && std::fetestexcept(FE_INVALID) != 0); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + Require(std::isnan(fmodf_nid_postfix(static_cast(x), static_cast(y))) && errno == EDOM && std::fetestexcept(FE_INVALID) != 0); + } +} + static void CheckFmodConsoleBits() { constexpr struct { std::uint64_t x; @@ -2667,6 +2682,7 @@ int main() { CheckNextafterEnvironment(nextafter_nid_postfix, std::uint64_t{0x8000000000000000ull}, std::uint64_t{0x3ff0000000000000ull}, std::uint64_t{0x0010000000000000ull}, std::uint64_t{0x7fefffffffffffffull}, std::uint64_t{0x7ff0000000000000ull}); CheckExpm1ScalingRounding(); CheckFmodConsoleBits(); + CheckFmodEdgeFlags(); CheckAsinl(); Require(scalbn_nid_postfix(0.75, -2) == 0.1875); Require(scalbnf_nid_postfix(0.75f, 4) == 12.f); From c92934b0220d8312a2b8d6cec02415a338cbecc7 Mon Sep 17 00:00:00 2001 From: KernelPanicNT Date: Fri, 30 Oct 2026 12:26:56 +0000 Subject: [PATCH 110/612] docs(libSceCesCs): separate measured and unmeasured Jiscs gaps Split the sceCesJiscsToUcs and sceCesUcsToJiscs debt entry in two. The first lists the forms a payload process on a device answered that still throw here, with what the device returned: native Shift-JIS and EUC-JP codes, 0x02002422, the null-profile results and plane 2 with a JIS X 0208 profile. The second lists what was not measured: planes 1 and 3-16, the encoder's 0x03PPRRCC and 0x0400RRCC codes as input, and other or modified profiles. --- docs/dev/debt/libSceCesCs.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceCesCs.md b/docs/dev/debt/libSceCesCs.md index f2f945124a..8e357711b8 100644 --- a/docs/dev/debt/libSceCesCs.md +++ b/docs/dev/debt/libSceCesCs.md @@ -11,7 +11,8 @@ - [Unicode scalar conversions](../../../core/libs/prx/libSceCesCs/Unicode.cpp) (libSceCesCs) - console error precedence, optional length pointers and length units are unverified. Signatures follow the [Orbital type database](https://github.com/AlexAltea/orbital/blob/b321fcdbd700cc02da1cb1ad25412781d5ec73c6/tools/ida/db_types.json); names are in the aerolib table pinned by `tools/nid_names.py`. Encoding follows [Unicode section 3.9](https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-3/). Each call converts one scalar, including zero and U+FEFF, without a terminator or byte-order detection; lengths count bytes for UTF-8 and 16-bit units for UTF-16. Native forms use host byte order (supported hosts are little-endian). Errors follow the existing CES converters, not console measurements, and failure leaves outputs unchanged. No console or title comparison has been run - [sceCesEucJpToUtf8 and sceCesEucJpToUcs2](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) with the CP51932 profile follow a payload process on a PS5 (firmware 13.60): every two-byte input and every `0x8F` three-byte input gives the console's result, length and output (the test pins a hash of both sweeps). Bytes below 0xA0 other than 0x0E, 0x8E and 0x8F convert to themselves; JIS X 0212 is not decoded, so `0x8F` forms return `0x805C0020` in rows X 0212 leaves empty and `0x805C0014` elsewhere; a null profile returns `0x805C0004`, and the lengths are cleared first and then set as the console sets them. Their signatures follow the Orbital type database linked above. The console reads any other profile pointer as a sheet, where these return the invalid-parameter error. - [Shift-JIS and EUC-JP scalar conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - errors, their precedence and the optional length pointers of `sceCesSJisTo*`, `sceCesEucJpTo{Utf16,Utf32}*` and `sceCes{Ucs2,Utf8,Utf16,Utf32}*To{SJis,EucJp}` are unverified against the console. Signatures follow the Orbital type database linked above; the CP932 and CP51932 table and profile assumptions above apply. A profile of another encoding answers `INVALID_PARAMETER`, a surrogate or a value above U+10FFFF `INVALID_ENCODE`, a code point outside the BMP `UNASSIGNED_CODE`. UTF-8 input is decoded as `sceCesUtf8ToSbc` decodes it, so overlong forms answer `0x805C0015`. `sceCesSJisCodeToJisX0208` and `sceCesJisX0208ToSJisCode` do not check that the cell is assigned; their `INVALID_ENCODE` for rows or cells outside 1-94 and `INVALID_DST_BUFFER` for a null output are not console measurements -- [sceCesJiscsToUcs and sceCesUcsToJiscs](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - throw where nothing was measured: JIS codes other than a byte or a row and cell `0x21`-`0x7E` below `0x30000`, among them native Shift-JIS and EUC-JP codes and the `0x0200RRCC`, `0x03PPRRCC` and `0x0400RRCC` forms with rows and cells from 1 that the encoder returns (`0x02002422` decoded as row 36, cell 34), so encoded output does not convert back; code points outside the BMP and plane 2; plane 2 with a JIS X 0208 profile; profiles other than the eight JIS ones. A null profile returned `0x805C0004` with those codes except `0x8FA2AF`, which faulted. Unverified: a conversion error answering before `0x805C0030` for a null output (seen only on a zero-filled sheet), `0x805C0015` for every value above U+10FFFF (two samples), and profiles changed by `sce*UcsProfileSetSbcs`, which are not distinguished. Signatures follow Orbital +- [sceCesJiscsToUcs and sceCesUcsToJiscs, measured forms](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - these throw here, although a payload process on a device measured them. JIS codes other than a byte or a row and cell `0x21`-`0x7E` below `0x30000`: native Shift-JIS codes decode with the Shift-JIS profiles (`0x82A0` is U+3042) and return `0x805C0015` with the EUC-JP ones; `0xA4A2` is U+3042 with EUC-JP and U+FF64 with Shift-JIS; `0x8FA2AF` is U+02D8 with the two SS3 profiles; `0x8E21` returns `0x805C0014`; `0x02002422` is U+8CC3 with every profile (row 36, cell 34, counted from 1 as in the encoder's output); most other samples, such as `0x10000`, `0x30000` and `0x1002422`, return `0x805C0015`. With a null profile all of these return `0x805C0004`, except `0x8FA2AF`, which faults. U+2000B and U+20B9F with a JIS X 0208 profile return `0x805C0020`. A zero-filled sheet passed as the profile gives mixed results (`0x80` converts) +- [sceCesJiscsToUcs and sceCesUcsToJiscs, unmeasured forms](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - not measured, so these throw: code points of planes 1 and 3-16; the `0x03PPRRCC` and `0x0400RRCC` codes the encoder returns, given back to `sceCesJiscsToUcs`, so encoded multibyte output does not convert back; profiles other than the eight JIS ones and the zero-filled sheet. Unverified: a conversion error answering before `0x805C0030` for a null output (seen only on a zero-filled sheet), `0x805C0015` for every value above U+10FFFF (two samples), and profiles changed by `sce*UcsProfileSetSbcs`, which are not distinguished. Signatures follow Orbital - [Strict JIS X 0208 profiles](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - the tables were measured on a PS5 (`sceCesMbcToUtf32` and `sceCesUtf32ToMbc` over every one- and two-byte sequence and every scalar); the errors follow the other JIS profiles, a missing SS2 or SS3 answering `INVALID_ENCODE`. The table is CP932's without rows 13 and 89-92 and with seven cells changed (`0x213D` U+2014 and the six of [JIS0208.TXT](https://unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/JIS0208.TXT)). Cell `0x2140` is U+005C in `sceCesUcsProfileInitSJis1997X0208`, whose bytes `0x5C` and `0x7E` are U+00A5 and U+203E, and U+FF3C in the EUC-JP profiles, which keep ASCII and also encode U+00A5 and U+203E as `0xA1EF` and `0xA1B1`. Per [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Text/Ces.cs#L57-L67), `X0208Ss2` adds SS2 katakana and `X0208Ss2Ss3` SS3 JIS X 0212 - [UTF-16 and UTF-32 forms of the single-byte and MBCS string conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - errors, their precedence and the optional length pointers of `sceCesSbcTo{Ucs2,Utf16,Utf32}*`, `sceCes{Ucs2,Utf16,Utf32}*ToSbc`, `sceCesMbcsStr{To,Get}{Utf16,Utf32}*` and `sceCes{Utf16,Utf32}Str{To,Get}Mbcs*` are unverified against the console. Signatures follow the Orbital type database linked above. They follow `sceCesSbcToUtf8`, `sceCesUtf8ToSbc` and the UTF-8 MBCS string forms with the profiles and tables listed above. A surrogate or a value above U+10FFFF answers `INVALID_ENCODE`, a valid code point no profile maps `UNASSIGNED_CODE`. The string forms stop at a zero code unit, write no terminator and count code units - [GBK (CP936) and EUC-CN conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - `sceCesUcsProfileInitGbkCp936` and `sceCesUcsProfileInitEucCnGb2312` mark the caller's sheet with an 8-byte tag; the console's profile layout is not known. Both use one table ([Cp936.hpp](../../../core/libs/prx/libSceCesCs/Cp936.hpp)) read from Microsoft's [CP936.TXT](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP936.TXT); the EUC-CN profile accepts only its `0xA1`-`0xFE` lead and trail bytes, so it includes the GBK cells inside that range and the CP936 forms of `0xA1A4` (U+00B7) and `0xA1AA` (U+2014) instead of GB 2312's. That the console's GB 2312 and CP936 tables equal it is not measured. User-defined areas and `sceCesGbUcsProfileSet*` are not handled. Errors, their precedence and the length pointers follow the Shift-JIS forms above and are unverified From aebab1eb2aca8c578521de0e6f22b849e34406c1 Mon Sep 17 00:00:00 2001 From: KernelPanicNT Date: Fri, 30 Oct 2026 12:57:47 +0000 Subject: [PATCH 111/612] docs(libSceCesCs): keep the measured sheet out of the unmeasured gaps and update the pending count --- docs/dev/debt/libSceCesCs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/dev/debt/libSceCesCs.md b/docs/dev/debt/libSceCesCs.md index 8e357711b8..2c944823fd 100644 --- a/docs/dev/debt/libSceCesCs.md +++ b/docs/dev/debt/libSceCesCs.md @@ -12,7 +12,7 @@ - [sceCesEucJpToUtf8 and sceCesEucJpToUcs2](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) with the CP51932 profile follow a payload process on a PS5 (firmware 13.60): every two-byte input and every `0x8F` three-byte input gives the console's result, length and output (the test pins a hash of both sweeps). Bytes below 0xA0 other than 0x0E, 0x8E and 0x8F convert to themselves; JIS X 0212 is not decoded, so `0x8F` forms return `0x805C0020` in rows X 0212 leaves empty and `0x805C0014` elsewhere; a null profile returns `0x805C0004`, and the lengths are cleared first and then set as the console sets them. Their signatures follow the Orbital type database linked above. The console reads any other profile pointer as a sheet, where these return the invalid-parameter error. - [Shift-JIS and EUC-JP scalar conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - errors, their precedence and the optional length pointers of `sceCesSJisTo*`, `sceCesEucJpTo{Utf16,Utf32}*` and `sceCes{Ucs2,Utf8,Utf16,Utf32}*To{SJis,EucJp}` are unverified against the console. Signatures follow the Orbital type database linked above; the CP932 and CP51932 table and profile assumptions above apply. A profile of another encoding answers `INVALID_PARAMETER`, a surrogate or a value above U+10FFFF `INVALID_ENCODE`, a code point outside the BMP `UNASSIGNED_CODE`. UTF-8 input is decoded as `sceCesUtf8ToSbc` decodes it, so overlong forms answer `0x805C0015`. `sceCesSJisCodeToJisX0208` and `sceCesJisX0208ToSJisCode` do not check that the cell is assigned; their `INVALID_ENCODE` for rows or cells outside 1-94 and `INVALID_DST_BUFFER` for a null output are not console measurements - [sceCesJiscsToUcs and sceCesUcsToJiscs, measured forms](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - these throw here, although a payload process on a device measured them. JIS codes other than a byte or a row and cell `0x21`-`0x7E` below `0x30000`: native Shift-JIS codes decode with the Shift-JIS profiles (`0x82A0` is U+3042) and return `0x805C0015` with the EUC-JP ones; `0xA4A2` is U+3042 with EUC-JP and U+FF64 with Shift-JIS; `0x8FA2AF` is U+02D8 with the two SS3 profiles; `0x8E21` returns `0x805C0014`; `0x02002422` is U+8CC3 with every profile (row 36, cell 34, counted from 1 as in the encoder's output); most other samples, such as `0x10000`, `0x30000` and `0x1002422`, return `0x805C0015`. With a null profile all of these return `0x805C0004`, except `0x8FA2AF`, which faults. U+2000B and U+20B9F with a JIS X 0208 profile return `0x805C0020`. A zero-filled sheet passed as the profile gives mixed results (`0x80` converts) -- [sceCesJiscsToUcs and sceCesUcsToJiscs, unmeasured forms](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - not measured, so these throw: code points of planes 1 and 3-16; the `0x03PPRRCC` and `0x0400RRCC` codes the encoder returns, given back to `sceCesJiscsToUcs`, so encoded multibyte output does not convert back; profiles other than the eight JIS ones and the zero-filled sheet. Unverified: a conversion error answering before `0x805C0030` for a null output (seen only on a zero-filled sheet), `0x805C0015` for every value above U+10FFFF (two samples), and profiles changed by `sce*UcsProfileSetSbcs`, which are not distinguished. Signatures follow Orbital +- [sceCesJiscsToUcs and sceCesUcsToJiscs, unmeasured forms](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - not measured, so these throw: code points of planes 1 and 3-16; the `0x03PPRRCC` and `0x0400RRCC` codes the encoder returns, given back to `sceCesJiscsToUcs`, so encoded multibyte output does not convert back; profiles other than the eight JIS ones and the zero-filled sheet above. Unverified: a conversion error answering before `0x805C0030` for a null output (seen only on a zero-filled sheet), `0x805C0015` for every value above U+10FFFF (two samples), and profiles changed by `sce*UcsProfileSetSbcs`, which are not distinguished. Signatures follow Orbital - [Strict JIS X 0208 profiles](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - the tables were measured on a PS5 (`sceCesMbcToUtf32` and `sceCesUtf32ToMbc` over every one- and two-byte sequence and every scalar); the errors follow the other JIS profiles, a missing SS2 or SS3 answering `INVALID_ENCODE`. The table is CP932's without rows 13 and 89-92 and with seven cells changed (`0x213D` U+2014 and the six of [JIS0208.TXT](https://unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/JIS0208.TXT)). Cell `0x2140` is U+005C in `sceCesUcsProfileInitSJis1997X0208`, whose bytes `0x5C` and `0x7E` are U+00A5 and U+203E, and U+FF3C in the EUC-JP profiles, which keep ASCII and also encode U+00A5 and U+203E as `0xA1EF` and `0xA1B1`. Per [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Text/Ces.cs#L57-L67), `X0208Ss2` adds SS2 katakana and `X0208Ss2Ss3` SS3 JIS X 0212 - [UTF-16 and UTF-32 forms of the single-byte and MBCS string conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - errors, their precedence and the optional length pointers of `sceCesSbcTo{Ucs2,Utf16,Utf32}*`, `sceCes{Ucs2,Utf16,Utf32}*ToSbc`, `sceCesMbcsStr{To,Get}{Utf16,Utf32}*` and `sceCes{Utf16,Utf32}Str{To,Get}Mbcs*` are unverified against the console. Signatures follow the Orbital type database linked above. They follow `sceCesSbcToUtf8`, `sceCesUtf8ToSbc` and the UTF-8 MBCS string forms with the profiles and tables listed above. A surrogate or a value above U+10FFFF answers `INVALID_ENCODE`, a valid code point no profile maps `UNASSIGNED_CODE`. The string forms stop at a zero code unit, write no terminator and count code units - [GBK (CP936) and EUC-CN conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - `sceCesUcsProfileInitGbkCp936` and `sceCesUcsProfileInitEucCnGb2312` mark the caller's sheet with an 8-byte tag; the console's profile layout is not known. Both use one table ([Cp936.hpp](../../../core/libs/prx/libSceCesCs/Cp936.hpp)) read from Microsoft's [CP936.TXT](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP936.TXT); the EUC-CN profile accepts only its `0xA1`-`0xFE` lead and trail bytes, so it includes the GBK cells inside that range and the CP936 forms of `0xA1A4` (U+00B7) and `0xA1AA` (U+2014) instead of GB 2312's. That the console's GB 2312 and CP936 tables equal it is not measured. User-defined areas and `sceCesGbUcsProfileSet*` are not handled. Errors, their precedence and the length pointers follow the Shift-JIS forms above and are unverified @@ -26,6 +26,6 @@ - [ISO-2022-JP profiles and string conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - the signatures follow the MBCS string family by analogy (none in the type database). The UTF-16 and UCS-2 forms share the converters and were not measured separately. `sceCesIso2022UcsContextInit` with a null profile zeroes 256 context bytes and answers `INVALID_PARAMETER`, from a PC run of the firmware 13.42 module, not a console. `sceCesIso2022JpUcsProfileSetSbcs` still throws. Measured on a PS5 through the UTF-32 and UTF-8 forms (every BMP scalar, every designation then every byte and 94x94 pair, 46 strings per profile): RFC 1468 starts in JIS X 0201 Roman (`ESC ( B` and `ESC ( J` both select it) and uses the strict JIS X 0208 table; the code pages start in ASCII and use CP932's table plus the JIS forms. No profile has JIS X 0212. A trailing designation is not counted; one at the buffer end answers `SRC_BUFFER_END`. The encoder returns to the initial set only if it left it. - [Single-byte profiles](../../../core/libs/prx/libSceCesCs/SingleByte.hpp) (libSceCesCs) - the console's tables for these 27 profiles are not verified. Each returns a profile accepted by `sceCesSbcToUtf8`, `sceCesUtf8ToSbc` and the MBCS context conversions, with bytes 0x00-0x7F unchanged and the high half of a public table: ISO 8859-1, -2, -4, -5, -9, -10, -13, -14, -15 and -16 from [unicode.org](https://unicode.org/Public/MAPPINGS/ISO8859/), code pages 437, 737, 775, 850, 852, 855, 860-863, 865 and 866 from [Microsoft's PC tables](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/), KOI8-R and KOI8-U from [unicode.org](https://unicode.org/Public/MAPPINGS/VENDORS/MISC/), and 1254, 1256 and 1258 from Microsoft's [best-fit tables](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WindowsBestFit/), whose unassigned bytes map to C1 controls as in Cp1250. Profiles whose public table leaves bytes undefined or maps them to private use still throw - [sceCesRefersUcsProfileCp858](../../../core/libs/prx/libSceCesCs/SingleByte.hpp) (libSceCesCs) - the console's table is not verified. The profile is the Cp850 one with byte 0xD5 mapped to U+20AC instead of U+0131, which is the whole high half of ICU's [ibm-858_P100-1997.ucm](https://github.com/unicode-org/icu-data/blob/main/charset/data/ucm/ibm-858_P100-1997.ucm); bytes 0x00-0x7F stay unchanged as in the other single-byte profiles, not swapped as in IBM's control mapping -- [libSceCesCs firmware 3.20 pending exports](../../../core/libs/prx/libSceCesCs/PendingExports.cpp) - 26 symbol names are present in [the PS5 3.20 libSceCesCs export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCesCs.c). Their signatures and console behaviour are unverified. The declarations use the existing no-parameter unknown-signature convention solely to resolve imports, and every call throws through `NotImplemented_nid_no_patch`. +- [libSceCesCs firmware 3.20 pending exports](../../../core/libs/prx/libSceCesCs/PendingExports.cpp) - 24 symbol names are present in [the PS5 3.20 libSceCesCs export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCesCs.c). Their signatures and console behaviour are unverified. The declarations use the existing no-parameter unknown-signature convention solely to resolve imports, and every call throws through `NotImplemented_nid_no_patch`. - [EUC-KR, UHC and Big5 conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - the errors are assumed; the decode tables and the Big5 duplicate choices were measured on a PS5 (`sceCesMbcToUtf32` and each `ToUtf8` over every one- and two-byte sequence): UHC and Big5 match, EUC-KR apart from its single bytes. The tables are Microsoft's [CP949.TXT](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP949.TXT), plus `0xA2E8` U+327E from KS X 1001:2002, and [CP950.TXT](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP950.TXT); EUC-KR is CP949 limited to bytes `0xA1`-`0xFE`, below `0x80` ASCII; the KS X 1003 profile is not implemented. User-defined areas answer `UNASSIGNED_CODE`. Big5 encoding takes the lower of two cells except for U+5341 and U+5345, as a PS5's `sceCesUtf32ToMbc` does for every scalar (CP950 takes the higher cell of U+2550, U+255E, U+2561 and U+256A; the console does not). The profiles tag the caller's sheet like the EUC-JP profile - [ISO 646 variants and JIS X 0201 profiles](../../../core/libs/prx/libSceCesCs/SingleByte.hpp) (libSceCesCs) - the tables and errors were measured on a PS5 with `sceCesSbcToUtf32` over every byte and `sceCesUtf32ToSbc` over every BMP scalar; the other single-byte forms share them and were not measured separately. The 7-bit sets (ASCII, BS 4730, GB 1988-80, JIS X 0201 Roman and Katakana, KS X 1003 and their `Tilde0x7e` variants) answer `INVALID_ENCODE` for bytes `0x80`-`0xFF`. BS 4730 changes only `0x23` (£), GB 1988-80 `0x24` (¥), JIS X 0201 Roman `0x5C` (¥) and KS X 1003 `0x5C` (₩); the base GB, JIS and KS profiles also map `0x7E` to overline and the `Tilde0x7e` variants keep a tilde. The katakana-only profile maps `0x21`-`0x5F` to U+FF61-U+FF9F and leaves `0x60`-`0x7E` unassigned; the 8-bit katakana profiles use `0xA1`-`0xDF`. From e470b84e4b2af5cb44667d185d2d02fa762b16c5 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:40:47 +0200 Subject: [PATCH 112/612] feat(libSceLibreSsl): implement X509_NAME_get_entry --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 ++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 44 +++++++++++ core/libs/tests/GuestLibreSslX509Name.cpp | 79 +++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 1 + 5 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/X509Name.cpp create mode 100644 core/libs/tests/GuestLibreSslX509Name.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 442fdbe021..ba6bd97da3 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509Name.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -148,6 +148,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_pkey_type PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_x509_name_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslX509Name.cpp) + target_include_directories(guest_libressl_x509_name_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_x509_name_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_x509_name_tests) + add_test(NAME guest_libressl_x509_name COMMAND guest_libressl_x509_name_tests) + set_tests_properties(guest_libressl_x509_name PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_x509_name PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index d3c66c41de..c41384330d 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1404,11 +1404,6 @@ int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void) { return 0; } -int APS5_VABI X509_NAME_get_entry_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp new file mode 100644 index 0000000000..85fb8215a6 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct LibreSslX509NameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct LibreSslX509Name { + LibreSslStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + +static_assert(sizeof(LibreSslStack) == 32); +static_assert(offsetof(LibreSslStack, data) == 8); +static_assert(sizeof(LibreSslX509NameEntry) == 24); +static_assert(offsetof(LibreSslX509NameEntry, value) == 8); +static_assert(sizeof(LibreSslX509Name) == 40); +static_assert(offsetof(LibreSslX509Name, entries) == 0); + +extern "C" { + +LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc) { + if (!name || loc < 0 || !name->entries || name->entries->num <= loc) return nullptr; + if (!name->entries->data) throw std::invalid_argument(std::string(__func__) + ": null entry array"); + return reinterpret_cast(name->entries->data[loc]); +} + +} diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp new file mode 100644 index 0000000000..ac574ce133 --- /dev/null +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -0,0 +1,79 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct TestStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct TestNameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct TestName { + TestStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + +static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); +static_assert(sizeof(TestNameEntry) == 24 && offsetof(TestNameEntry, value) == 8); +static_assert(sizeof(TestName) == 40); + +extern "C" { +TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL X509_NAME check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetEntry() { + TestNameEntry first {}; + TestNameEntry second {}; + char* slots[] {reinterpret_cast(&first), reinterpret_cast(&second), nullptr}; + TestStack entries {2, slots, 0, 3, nullptr}; + TestName name {}; + Require(X509_NAME_get_entry_nid_postfix(nullptr, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + name.entries = &entries; + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == &first); + Require(X509_NAME_get_entry_nid_postfix(&name, 1) == &second); + Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, INT_MIN) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, INT_MAX) == nullptr); + entries.num = 0; + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + entries.num = 3; + Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + entries.data = nullptr; + RequireThrows([&] { X509_NAME_get_entry_nid_postfix(&name, 0); }); + Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); +} + +int main() { + TestGetEntry(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 447430bb92..f4eaf4ff78 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -26,6 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, OBJ_cleanup, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `OBJ_cleanup` frees no added object, because `OBJ_create` throws and none can exist ([obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c#L265-L280)); it must free them once objects can be added. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console +- [X509_NAME_get_entry](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names the caller builds reach it. A positive entry count with a null entry array, which LibreSSL dereferences, throws ### Functional From 30f7dff34e01edc2a78058fad13ff4dc98f89da4 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:42:33 +0200 Subject: [PATCH 113/612] feat(libSceLibreSsl): implement X509_NAME_ENTRY_get_data --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 5 +++++ core/libs/tests/GuestLibreSslX509Name.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index c41384330d..786c043ea4 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1399,11 +1399,6 @@ int APS5_VABI X509_INFO_free_nid_postfix(void) { return 0; } -int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp index 85fb8215a6..57efc6993b 100644 --- a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -41,4 +41,9 @@ LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX return reinterpret_cast(name->entries->data[loc]); } +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry* entry) { + if (!entry) return nullptr; + return entry->value; +} + } diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp index ac574ce133..1dea64955e 100644 --- a/core/libs/tests/GuestLibreSslX509Name.cpp +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -35,6 +35,7 @@ static_assert(sizeof(TestName) == 40); extern "C" { TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const TestNameEntry*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -73,7 +74,20 @@ static void TestGetEntry() { Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); } +static void TestEntryGetData() { + int value = 0; + TestNameEntry entry {}; + Require(X509_NAME_ENTRY_get_data_nid_postfix(nullptr) == nullptr); + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == nullptr); + entry.value = &value; + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + entry.object = &entry; + entry.set = 7; + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); +} + int main() { TestGetEntry(); + TestEntryGetData(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index f4eaf4ff78..2443264a08 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 293 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 291 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, OBJ_cleanup, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `OBJ_cleanup` frees no added object, because `OBJ_create` throws and none can exist ([obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c#L265-L280)); it must free them once objects can be added. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [X509_NAME_get_entry](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names the caller builds reach it. A positive entry count with a null entry array, which LibreSSL dereferences, throws +- [X509_NAME_get_entry, X509_NAME_ENTRY_get_data](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names and entries the caller builds reach them. A positive entry count with a null entry array, which LibreSSL dereferences, throws ### Functional From db91bf3742ca1524465823dad8115018940087d5 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 06:00:22 +0100 Subject: [PATCH 114/612] refactor(libSceLibreSsl): declare the X509_NAME exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 19 ++++++++- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 26 +----------- core/libs/tests/GuestLibreSslX509Name.cpp | 40 ++++++++++--------- 3 files changed, 40 insertions(+), 45 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 3419a18af1..d638390f68 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -245,6 +245,21 @@ struct LibreSslX509StoreContext { void* exData; }; +struct LibreSslX509NameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct LibreSslX509Name { + LibreSslStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -558,8 +573,8 @@ int APS5_VABI X509_EXTENSION_get_data_nid_postfix(void); int APS5_VABI X509_EXTENSION_get_object_nid_postfix(void); int APS5_VABI X509_INFO_free_nid_postfix(void); LibreSslX509LookupMethod* APS5_VABI X509_LOOKUP_file_nid_postfix(void); -int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void); -int APS5_VABI X509_NAME_get_entry_nid_postfix(void); +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry* entry); +LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc); int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void); int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(void); int APS5_VABI X509_NAME_oneline_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp index 57efc6993b..c220b821a0 100644 --- a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -1,33 +1,9 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslStack { - int num; - char** data; - int sorted; - int numAlloc; - void* comp; -}; - -struct LibreSslX509NameEntry { - void* object; - void* value; - int set; - int size; -}; - -struct LibreSslX509Name { - LibreSslStack* entries; - int modified; - void* bytes; - unsigned char* canonEnc; - int canonEnclen; -}; - -static_assert(sizeof(LibreSslStack) == 32); -static_assert(offsetof(LibreSslStack, data) == 8); static_assert(sizeof(LibreSslX509NameEntry) == 24); static_assert(offsetof(LibreSslX509NameEntry, value) == 8); static_assert(sizeof(LibreSslX509Name) == 40); diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp index 1dea64955e..365b16002e 100644 --- a/core/libs/tests/GuestLibreSslX509Name.cpp +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -33,9 +34,12 @@ static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); static_assert(sizeof(TestNameEntry) == 24 && offsetof(TestNameEntry, value) == 8); static_assert(sizeof(TestName) == 40); -extern "C" { -TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); -void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const TestNameEntry*); +static const LibreSslX509Name* Name(const TestName* name) { + return reinterpret_cast(name); +} + +static const LibreSslX509NameEntry* Entry(const TestNameEntry* entry) { + return reinterpret_cast(entry); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -56,34 +60,34 @@ static void TestGetEntry() { TestStack entries {2, slots, 0, 3, nullptr}; TestName name {}; Require(X509_NAME_get_entry_nid_postfix(nullptr, 0) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), -1) == nullptr); name.entries = &entries; - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == &first); - Require(X509_NAME_get_entry_nid_postfix(&name, 1) == &second); - Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, INT_MIN) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, INT_MAX) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == Entry(&first)); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 1) == Entry(&second)); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), INT_MIN) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), INT_MAX) == nullptr); entries.num = 0; - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == nullptr); entries.num = 3; - Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 2) == nullptr); entries.data = nullptr; - RequireThrows([&] { X509_NAME_get_entry_nid_postfix(&name, 0); }); - Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); + RequireThrows([&] { X509_NAME_get_entry_nid_postfix(Name(&name), 0); }); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 3) == nullptr); } static void TestEntryGetData() { int value = 0; TestNameEntry entry {}; Require(X509_NAME_ENTRY_get_data_nid_postfix(nullptr) == nullptr); - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == nullptr); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == nullptr); entry.value = &value; - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == &value); entry.object = &entry; entry.set = 7; - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == &value); } int main() { From f02b21d9b71066309a1a1144dd9f5239df23cd10 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:53:15 +0200 Subject: [PATCH 115/612] feat(libSceLibreSsl): implement SSL_get_SSL_CTX --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 +++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 78 +++++++++++++++++++++ core/libs/tests/GuestLibreSslSsl.cpp | 65 +++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 1 + 5 files changed, 156 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/Ssl.cpp create mode 100644 core/libs/tests/GuestLibreSslSsl.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 442fdbe021..488d7f985c 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ssl.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -148,6 +148,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_pkey_type PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_ssl_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSsl.cpp) + target_include_directories(guest_libressl_ssl_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_tests) + add_test(NAME guest_libressl_ssl COMMAND guest_libressl_ssl_tests) + set_tests_properties(guest_libressl_ssl PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index d3c66c41de..8c898349f2 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1168,11 +1168,6 @@ int APS5_VABI SSL_get1_session_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_SSL_CTX_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_get_certificate_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp new file mode 100644 index 0000000000..c817055b09 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct LibreSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + int server; + void* s3; + void* d1; + void* param; + void* cipherList; + void* cert; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* session; + int verifyMode; + int error; + int errorCode; + void* ctx; + std::int64_t verifyResult; + int references; + int clientVersion; + unsigned int maxSendFragment; + char* tlsextHostname; + int tlsextStatusType; + void* initialCtx; + void* encReadCtx; + void* readHash; + LibreSslSslInternal* internal; +}; + +static_assert(sizeof(LibreSslSslInternal) == 432); +static_assert(offsetof(LibreSslSslInternal, rwstate) == 156); +static_assert(offsetof(LibreSslSslInternal, shutdown) == 168); +static_assert(sizeof(LibreSsl) == 232); +static_assert(offsetof(LibreSsl, rbio) == 16); +static_assert(offsetof(LibreSsl, wbio) == 24); +static_assert(offsetof(LibreSsl, session) == 128); +static_assert(offsetof(LibreSsl, ctx) == 152); +static_assert(offsetof(LibreSsl, verifyResult) == 160); +static_assert(offsetof(LibreSsl, internal) == 224); + +namespace { + +const LibreSsl& Connection(const LibreSsl* ssl, const char* function) { + if (!ssl) throw std::invalid_argument(std::string(function) + ": null connection"); + return *ssl; +} + +const LibreSslSslInternal& Internal(const LibreSsl* ssl, const char* function) { + const LibreSsl& connection = Connection(ssl, function); + if (!connection.internal) throw std::invalid_argument(std::string(function) + ": null internal state"); + return *connection.internal; +} + +} + +extern "C" { + +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).ctx; +} + +} diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp new file mode 100644 index 0000000000..db130edf56 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -0,0 +1,65 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct TestSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct TestSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + unsigned char beforeSession[88]; + void* session; + unsigned char beforeCtx[16]; + void* ctx; + std::int64_t verifyResult; + unsigned char beforeInternal[56]; + TestSslInternal* internal; +}; + +static_assert(sizeof(TestSslInternal) == 432 && offsetof(TestSslInternal, rwstate) == 156 && offsetof(TestSslInternal, shutdown) == 168); +static_assert(sizeof(TestSsl) == 232 && offsetof(TestSsl, session) == 128 && offsetof(TestSsl, ctx) == 152); +static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, internal) == 224); + +extern "C" { +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetSslCtx() { + int context = 0; + int session = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_SSL_CTX_nid_postfix(nullptr); }); + Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == nullptr); + ssl.ctx = &context; + ssl.session = &session; + Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); +} + +int main() { + TestGetSslCtx(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 447430bb92..4b2f5999cf 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -26,6 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, OBJ_cleanup, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `OBJ_cleanup` frees no added object, because `OBJ_create` throws and none can exist ([obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c#L265-L280)); it must free them once objects can be added. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console +- [SSL_get_SSL_CTX](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach it. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 1cf523780d7e6c38b8ab522bd093ce77f2831bf5 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:17 +0200 Subject: [PATCH 116/612] feat(libSceLibreSsl): implement SSL_get_rbio --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 13 +++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 8c898349f2..04d0f6e2fc 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1208,11 +1208,6 @@ int APS5_VABI SSL_get_privatekey_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_rbio_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_get_servername_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index c817055b09..3069c3cc5d 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -75,4 +75,8 @@ void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).ctx; } +void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).rbio; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index db130edf56..95fa53ff94 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -35,6 +35,7 @@ static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, intern extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); +void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -59,7 +60,19 @@ static void TestGetSslCtx() { Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); } +static void TestGetRbio() { + int read = 0; + int write = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_rbio_nid_postfix(nullptr); }); + Require(SSL_get_rbio_nid_postfix(&ssl) == nullptr); + ssl.rbio = &read; + ssl.wbio = &write; + Require(SSL_get_rbio_nid_postfix(&ssl) == &read); +} + int main() { TestGetSslCtx(); + TestGetRbio(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 4b2f5999cf..53fef222d8 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 293 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 291 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, OBJ_cleanup, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `OBJ_cleanup` frees no added object, because `OBJ_create` throws and none can exist ([obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c#L265-L280)); it must free them once objects can be added. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_get_SSL_CTX](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach it. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From a61c155a52c46bb2325a224e15bd643cfc1c0ab1 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:27 +0200 Subject: [PATCH 117/612] feat(libSceLibreSsl): implement SSL_get_wbio --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 15 +++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 04d0f6e2fc..4b1b5911b7 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1228,11 +1228,6 @@ int APS5_VABI SSL_get_verify_result_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_wbio_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 3069c3cc5d..2c544b2aa2 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -79,4 +79,8 @@ void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).rbio; } +void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).wbio; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 95fa53ff94..6547b2aac1 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -36,6 +36,7 @@ static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, intern extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); +void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -71,8 +72,22 @@ static void TestGetRbio() { Require(SSL_get_rbio_nid_postfix(&ssl) == &read); } +static void TestGetWbio() { + int read = 0; + int write = 0; + int buffer = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_wbio_nid_postfix(nullptr); }); + Require(SSL_get_wbio_nid_postfix(&ssl) == nullptr); + ssl.rbio = &read; + ssl.wbio = &write; + ssl.bbio = &buffer; + Require(SSL_get_wbio_nid_postfix(&ssl) == &write); +} + int main() { TestGetSslCtx(); TestGetRbio(); + TestGetWbio(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 53fef222d8..8283ead290 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 291 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 290 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, OBJ_cleanup, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `OBJ_cleanup` frees no added object, because `OBJ_create` throws and none can exist ([obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c#L265-L280)); it must free them once objects can be added. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_get_SSL_CTX, SSL_get_rbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 15d0de0ade8f37f6bac308f5c5918b61c9f7c75e Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:37 +0200 Subject: [PATCH 118/612] feat(libSceLibreSsl): implement SSL_version --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 4b1b5911b7..b398881dc1 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1305,11 +1305,6 @@ int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void) { return 0; } -int APS5_VABI SSL_version_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_want_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 2c544b2aa2..a980c12199 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -83,4 +83,8 @@ void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).wbio; } +int APS5_VABI SSL_version_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).version; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 6547b2aac1..10945d54ba 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -37,6 +37,7 @@ extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); +int APS5_VABI SSL_version_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -85,9 +86,20 @@ static void TestGetWbio() { Require(SSL_get_wbio_nid_postfix(&ssl) == &write); } +static void TestVersion() { + TestSsl ssl {}; + RequireThrows([] { SSL_version_nid_postfix(nullptr); }); + Require(SSL_version_nid_postfix(&ssl) == 0); + ssl.version = 0x0303; + Require(SSL_version_nid_postfix(&ssl) == 0x0303); + ssl.version = 0xFEFF; + Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); + TestVersion(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 8283ead290..b63e9fba9a 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 290 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 289 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, OBJ_cleanup, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `OBJ_cleanup` frees no added object, because `OBJ_create` throws and none can exist ([obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c#L265-L280)); it must free them once objects can be added. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From d2580b8324e4491cab3a52994618da808b49a3c9 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:47 +0200 Subject: [PATCH 119/612] feat(libSceLibreSsl): implement SSL_get_verify_result --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index b398881dc1..0224b76452 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1223,11 +1223,6 @@ int APS5_VABI SSL_get_shutdown_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_verify_result_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index a980c12199..89c984e2d6 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -87,4 +87,8 @@ int APS5_VABI SSL_version_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).version; } +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).verifyResult; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 10945d54ba..2d1a43528c 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -38,6 +38,7 @@ void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -96,10 +97,23 @@ static void TestVersion() { Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); } +static void TestVerifyResult() { + TestSsl ssl {}; + RequireThrows([] { SSL_get_verify_result_nid_postfix(nullptr); }); + Require(SSL_get_verify_result_nid_postfix(&ssl) == 0); + ssl.verifyResult = 20; + Require(SSL_get_verify_result_nid_postfix(&ssl) == 20); + ssl.verifyResult = INT64_C(0x100000002); + Require(SSL_get_verify_result_nid_postfix(&ssl) == INT64_C(0x100000002)); + ssl.verifyResult = -1; + Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); TestVersion(); + TestVerifyResult(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index b63e9fba9a..2efc15fd21 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 289 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 288 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, OBJ_cleanup, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `OBJ_cleanup` frees no added object, because `OBJ_create` throws and none can exist ([obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c#L265-L280)); it must free them once objects can be added. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From bc773cc9d5fd46975faa75c72e35f59d8e92494b Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:57 +0200 Subject: [PATCH 120/612] feat(libSceLibreSsl): implement SSL_get_shutdown --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 16 ++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 0224b76452..0efc1b3f59 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1218,11 +1218,6 @@ int APS5_VABI SSL_get_session_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_shutdown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 89c984e2d6..cb226a9590 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -91,4 +91,8 @@ std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).verifyResult; } +int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSsl* ssl) { + return Internal(ssl, __func__).shutdown; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 2d1a43528c..2bca9bdbdb 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -39,6 +39,7 @@ void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); +int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -109,11 +110,26 @@ static void TestVerifyResult() { Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); } +static void TestGetShutdown() { + TestSslInternal internal {}; + TestSsl ssl {}; + RequireThrows([] { SSL_get_shutdown_nid_postfix(nullptr); }); + RequireThrows([&] { SSL_get_shutdown_nid_postfix(&ssl); }); + ssl.internal = &internal; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 0); + internal.rwstate = 3; + internal.shutdown = 1; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 1); + internal.shutdown = 3; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); TestVersion(); TestVerifyResult(); + TestGetShutdown(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 2efc15fd21..6bc7344650 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 288 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 287 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, OBJ_cleanup, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `OBJ_cleanup` frees no added object, because `OBJ_create` throws and none can exist ([obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c#L265-L280)); it must free them once objects can be added. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 9c0d2f29eebac42ddf91ac8c3931ce1522f8fe60 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:55:07 +0200 Subject: [PATCH 121/612] feat(libSceLibreSsl): implement SSL_want --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 16 ++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 0efc1b3f59..d6f8519fc0 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1295,11 +1295,6 @@ int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void) { return 0; } -int APS5_VABI SSL_want_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_write_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index cb226a9590..69780d7342 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -95,4 +95,8 @@ int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSsl* ssl) { return Internal(ssl, __func__).shutdown; } +int APS5_VABI SSL_want_nid_postfix(const LibreSsl* ssl) { + return Internal(ssl, __func__).rwstate; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 2bca9bdbdb..04abd2ad93 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -40,6 +40,7 @@ void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); +int APS5_VABI SSL_want_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -124,6 +125,20 @@ static void TestGetShutdown() { Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); } +static void TestWant() { + TestSslInternal internal {}; + TestSsl ssl {}; + RequireThrows([] { SSL_want_nid_postfix(nullptr); }); + RequireThrows([&] { SSL_want_nid_postfix(&ssl); }); + ssl.internal = &internal; + Require(SSL_want_nid_postfix(&ssl) == 0); + internal.rwstate = 1; + Require(SSL_want_nid_postfix(&ssl) == 1); + internal.shutdown = 2; + internal.rwstate = 3; + Require(SSL_want_nid_postfix(&ssl) == 3); +} + int main() { TestGetSslCtx(); TestGetRbio(); @@ -131,5 +146,6 @@ int main() { TestVersion(); TestVerifyResult(); TestGetShutdown(); + TestWant(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 6bc7344650..86c2e3f3ca 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 287 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 286 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, OBJ_cleanup, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `OBJ_cleanup` frees no added object, because `OBJ_create` throws and none can exist ([obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c#L265-L280)); it must free them once objects can be added. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown, SSL_want](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 918997af54f58bcd7854200d2794f5c2f62273cd Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 06:01:21 +0100 Subject: [PATCH 122/612] refactor(libSceLibreSsl): declare the SSL accessor exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 53 +++++++++++++++++--- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 40 +-------------- core/libs/tests/GuestLibreSslSsl.cpp | 53 +++++++++----------- 3 files changed, 71 insertions(+), 75 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 3419a18af1..29141d24e3 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -245,6 +245,45 @@ struct LibreSslX509StoreContext { void* exData; }; +struct LibreSslSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct LibreSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + int server; + void* s3; + void* d1; + void* param; + void* cipherList; + void* cert; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* session; + int verifyMode; + int error; + int errorCode; + void* ctx; + std::int64_t verifyResult; + int references; + int clientVersion; + unsigned int maxSendFragment; + char* tlsextHostname; + int tlsextStatusType; + void* initialCtx; + void* encReadCtx; + void* readHash; + LibreSslSslInternal* internal; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -496,7 +535,7 @@ int APS5_VABI SSL_free_nid_postfix(void); int APS5_VABI SSL_get0_alpn_selected_nid_postfix(void); int APS5_VABI SSL_get0_param_nid_postfix(void); int APS5_VABI SSL_get1_session_nid_postfix(void); -int APS5_VABI SSL_get_SSL_CTX_nid_postfix(void); +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_get_certificate_nid_postfix(void); int APS5_VABI SSL_get_current_cipher_nid_postfix(void); int APS5_VABI SSL_get_error_nid_postfix(void); @@ -507,12 +546,12 @@ int APS5_VABI SSL_get_fd_nid_postfix(void); int APS5_VABI SSL_get_peer_cert_chain_nid_postfix(void); int APS5_VABI SSL_get_peer_certificate_nid_postfix(void); int APS5_VABI SSL_get_privatekey_nid_postfix(void); -int APS5_VABI SSL_get_rbio_nid_postfix(void); +void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_get_servername_nid_postfix(void); int APS5_VABI SSL_get_session_nid_postfix(void); -int APS5_VABI SSL_get_shutdown_nid_postfix(void); -int APS5_VABI SSL_get_verify_result_nid_postfix(void); -int APS5_VABI SSL_get_wbio_nid_postfix(void); +int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSsl* ssl); +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSsl* ssl); +void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_library_init_nid_postfix(void); void APS5_VABI SSL_load_error_strings_nid_postfix(void); int APS5_VABI SSL_new_nid_postfix(void); @@ -530,8 +569,8 @@ int APS5_VABI SSL_set_verify_nid_postfix(void); int APS5_VABI SSL_shutdown_nid_postfix(void); int APS5_VABI SSL_use_PrivateKey_ASN1_nid_postfix(void); int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void); -int APS5_VABI SSL_version_nid_postfix(void); -int APS5_VABI SSL_want_nid_postfix(void); +int APS5_VABI SSL_version_nid_postfix(const LibreSsl* ssl); +int APS5_VABI SSL_want_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_write_nid_postfix(void); std::uint64_t APS5_VABI SSLeay_nid_postfix(void); int APS5_VABI SSLv23_client_method_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 69780d7342..74c3e5aa99 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -1,48 +1,10 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslSslInternal { - unsigned char beforeRwstate[156]; - int rwstate; - unsigned char beforeShutdown[8]; - int shutdown; - unsigned char rest[260]; -}; - -struct LibreSsl { - int version; - const void* method; - void* rbio; - void* wbio; - void* bbio; - int server; - void* s3; - void* d1; - void* param; - void* cipherList; - void* cert; - unsigned int sidCtxLength; - unsigned char sidCtx[32]; - void* session; - int verifyMode; - int error; - int errorCode; - void* ctx; - std::int64_t verifyResult; - int references; - int clientVersion; - unsigned int maxSendFragment; - char* tlsextHostname; - int tlsextStatusType; - void* initialCtx; - void* encReadCtx; - void* readHash; - LibreSslSslInternal* internal; -}; - static_assert(sizeof(LibreSslSslInternal) == 432); static_assert(offsetof(LibreSslSslInternal, rwstate) == 156); static_assert(offsetof(LibreSslSslInternal, shutdown) == 168); diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 04abd2ad93..e2549fab2f 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -33,14 +34,8 @@ static_assert(sizeof(TestSslInternal) == 432 && offsetof(TestSslInternal, rwstat static_assert(sizeof(TestSsl) == 232 && offsetof(TestSsl, session) == 128 && offsetof(TestSsl, ctx) == 152); static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, internal) == 224); -extern "C" { -void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); -void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); -void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); -int APS5_VABI SSL_version_nid_postfix(const TestSsl*); -std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); -int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); -int APS5_VABI SSL_want_nid_postfix(const TestSsl*); +static const LibreSsl* Ssl(const TestSsl* ssl) { + return reinterpret_cast(ssl); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -59,10 +54,10 @@ static void TestGetSslCtx() { int session = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_SSL_CTX_nid_postfix(nullptr); }); - Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == nullptr); + Require(SSL_get_SSL_CTX_nid_postfix(Ssl(&ssl)) == nullptr); ssl.ctx = &context; ssl.session = &session; - Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); + Require(SSL_get_SSL_CTX_nid_postfix(Ssl(&ssl)) == &context); } static void TestGetRbio() { @@ -70,10 +65,10 @@ static void TestGetRbio() { int write = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_rbio_nid_postfix(nullptr); }); - Require(SSL_get_rbio_nid_postfix(&ssl) == nullptr); + Require(SSL_get_rbio_nid_postfix(Ssl(&ssl)) == nullptr); ssl.rbio = &read; ssl.wbio = &write; - Require(SSL_get_rbio_nid_postfix(&ssl) == &read); + Require(SSL_get_rbio_nid_postfix(Ssl(&ssl)) == &read); } static void TestGetWbio() { @@ -82,61 +77,61 @@ static void TestGetWbio() { int buffer = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_wbio_nid_postfix(nullptr); }); - Require(SSL_get_wbio_nid_postfix(&ssl) == nullptr); + Require(SSL_get_wbio_nid_postfix(Ssl(&ssl)) == nullptr); ssl.rbio = &read; ssl.wbio = &write; ssl.bbio = &buffer; - Require(SSL_get_wbio_nid_postfix(&ssl) == &write); + Require(SSL_get_wbio_nid_postfix(Ssl(&ssl)) == &write); } static void TestVersion() { TestSsl ssl {}; RequireThrows([] { SSL_version_nid_postfix(nullptr); }); - Require(SSL_version_nid_postfix(&ssl) == 0); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0); ssl.version = 0x0303; - Require(SSL_version_nid_postfix(&ssl) == 0x0303); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0x0303); ssl.version = 0xFEFF; - Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0xFEFF); } static void TestVerifyResult() { TestSsl ssl {}; RequireThrows([] { SSL_get_verify_result_nid_postfix(nullptr); }); - Require(SSL_get_verify_result_nid_postfix(&ssl) == 0); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == 0); ssl.verifyResult = 20; - Require(SSL_get_verify_result_nid_postfix(&ssl) == 20); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == 20); ssl.verifyResult = INT64_C(0x100000002); - Require(SSL_get_verify_result_nid_postfix(&ssl) == INT64_C(0x100000002)); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == INT64_C(0x100000002)); ssl.verifyResult = -1; - Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == -1); } static void TestGetShutdown() { TestSslInternal internal {}; TestSsl ssl {}; RequireThrows([] { SSL_get_shutdown_nid_postfix(nullptr); }); - RequireThrows([&] { SSL_get_shutdown_nid_postfix(&ssl); }); + RequireThrows([&] { SSL_get_shutdown_nid_postfix(Ssl(&ssl)); }); ssl.internal = &internal; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 0); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 0); internal.rwstate = 3; internal.shutdown = 1; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 1); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 1); internal.shutdown = 3; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 3); } static void TestWant() { TestSslInternal internal {}; TestSsl ssl {}; RequireThrows([] { SSL_want_nid_postfix(nullptr); }); - RequireThrows([&] { SSL_want_nid_postfix(&ssl); }); + RequireThrows([&] { SSL_want_nid_postfix(Ssl(&ssl)); }); ssl.internal = &internal; - Require(SSL_want_nid_postfix(&ssl) == 0); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 0); internal.rwstate = 1; - Require(SSL_want_nid_postfix(&ssl) == 1); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 1); internal.shutdown = 2; internal.rwstate = 3; - Require(SSL_want_nid_postfix(&ssl) == 3); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 3); } int main() { From fceca5312b8f5de78fd0fb65970448328938315e Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:00:27 +0200 Subject: [PATCH 123/612] feat(libSceLibreSsl): implement SSL_CTX_get_verify_mode --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 ++++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 53 ++++++++++++++++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 60 +++++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 1 + 5 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/SslCtx.cpp create mode 100644 core/libs/tests/GuestLibreSslSslCtx.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 442fdbe021..89190907af 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SslCtx.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -148,6 +148,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_pkey_type PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_ssl_ctx_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSslCtx.cpp) + target_include_directories(guest_libressl_ssl_ctx_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_ctx_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_ctx_tests) + add_test(NAME guest_libressl_ssl_ctx COMMAND guest_libressl_ssl_ctx_tests) + set_tests_properties(guest_libressl_ssl_ctx PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl_ctx PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index d3c66c41de..f5458ab531 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -983,11 +983,6 @@ int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_load_verify_locations_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp new file mode 100644 index 0000000000..f4566a99a9 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct LibreSslCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + int references; + void* extraCerts; + int verifyMode; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* param; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + LibreSslCtxInternal* internal; +}; + +static_assert(sizeof(LibreSslCtxInternal) == 464); +static_assert(offsetof(LibreSslCtxInternal, quietShutdown) == 292); +static_assert(sizeof(LibreSslCtx) == 120); +static_assert(offsetof(LibreSslCtx, certStore) == 16); +static_assert(offsetof(LibreSslCtx, verifyMode) == 48); +static_assert(offsetof(LibreSslCtx, defaultPasswdCallback) == 96); +static_assert(offsetof(LibreSslCtx, defaultPasswdCallbackUserdata) == 104); +static_assert(offsetof(LibreSslCtx, internal) == 112); + +namespace { + +LibreSslCtx& Context(LibreSslCtx* ctx, const char* function) { + if (!ctx) throw std::invalid_argument(std::string(function) + ": null context"); + return *ctx; +} + +} + +extern "C" { + +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx) { + return Context(ctx, __func__).verifyMode; +} + +} diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp new file mode 100644 index 0000000000..acbcf82c94 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -0,0 +1,60 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +struct TestCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct TestCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + unsigned char beforeVerifyMode[16]; + int verifyMode; + unsigned char beforeCallback[44]; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + TestCtxInternal* internal; +}; + +static_assert(sizeof(TestCtxInternal) == 464 && offsetof(TestCtxInternal, quietShutdown) == 292); +static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && offsetof(TestCtx, defaultPasswdCallback) == 96); + +extern "C" { +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL_CTX check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetVerifyMode() { + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_get_verify_mode_nid_postfix(nullptr); }); + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0); + ctx.verifyMode = 0x03; + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0x03); + ctx.verifyMode = -1; + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); +} + +int main() { + TestGetVerifyMode(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 447430bb92..25617d55bd 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -26,6 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, OBJ_cleanup, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `OBJ_cleanup` frees no added object, because `OBJ_create` throws and none can exist ([obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c#L265-L280)); it must free them once objects can be added. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console +- [SSL_CTX_get_verify_mode](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach it, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 2d1d6c5ad19adb35e2af7d71b7182fd7a27089a4 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:22 +0200 Subject: [PATCH 124/612] feat(libSceLibreSsl): implement SSL_CTX_get_cert_store --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index f5458ab531..afe58f4d63 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -973,11 +973,6 @@ int APS5_VABI SSL_CTX_free_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_get_cert_store_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index f4566a99a9..446eff72de 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -50,4 +50,8 @@ int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx) { return Context(ctx, __func__).verifyMode; } +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx) { + return Context(ctx, __func__).certStore; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index acbcf82c94..df6c8d1c3d 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -31,6 +31,7 @@ static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && o extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -54,7 +55,20 @@ static void TestGetVerifyMode() { Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); } +static void TestGetCertStore() { + int store = 0; + int ciphers = 0; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_get_cert_store_nid_postfix(nullptr); }); + Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == nullptr); + ctx.cipherList = &ciphers; + ctx.certStore = &store; + ctx.sessionTimeout = -1; + Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); +} + int main() { TestGetVerifyMode(); + TestGetCertStore(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 25617d55bd..a96aca17f2 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 293 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 291 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, OBJ_cleanup, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `OBJ_cleanup` frees no added object, because `OBJ_create` throws and none can exist ([obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c#L265-L280)); it must free them once objects can be added. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_CTX_get_verify_mode](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach it, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From d58cc5afff52d41f8eceffb46da8dbc863b0030d Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:31 +0200 Subject: [PATCH 125/612] feat(libSceLibreSsl): implement SSL_CTX_set_quiet_shutdown --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 6 ++++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 18 ++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index afe58f4d63..50e0e7c36e 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1063,11 +1063,6 @@ int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 446eff72de..2a86c75d25 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -54,4 +54,10 @@ void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx) { return Context(ctx, __func__).certStore; } +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode) { + LibreSslCtx& context = Context(ctx, __func__); + if (!context.internal) throw std::invalid_argument(std::string(__func__) + ": null internal state"); + context.internal->quietShutdown = mode; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index df6c8d1c3d..38f3bb5fad 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -32,6 +32,7 @@ static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && o extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -67,8 +68,25 @@ static void TestGetCertStore() { Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); } +static void TestSetQuietShutdown() { + TestCtxInternal internal {}; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_set_quiet_shutdown_nid_postfix(nullptr, 1); }); + RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); }); + ctx.internal = &internal; + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); + Require(internal.quietShutdown == 1); + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, INT_MIN); + Require(internal.quietShutdown == INT_MIN); + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 0); + Require(internal.quietShutdown == 0); + for (unsigned char byte : internal.beforeQuietShutdown) Require(byte == 0); + for (unsigned char byte : internal.rest) Require(byte == 0); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); + TestSetQuietShutdown(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index a96aca17f2..f560156deb 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 291 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 290 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, OBJ_cleanup, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `OBJ_cleanup` frees no added object, because `OBJ_create` throws and none can exist ([obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c#L265-L280)); it must free them once objects can be added. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 71a0ce2ac73ea641ad157fe53bc3c9b6d064b51b Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:41 +0200 Subject: [PATCH 126/612] feat(libSceLibreSsl): implement SSL_CTX_set_default_passwd_cb --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 50e0e7c36e..7bb76c6191 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1033,11 +1033,6 @@ int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 2a86c75d25..573dbf8978 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -60,4 +60,8 @@ void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode context.internal->quietShutdown = mode; } +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* callback) { + Context(ctx, __func__).defaultPasswdCallback = callback; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index 38f3bb5fad..613aed964a 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -33,6 +33,7 @@ extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -84,9 +85,20 @@ static void TestSetQuietShutdown() { for (unsigned char byte : internal.rest) Require(byte == 0); } +static void TestSetPasswordCallback() { + int callback = 0; + TestCtx ctx {}; + RequireThrows([&] { SSL_CTX_set_default_passwd_cb_nid_postfix(nullptr, &callback); }); + SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, &callback); + Require(ctx.defaultPasswdCallback == &callback && ctx.defaultPasswdCallbackUserdata == nullptr); + SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, nullptr); + Require(ctx.defaultPasswdCallback == nullptr); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); TestSetQuietShutdown(); + TestSetPasswordCallback(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index f560156deb..40153b90e5 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 290 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 289 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, OBJ_cleanup, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `OBJ_cleanup` frees no added object, because `OBJ_create` throws and none can exist ([obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c#L265-L280)); it must free them once objects can be added. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 1482bff968b8887eacf2015ae0573bad38d869cd Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:51 +0200 Subject: [PATCH 127/612] feat(libSceLibreSsl): implement SSL_CTX_set_default_passwd_cb_userdata --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 7bb76c6191..1a8aba2d68 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1033,11 +1033,6 @@ int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 573dbf8978..b70a0d9f12 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -64,4 +64,8 @@ void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* Context(ctx, __func__).defaultPasswdCallback = callback; } +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* ctx, void* userdata) { + Context(ctx, __func__).defaultPasswdCallbackUserdata = userdata; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index 613aed964a..fe76a282de 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -34,6 +34,7 @@ int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(TestCtx*, void*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -95,10 +96,21 @@ static void TestSetPasswordCallback() { Require(ctx.defaultPasswdCallback == nullptr); } +static void TestSetPasswordUserdata() { + int userdata = 0; + TestCtx ctx {}; + RequireThrows([&] { SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(nullptr, &userdata); }); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, &userdata); + Require(ctx.defaultPasswdCallbackUserdata == &userdata && ctx.defaultPasswdCallback == nullptr && ctx.internal == nullptr); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, nullptr); + Require(ctx.defaultPasswdCallbackUserdata == nullptr); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); TestSetQuietShutdown(); TestSetPasswordCallback(); + TestSetPasswordUserdata(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 40153b90e5..c155dc4792 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 289 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 288 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, OBJ_cleanup, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `OBJ_cleanup` frees no added object, because `OBJ_create` throws and none can exist ([obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c#L265-L280)); it must free them once objects can be added. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb, SSL_CTX_set_default_passwd_cb_userdata](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From e4988f3258eae2b92095157055aab6f5f99e4f3d Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 05:59:27 +0100 Subject: [PATCH 128/612] refactor(libSceLibreSsl): declare the SSL_CTX accessor exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 32 +++++++++++++++--- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 23 +------------ core/libs/tests/GuestLibreSslSslCtx.cpp | 35 +++++++++----------- 3 files changed, 44 insertions(+), 46 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 3419a18af1..4db2d4ac7a 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -245,6 +245,28 @@ struct LibreSslX509StoreContext { void* exData; }; +struct LibreSslCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct LibreSslCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + int references; + void* extraCerts; + int verifyMode; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* param; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + LibreSslCtxInternal* internal; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -455,10 +477,10 @@ int APS5_VABI SSL_CTX_callback_ctrl_nid_postfix(void); int APS5_VABI SSL_CTX_check_private_key_nid_postfix(void); int APS5_VABI SSL_CTX_ctrl_nid_postfix(void); int APS5_VABI SSL_CTX_free_nid_postfix(void); -int APS5_VABI SSL_CTX_get_cert_store_nid_postfix(void); +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx); int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void); int APS5_VABI SSL_CTX_get_ex_new_index_nid_postfix(std::int64_t argl, void* argp, LibreSslExNew newFunction, LibreSslExDup dupFunction, LibreSslExFree freeFunction); -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void); +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx); int APS5_VABI SSL_CTX_load_verify_locations_nid_postfix(void); int APS5_VABI SSL_CTX_load_verify_mem_nid_postfix(void); int APS5_VABI SSL_CTX_new_nid_postfix(void); @@ -470,13 +492,13 @@ int APS5_VABI SSL_CTX_set_cert_store_nid_postfix(void); int APS5_VABI SSL_CTX_set_cert_verify_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_cipher_list_nid_postfix(void); int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void); +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* callback); +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* ctx, void* userdata); int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void); int APS5_VABI SSL_CTX_set_info_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_msg_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(void); +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode); int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void); int APS5_VABI SSL_CTX_set_verify_nid_postfix(void); int APS5_VABI SSL_CTX_use_PrivateKey_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index b70a0d9f12..99eb4fa5a1 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -1,31 +1,10 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslCtxInternal { - unsigned char beforeQuietShutdown[292]; - int quietShutdown; - unsigned char rest[168]; -}; - -struct LibreSslCtx { - const void* method; - void* cipherList; - void* certStore; - std::int64_t sessionTimeout; - int references; - void* extraCerts; - int verifyMode; - unsigned int sidCtxLength; - unsigned char sidCtx[32]; - void* param; - void* defaultPasswdCallback; - void* defaultPasswdCallbackUserdata; - LibreSslCtxInternal* internal; -}; - static_assert(sizeof(LibreSslCtxInternal) == 464); static_assert(offsetof(LibreSslCtxInternal, quietShutdown) == 292); static_assert(sizeof(LibreSslCtx) == 120); diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index fe76a282de..3e77a25e02 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -29,12 +30,8 @@ struct TestCtx { static_assert(sizeof(TestCtxInternal) == 464 && offsetof(TestCtxInternal, quietShutdown) == 292); static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && offsetof(TestCtx, defaultPasswdCallback) == 96); -extern "C" { -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); -void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); -void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); -void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); -void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(TestCtx*, void*); +static LibreSslCtx* Context(TestCtx* ctx) { + return reinterpret_cast(ctx); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -51,11 +48,11 @@ template static void RequireThrows(TAction action, std::source static void TestGetVerifyMode() { TestCtx ctx {}; RequireThrows([] { SSL_CTX_get_verify_mode_nid_postfix(nullptr); }); - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == 0); ctx.verifyMode = 0x03; - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0x03); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == 0x03); ctx.verifyMode = -1; - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == -1); } static void TestGetCertStore() { @@ -63,24 +60,24 @@ static void TestGetCertStore() { int ciphers = 0; TestCtx ctx {}; RequireThrows([] { SSL_CTX_get_cert_store_nid_postfix(nullptr); }); - Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == nullptr); + Require(SSL_CTX_get_cert_store_nid_postfix(Context(&ctx)) == nullptr); ctx.cipherList = &ciphers; ctx.certStore = &store; ctx.sessionTimeout = -1; - Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); + Require(SSL_CTX_get_cert_store_nid_postfix(Context(&ctx)) == &store); } static void TestSetQuietShutdown() { TestCtxInternal internal {}; TestCtx ctx {}; RequireThrows([] { SSL_CTX_set_quiet_shutdown_nid_postfix(nullptr, 1); }); - RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); }); + RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 1); }); ctx.internal = &internal; - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 1); Require(internal.quietShutdown == 1); - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, INT_MIN); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), INT_MIN); Require(internal.quietShutdown == INT_MIN); - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 0); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 0); Require(internal.quietShutdown == 0); for (unsigned char byte : internal.beforeQuietShutdown) Require(byte == 0); for (unsigned char byte : internal.rest) Require(byte == 0); @@ -90,9 +87,9 @@ static void TestSetPasswordCallback() { int callback = 0; TestCtx ctx {}; RequireThrows([&] { SSL_CTX_set_default_passwd_cb_nid_postfix(nullptr, &callback); }); - SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, &callback); + SSL_CTX_set_default_passwd_cb_nid_postfix(Context(&ctx), &callback); Require(ctx.defaultPasswdCallback == &callback && ctx.defaultPasswdCallbackUserdata == nullptr); - SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, nullptr); + SSL_CTX_set_default_passwd_cb_nid_postfix(Context(&ctx), nullptr); Require(ctx.defaultPasswdCallback == nullptr); } @@ -100,9 +97,9 @@ static void TestSetPasswordUserdata() { int userdata = 0; TestCtx ctx {}; RequireThrows([&] { SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(nullptr, &userdata); }); - SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, &userdata); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(Context(&ctx), &userdata); Require(ctx.defaultPasswdCallbackUserdata == &userdata && ctx.defaultPasswdCallback == nullptr && ctx.internal == nullptr); - SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, nullptr); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(Context(&ctx), nullptr); Require(ctx.defaultPasswdCallbackUserdata == nullptr); } From 4de331c64b8d5f6ee4638452d6dda74e14fbd53d Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:56:09 +0000 Subject: [PATCH 129/612] docs(libSceMsgDialog): dialogs that show no UI finish on different status calls Old claim: the dialogs that show nothing (progress bar, system message, other button types, no window, opened while another is shown, no flip within three seconds) finish on the second sceMsgDialogGetStatus or sceMsgDialogUpdateStatus call. What the code does: a progress bar runs until sceMsgDialogClose or 30 seconds (Export.cpp L64-65); a dialog that is not presented finishes on the second status call (L67, L99); a presented dialog that no flip drew leaves the modal state at the first poll after three seconds (ModalDialog.cpp L45-47, 3000 ms in ModalDialog.hpp); sceMsgDialogOpen returns busy while a dialog runs (Export.cpp L94). The entry is split in two so each stays under 1000 characters. Permalinks at b1b5353cfcee098ed17e3ba8a6e616bee97c9215: - https://github.com/boykopovar/AnyPS5/blob/b1b5353cfcee098ed17e3ba8a6e616bee97c9215/core/libs/prx/libSceMsgDialog/Export.cpp#L55-L68 - https://github.com/boykopovar/AnyPS5/blob/b1b5353cfcee098ed17e3ba8a6e616bee97c9215/core/libs/prx/libSceMsgDialog/Export.cpp#L94-L100 - https://github.com/boykopovar/AnyPS5/blob/b1b5353cfcee098ed17e3ba8a6e616bee97c9215/core/libs/prx/libSceIme/src/ModalDialog.cpp#L45-L47 --- docs/dev/debt/libSceMsgDialog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceMsgDialog.md b/docs/dev/debt/libSceMsgDialog.md index a4e20b95dd..9ecd857a71 100644 --- a/docs/dev/debt/libSceMsgDialog.md +++ b/docs/dev/debt/libSceMsgDialog.md @@ -2,7 +2,8 @@ ### Silent stubs -- [libSceMsgDialog](../../../core/libs/prx/libSceMsgDialog/Export.cpp) - only a user message dialog with button type 0, 1, 3 or 4 shows UI, and only while the video output has a window to draw it. A progress bar, a system message, a user message of any other button type, a dialog opened with no window, one opened while another dialog is shown and one that no flip drew within three seconds show none: `sceMsgDialogOpen` opens them without `sceMsgDialogInitialize` as well, and they finish on the second `sceMsgDialogGetStatus` or `sceMsgDialogUpdateStatus` call; the first 160 characters of a user message are only logged. The button a player picks, or that a dialog without UI picks (2 for `YES_NO_FOCUS_NO`, otherwise 1), is kept but not returned, as `sceMsgDialogGetResult` is exported by libSceMsgDialog.native. +- [libSceMsgDialog](../../../core/libs/prx/libSceMsgDialog/Export.cpp) - only a user message dialog with button type 0, 1, 3 or 4 shows UI, and only while the video output has a window to draw it. A progress bar, a system message, a user message of any other button type, a dialog opened with no window, one opened while another dialog is shown and one that no flip drew within three seconds show none: `sceMsgDialogOpen` opens them without `sceMsgDialogInitialize` as well, except that one opened while another is shown returns busy; a progress bar finishes after 30 seconds or on `sceMsgDialogClose`, a system message, another button type or a dialog with no window finishes on the second `sceMsgDialogGetStatus` or `sceMsgDialogUpdateStatus` call, and a shown one that no flip drew finishes at the first status call after three seconds; the first 160 characters of a user message are only logged. +- [libSceMsgDialog](../../../core/libs/prx/libSceMsgDialog/Export.cpp) - the button a player picks, or that a dialog without UI picks (2 for `YES_NO_FOCUS_NO`, otherwise 1), is kept but not returned, as `sceMsgDialogGetResult` is exported by libSceMsgDialog.native. - [libSceMsgDialog](../../../core/libs/prx/libSceMsgDialog/Export.cpp) - a progress bar dialog runs until `sceMsgDialogClose` or for 30 seconds, and `sceMsgDialogProgressBarInc`, `sceMsgDialogProgressBarSetMsg` and `sceMsgDialogProgressBarSetValue` ignore their arguments ### Unknown function info From c88cea35f8fc961dbd49e94ddfa4bd8c3eb0ab31 Mon Sep 17 00:00:00 2001 From: Ahmad Mujahid Date: Sat, 31 Oct 2026 10:04:15 +0400 Subject: [PATCH 130/612] docs: record X509 name lookup gap for dynamic objects --- docs/dev/debt/libSceLibreSsl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 2cd4df21ad..c127fcfd56 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -27,7 +27,7 @@ - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [X509_NAME_get_entry, X509_NAME_ENTRY_get_data](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names and entries the caller builds reach them. A positive entry count with a null entry array, which LibreSSL dereferences, throws -- [X509_NAME_get_index_by_NID, X509_NAME_get_text_by_NID](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) - no console call is verified. Only `NID_undef`, the X.520 (2.5.4.x) attribute types, emailAddress, domainComponent and userId have OID bytes, from LibreSSL 2.5.4's [objects.txt](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/objects.txt). Other NIDs from 0 to 955 throw; for an unassigned one LibreSSL returns -2 or -1 and queues `OBJ_R_UNKNOWN_NID`. NIDs outside that range return -2 or -1, as [obj_dat.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/obj_dat.c) does while `OBJ_create`, which throws, has added no object. A `lastpos` of `INT_MAX` and a matched `len` of `INT_MIN` throw: [x509name.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509name.c) overflows a signed int there. +- [X509_NAME_get_index_by_NID, X509_NAME_get_text_by_NID](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) - no console call is verified. Only `NID_undef`, the X.520 (2.5.4.x) attribute types, emailAddress, domainComponent and userId have OID bytes, from LibreSSL 2.5.4's [objects.txt](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/objects.txt). Other NIDs from 0 to 955 throw; for an unassigned one LibreSSL returns -2 or -1 and queues `OBJ_R_UNKNOWN_NID`. NIDs outside that range return -2 or -1; these functions do not consult dynamic objects added by `OBJ_create`. A `lastpos` of `INT_MAX` and a matched `len` of `INT_MIN` throw: [x509name.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509name.c) overflows a signed int there. ### Functional From dfde2d2d0840b1a3abe1ca7f9e06d3df6559ac71 Mon Sep 17 00:00:00 2001 From: Ahmad Mujahid Date: Sat, 31 Oct 2026 10:11:06 +0400 Subject: [PATCH 131/612] fix(libressl): throw for unsupported dynamic X509 name lookups --- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 5 --- core/libs/tests/GuestLibreSslX509Name.cpp | 36 +++++++++++++++++-- docs/dev/debt/libSceLibreSsl.md | 2 +- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp index dafccf08e1..38e7d1819a 100644 --- a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -30,8 +30,6 @@ struct NameAttribute { unsigned char der[10]; }; -constexpr int NidCount = 956; - constexpr NameAttribute NameAttributes[] { {0, 0, {}}, {13, 3, {0x55, 0x04, 0x03}}, @@ -94,7 +92,6 @@ constexpr NameAttribute NameAttributes[] { }; const NameAttribute* FindAttribute(int nid, const char* function) { - if (nid < 0 || nid >= NidCount) return nullptr; for (const auto& attribute : NameAttributes) { if (attribute.nid == nid) return &attribute; } @@ -135,13 +132,11 @@ void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(const LibreSslX509Name* name, int nid, int lastpos) { const auto* attribute = FindAttribute(nid, __func__); - if (!attribute) return -2; return IndexOf(name, *attribute, lastpos, __func__); } int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(const LibreSslX509Name* name, int nid, char* buf, int len) { const auto* attribute = FindAttribute(nid, __func__); - if (!attribute) return -1; const int index = IndexOf(name, *attribute, -1, __func__); if (index < 0) return -1; const auto* data = static_cast(X509_NAME_ENTRY_get_data_nid_postfix(X509_NAME_get_entry_nid_postfix(name, index))); diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp index 6dd12d19c4..cbb9627c50 100644 --- a/core/libs/tests/GuestLibreSslX509Name.cpp +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -157,9 +157,7 @@ static void TestIndexByNid() { Require(X509_NAME_get_index_by_NID_nid_postfix(name, 15, 7) == -1); Require(X509_NAME_get_index_by_NID_nid_postfix(name, 16, -1) == -1); Require(X509_NAME_get_index_by_NID_nid_postfix(name, 0, -1) == -1); - for (int nid : {-1, 956, INT_MIN, INT_MAX}) Require(X509_NAME_get_index_by_NID_nid_postfix(name, nid, -1) == -2); Require(X509_NAME_get_index_by_NID_nid_postfix(nullptr, 13, -1) == -1); - Require(X509_NAME_get_index_by_NID_nid_postfix(nullptr, -1, -1) == -2); decoded.objects[6].nid = 13; Require(X509_NAME_get_index_by_NID_nid_postfix(name, 13, 4) == -1); Require(X509_NAME_get_index_by_NID_nid_postfix(name, 458, -1) == -1); @@ -208,7 +206,7 @@ static void TestTextByNid() { std::memset(buffer, 0x7f, sizeof(buffer)); Require(X509_NAME_get_text_by_NID_nid_postfix(name, 13, nullptr, 64) == 5); Require(X509_NAME_get_text_by_NID_nid_postfix(name, 13, nullptr, 0) == 5); - for (int nid : {16, -1, 956}) Require(X509_NAME_get_text_by_NID_nid_postfix(name, nid, buffer, 64) == -1); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 16, buffer, 64) == -1); Require(X509_NAME_get_text_by_NID_nid_postfix(nullptr, 13, buffer, 64) == -1); Require(X509_NAME_get_text_by_NID_nid_postfix(name, 16, buffer, INT_MIN) == -1); Require(BufferHolds(buffer, "", 0)); @@ -226,10 +224,42 @@ static void TestTextByNid() { RequireThrows([&] { X509_NAME_get_text_by_NID_nid_postfix(name, 13, buffer, 64); }); } +static void TestDynamicNid() { + OBJ_cleanup_nid_postfix(); + ERR_clear_error_nid_postfix(); + const int nid = OBJ_create_nid_postfix("1.2.3.4", "testName", "Test Name"); + Require(nid >= 956); + DecodedName decoded; + bool indexAccepted = false; + try { + const int index = X509_NAME_get_index_by_NID_nid_postfix(&decoded.name, nid, -1); + indexAccepted = index == 6; + if (!indexAccepted) std::fprintf(stderr, "dynamic X509_NAME index: expected 6 or not implemented, got %d\n", index); + } catch (const std::runtime_error& error) { + Require(std::strstr(error.what(), "X509_NAME_get_index_by_NID") && std::strstr(error.what(), "not implemented")); + indexAccepted = true; + } + bool textAccepted = false; + char buffer[2] {}; + try { + const int length = X509_NAME_get_text_by_NID_nid_postfix(&decoded.name, nid, buffer, sizeof(buffer)); + textAccepted = length == 1 && std::strcmp(buffer, "x") == 0; + if (!textAccepted) std::fprintf(stderr, "dynamic X509_NAME text: expected x with length 1 or not implemented, got length %d\n", length); + } catch (const std::runtime_error& error) { + Require(std::strstr(error.what(), "X509_NAME_get_text_by_NID") && std::strstr(error.what(), "not implemented")); + textAccepted = true; + } + OBJ_cleanup_nid_postfix(); + ERR_clear_error_nid_postfix(); + Require(indexAccepted); + Require(textAccepted); +} + int main() { TestGetEntry(); TestEntryGetData(); TestIndexByNid(); TestTextByNid(); + TestDynamicNid(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index c127fcfd56..00d27e6b41 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -27,7 +27,7 @@ - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [X509_NAME_get_entry, X509_NAME_ENTRY_get_data](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names and entries the caller builds reach them. A positive entry count with a null entry array, which LibreSSL dereferences, throws -- [X509_NAME_get_index_by_NID, X509_NAME_get_text_by_NID](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) - no console call is verified. Only `NID_undef`, the X.520 (2.5.4.x) attribute types, emailAddress, domainComponent and userId have OID bytes, from LibreSSL 2.5.4's [objects.txt](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/objects.txt). Other NIDs from 0 to 955 throw; for an unassigned one LibreSSL returns -2 or -1 and queues `OBJ_R_UNKNOWN_NID`. NIDs outside that range return -2 or -1; these functions do not consult dynamic objects added by `OBJ_create`. A `lastpos` of `INT_MAX` and a matched `len` of `INT_MIN` throw: [x509name.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509name.c) overflows a signed int there. +- [X509_NAME_get_index_by_NID, X509_NAME_get_text_by_NID](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) - no console call is verified. Only `NID_undef`, the X.520 (2.5.4.x) attribute types, emailAddress, domainComponent and userId have OID bytes, from LibreSSL 2.5.4's [objects.txt](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/objects.txt). All other NIDs throw, including dynamic objects added by `OBJ_create`; these functions do not consult the added object table. For an unassigned NID, LibreSSL returns -2 or -1 and queues `OBJ_R_UNKNOWN_NID`. A `lastpos` of `INT_MAX` and a matched `len` of `INT_MIN` throw: [x509name.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509name.c) overflows a signed int there. ### Functional From e0af928fe934ca43d5b35aa8ac548e7e67eab4d1 Mon Sep 17 00:00:00 2001 From: Ahmad Mujahid Date: Sat, 31 Oct 2026 10:11:46 +0400 Subject: [PATCH 132/612] docs: qualify the built-in unknown NID error --- docs/dev/debt/libSceLibreSsl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 00d27e6b41..83a2cc4be9 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -27,7 +27,7 @@ - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [X509_NAME_get_entry, X509_NAME_ENTRY_get_data](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names and entries the caller builds reach them. A positive entry count with a null entry array, which LibreSSL dereferences, throws -- [X509_NAME_get_index_by_NID, X509_NAME_get_text_by_NID](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) - no console call is verified. Only `NID_undef`, the X.520 (2.5.4.x) attribute types, emailAddress, domainComponent and userId have OID bytes, from LibreSSL 2.5.4's [objects.txt](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/objects.txt). All other NIDs throw, including dynamic objects added by `OBJ_create`; these functions do not consult the added object table. For an unassigned NID, LibreSSL returns -2 or -1 and queues `OBJ_R_UNKNOWN_NID`. A `lastpos` of `INT_MAX` and a matched `len` of `INT_MIN` throw: [x509name.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509name.c) overflows a signed int there. +- [X509_NAME_get_index_by_NID, X509_NAME_get_text_by_NID](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) - no console call is verified. Only `NID_undef`, the X.520 (2.5.4.x) attribute types, emailAddress, domainComponent and userId have OID bytes, from LibreSSL 2.5.4's [objects.txt](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/objects/objects.txt). All other NIDs throw, including dynamic objects added by `OBJ_create`; these functions do not consult the added object table. For an unassigned built-in NID, LibreSSL returns -2 or -1 and queues `OBJ_R_UNKNOWN_NID`. A `lastpos` of `INT_MAX` and a matched `len` of `INT_MIN` throw: [x509name.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509name.c) overflows a signed int there. ### Functional From f0c452699298fa3c2882e820cec591e463b65c1c Mon Sep 17 00:00:00 2001 From: Bipin Date: Sat, 31 Oct 2026 19:13:05 +0530 Subject: [PATCH 133/612] fix(libSceJobManager): keep persistent workers spinning without the shared lock The keep-alive branch every persistent worker takes on an idle manager dropped and retook manager.lock around each spin window, so with several workerCount threads the releaser retook the lock before a queued waiter woke and the other workers starved inside lock acquisition. Burn the idle window on the workEpoch snapshot without the lock and relock only when dispatches, the persistent count or shutdown changed it, as WakeWorkers, StopManager and setPersistentThreadCount bump it. --- core/libs/prx/libSceJobManager/Export.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceJobManager/Export.cpp b/core/libs/prx/libSceJobManager/Export.cpp index abefb6f4f7..8457fd2cd6 100644 --- a/core/libs/prx/libSceJobManager/Export.cpp +++ b/core/libs/prx/libSceJobManager/Export.cpp @@ -829,13 +829,22 @@ void* APS5_VABI WorkerEntry(void* argument) { continue; } const bool spinning = manager.pendingInvocations != 0 && __rdtsc() - lastWork < manager.spinTicks; - if (spinning || manager.sleepingWorkers >= manager.workerCount - manager.persistentThreads) { + if (spinning) { const std::uint32_t epoch = manager.workEpoch.load(std::memory_order_acquire); guard.unlock(); for (std::uint32_t pause = 0; pause != SpinPauses && manager.workEpoch.load(std::memory_order_acquire) == epoch; ++pause) _mm_pause(); guard.lock(); continue; } + if (manager.sleepingWorkers >= manager.workerCount - manager.persistentThreads) { + const std::uint32_t epoch = manager.workEpoch.load(std::memory_order_acquire); + guard.unlock(); + while (manager.workEpoch.load(std::memory_order_acquire) == epoch) { + for (std::uint32_t pause = 0; pause != SpinPauses; ++pause) _mm_pause(); + } + guard.lock(); + continue; + } ++manager.sleepingWorkers; manager.work.wait(guard, [&manager] { return !manager.running || manager.wakeTokens != 0; }); if (manager.wakeTokens != 0) --manager.wakeTokens; From 3913101faca7cde2b88cab5f01377efd060fbfba Mon Sep 17 00:00:00 2001 From: Mohamed Aziz Ktata <111235921+medazizktata@users.noreply.github.com> Date: Sat, 31 Oct 2026 14:51:40 +0100 Subject: [PATCH 134/612] test(agc): skip the no-driver device test where an elevated loader still opens a device The Vulkan loader ignores VK_DRIVER_FILES and VK_ICD_FILENAMES in a process at high integrity or above, so from an elevated shell agc_driver_vulkan_test_device_no_driver opened the installed GPU and failed. It now skips when the process is elevated and a device opened anyway; without a driver it still runs every check. --- .../tests/execution/VulkanTestDevice.cpp | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/VulkanTestDevice.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/VulkanTestDevice.cpp index c3107a5676..405d4269d2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/VulkanTestDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/VulkanTestDevice.cpp @@ -1,4 +1,10 @@ #include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif #include #include #include @@ -21,6 +27,20 @@ void Require(const bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } +#ifdef _WIN32 +bool HighIntegrity() { + HANDLE token = nullptr; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) return false; + alignas(TOKEN_MANDATORY_LABEL) unsigned char label[sizeof(TOKEN_MANDATORY_LABEL) + SECURITY_MAX_SID_SIZE]; + DWORD size = 0; + const bool queried = GetTokenInformation(token, TokenIntegrityLevel, label, sizeof(label), &size) != FALSE; + CloseHandle(token); + if (!queried) return false; + const PSID sid = reinterpret_cast(label)->Label.Sid; + return *GetSidSubAuthority(sid, *GetSidSubAuthorityCount(sid) - 1u) >= SECURITY_MANDATORY_HIGH_RID; +} +#endif + Outcome Open(std::string& message) { message.clear(); try { @@ -61,6 +81,12 @@ int NoDriverTests() { SetVariable("ANYPS5_GPU", ""); std::string message; const auto optional = Open(message); +#ifdef _WIN32 + if (optional == Outcome::Device && HighIntegrity()) { + std::cout << "skipped, the Vulkan loader ignores VK_DRIVER_FILES in an elevated process and opened " << message << '\n'; + return VulkanTestSkipped; + } +#endif Require(optional == Outcome::Skipped, "without a Vulkan driver the optional device was not skipped: " + message); SetVariable("ANYPS5_REQUIRE_VULKAN", "1"); From 10f1717301ad1de30ab7444f2635bc1f3b86cb7e Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:54:45 +0000 Subject: [PATCH 135/612] fix(tools): keep a debt file's note and sub-headings when debt_split rewrites it --- tools/debt_split.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/tools/debt_split.py b/tools/debt_split.py index 14f0079352..1c26bc5ee7 100644 --- a/tools/debt_split.py +++ b/tools/debt_split.py @@ -138,8 +138,10 @@ def debt_file(name): return f"{DEBT_DIR}/{name}.md" -def render(title, sections, order): +def render(title, sections, order, preamble=()): out = [title, ""] + for text in preamble: + out += [text, ""] for section in sorted(sections, key=lambda section: order.index(section) if section in order else len(order)): out += [f"### {section}", ""] + sections[section] + [""] return "\n".join(out) @@ -154,25 +156,38 @@ def read_debt(root, name): if kind == "heading" and item_section and text.startswith("### "): section = item_section sections.setdefault(section, []) - elif kind != "heading" and section: + elif section and (kind != "heading" or text.startswith("#### ")): sections[section].append(text) return sections +def read_preamble(root, name): + path = root / debt_file(name) + lines = [] + if path.exists(): + for kind, item_section, text in parse(path.read_text(encoding="utf-8-sig")): + if item_section is not None: + break + if kind == "heading" and text.startswith("# ") and not text.startswith("## "): + continue + lines.append(text) + return lines + + def write_lf(path, text): with open(path, "w", encoding="utf-8", newline="\n") as file: file.write(text) -def write_debt(root, name, sections, order): +def write_debt(root, name, sections, order, preamble=()): path = root / debt_file(name) sections = {section: entries for section, entries in sections.items() if entries} - if not sections: + if not sections and not preamble: if path.exists(): path.unlink() return path.parent.mkdir(parents=True, exist_ok=True) - write_lf(path, render(f"# {name} technical debt", sections, order)) + write_lf(path, render(f"# {name} technical debt", sections, order, preamble)) def sections_of(text): @@ -212,11 +227,14 @@ def command_split(root): index, files, order = split(text) if not files: sys.exit(f"{INDEX} has no entries to move") + above = [item for kind, section, item in parse(text) if section is None and kind == "entry"] + if above: + sys.exit(f"{INDEX}: an entry is above the first ### section, so there is no section to file it under: {above[0].splitlines()[0][:100]}") for name, sections in files.items(): existing = read_debt(root, name) for section, entries in sections.items(): existing.setdefault(section, []).extend(entries) - write_debt(root, name, existing, order) + write_debt(root, name, existing, order, read_preamble(root, name)) if index_note() not in index: index.insert(1, index_note()) write_lf(root / INDEX, render_index(index)) @@ -236,9 +254,11 @@ def apply_change(root, base, new, index_text): before = [(kind, section, item, key(kind, item)) for kind, section, item in parse(base) if kind == "entry" and key(kind, item)] after = [(kind, section, item, key(kind, item)) for kind, section, item in parse(new) if kind == "entry" and key(kind, item)] files = {} + preambles = {} def load(name): if name not in files: + preambles[name] = read_preamble(root, name) files[name] = {section: [Entry(text) for text in entries] for section, entries in read_debt(root, name).items()} return files[name] @@ -302,7 +322,7 @@ def apply_change(root, base, new, index_text): insert(j1 + offset, anchor) for name, sections in files.items(): write_debt(root, name, {section: [entry.text for entry in entries] - for section, entries in sections.items()}, order) + for section, entries in sections.items()}, order, preambles[name]) return sorted(debt_file(name) for name in files) From 4f5c2bab3992fd5baaf7ee73e02e1bd6536715f7 Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:54:45 +0000 Subject: [PATCH 136/612] test(tools): cover the note, the empty-section file and the refusal in debt_split --- tools/tests/test_debt_split.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tools/tests/test_debt_split.py b/tools/tests/test_debt_split.py index 79dda7f3d7..4a248c3bcd 100644 --- a/tools/tests/test_debt_split.py +++ b/tools/tests/test_debt_split.py @@ -479,5 +479,32 @@ class DebtSplitTests(unittest.TestCase): self.assertIn("- A toolchain limitation\n\n" + paragraph + "\n", self.read(INDEX)) + def test_split_keeps_the_note_and_sub_headings_of_an_existing_debt_file(self): + self.write("docs/dev/debt/libSceA.md", "# libSceA technical debt\n\nA hand-written note that must stay.\n\n" + "#### Detail heading\n\n### Silent stubs\n\n" + A_STUB + "\n") + extra = "- [libSceA](../../core/libs/prx/libSceA/Export.cpp) - `sceAExtra` returns 1" + self.write(INDEX, "# Project technical debt\n\n### Silent stubs\n\n" + extra + "\n") + self.split() + text = self.read("docs/dev/debt/libSceA.md") + self.assertIn("A hand-written note that must stay.", text) + self.assertIn("#### Detail heading", text) + self.assertIn("`sceAExtra` returns 1", text) + self.assertEqual(text.count(A_STUB), 1) + self.assertLess(text.index("A hand-written note"), text.index("### Silent stubs")) + + def test_a_debt_file_with_only_a_note_is_written_and_not_deleted(self): + debt_split.write_debt(self.root, "libSceA", {}, [], ["A note that belongs to no section."]) + self.assertIn("A note that belongs to no section.", self.read("docs/dev/debt/libSceA.md")) + + def test_split_refuses_an_entry_above_the_first_section_without_writing(self): + before = self.read(INDEX) + self.write(INDEX, "# Project technical debt\n\n" + B_STUB + "\n\n### Silent stubs\n\n" + A_STUB + "\n") + result = self.tool("split") + self.assertEqual(result.returncode, 1, result.stderr) + self.assertIn("above the first ### section", result.stderr) + self.assertFalse((self.root / "docs/dev/debt").exists()) + self.assertNotEqual(before, "") + + if __name__ == "__main__": unittest.main() From 577d9ee29e1996e33981dfc7fc92ac1ad1e930af Mon Sep 17 00:00:00 2001 From: xXPBSXx <20614131+xXPBSXx@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:54:58 +0100 Subject: [PATCH 137/612] docs(debt): name what the emulators do for a sceHttpInit pool size of 0 Co-Authored-By: Claude Sonnet 5.5 --- docs/dev/debt/libSceHttp.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev/debt/libSceHttp.md b/docs/dev/debt/libSceHttp.md index c24ee6b1e0..694d58f273 100644 --- a/docs/dev/debt/libSceHttp.md +++ b/docs/dev/debt/libSceHttp.md @@ -26,6 +26,7 @@ - [sceHttpDbgEnableProfile](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the export is absent from the supplied native module; its signature remains unresolved. - [sceHttpDbgSetPrintf](../../../core/libs/prx/libSceHttp/Export.cpp) returns constant success without consuming arguments in the supplied module; the callback contract remains unresolved. - [sceHttpDbgShowStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the native diagnostic consumes no arguments; it prints library state. The throwing implementation remains unchanged. +- [sceHttpInit](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - a pool size of 0 returns a context id like any other size. That is an assumption that no console run has checked. shadPS4 (PS4, `src/core/libraries/network/http.cpp`, commit 0614ce324be092791b758d79f93d9f1ca5b10a81) returns the kernel `EINVAL` for 0, fpPS4 (PS4, `src/ps4_libscehttp.pas`, commit 04cefd43e6fddd1ab033e7980cd356d14c964905) accepts any size, and KytyPS5 (PS5, `src/libs/network.cpp`, commit 5e837a9beac82c031868eaad9348d6a48245fb99) treats 0 as not implemented (`EXIT_NOT_IMPLEMENTED`) and returns no value. Our `sceSslInit` returns the kernel `EINVAL` for 0 (added in commit 2798e6a6fd), but neither that commit nor the libSceSsl entries say a console run measured it. Which result is right is not decided. Twenty-nine of the 53 local titles import `sceHttpInit`. - [sceHttpDbgShowConnectionStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the native diagnostic consumes no arguments; it prints library state. The throwing implementation remains unchanged. - [sceHttpDbgShowRequestStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the native diagnostic consumes no arguments; it prints library state. The throwing implementation remains unchanged. - [sceHttpDbgShowMemoryPoolStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the native diagnostic consumes no arguments; it prints library state. The throwing implementation remains unchanged. From 83ab32415f23e587c9c9be5f07c1ea21509ef918 Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Sat, 31 Oct 2026 14:38:08 +0000 Subject: [PATCH 138/612] fix(tools): report --scripts outside a git checkout as a usage error --- tools/check_test_registration.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/check_test_registration.py b/tools/check_test_registration.py index 60042563e1..8bf0abe498 100644 --- a/tools/check_test_registration.py +++ b/tools/check_test_registration.py @@ -221,7 +221,10 @@ def main(argv=None): parser.add_argument("--scripts", action="store_true", help="instead check that an add_test or a workflow runs every test_*.py of the source tree") args = parser.parse_args(argv) if args.scripts: - scripts, unrun = unrun_scripts(Path(__file__).resolve().parents[1]) + try: + scripts, unrun = unrun_scripts(Path(__file__).resolve().parents[1]) + except (OSError, RuntimeError, ValueError, subprocess.CalledProcessError) as error: + parser.error(f"--scripts reads the source tree from git, so run it in a git checkout: {error}") for name in unrun: print(f"error: {name} is a test script that no add_test or workflow runs") if unrun: From c770aebb6a38fd18c954657669b549a714e9a469 Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Sat, 31 Oct 2026 14:38:08 +0000 Subject: [PATCH 139/612] test(tools): cover --scripts outside a git checkout in check_test_registration --- tools/tests/test_test_registration.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tools/tests/test_test_registration.py b/tools/tests/test_test_registration.py index 4ba320e93b..fd76bab25c 100644 --- a/tools/tests/test_test_registration.py +++ b/tools/tests/test_test_registration.py @@ -581,6 +581,15 @@ class CheckRegistrationCliTests(unittest.TestCase): names = check_test_registration.built(build) self.assertEqual(names, {"dummy_tests"}) + def test_scripts_outside_a_git_checkout_is_a_usage_error(self): + error = subprocess.CalledProcessError(128, ["git", "ls-files"], stderr="fatal: not a git repository") + with patch.object(check_test_registration, "unrun_scripts", side_effect=error): + with contextlib.redirect_stderr(io.StringIO()) as errors, self.assertRaises(SystemExit) as caught: + check_test_registration.main(["--scripts"]) + self.assertEqual(caught.exception.code, 2) + self.assertIn("run it in a git checkout", errors.getvalue()) + self.assertNotIn("Traceback", errors.getvalue()) + def test_main_scripts_with_explicit_argv(self): with patch.object(check_test_registration, "unrun_scripts", return_value=(["test_a.py"], [])): with contextlib.redirect_stdout(io.StringIO()): From 3188545ac31d435a56457c8cabd3329d349959d9 Mon Sep 17 00:00:00 2001 From: xXPBSXx <20614131+xXPBSXx@users.noreply.github.com> Date: Sat, 31 Oct 2026 16:05:50 +0100 Subject: [PATCH 140/612] test(libSceSysmodule): check which of the 65536 module ids the module accepts Co-Authored-By: Claude Sonnet 5.5 --- core/libs/tests/GuestSysmodule.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/core/libs/tests/GuestSysmodule.cpp b/core/libs/tests/GuestSysmodule.cpp index 20c7a7235c..814bae392d 100644 --- a/core/libs/tests/GuestSysmodule.cpp +++ b/core/libs/tests/GuestSysmodule.cpp @@ -260,8 +260,34 @@ void CheckUnwindInfo() { } +void CheckAcceptedIdSet() { + constexpr std::size_t kAcceptedCount = 141; + constexpr std::uint16_t kAcceptedWithoutName[] = { + 0x0111, 0x0117, 0x011c, 0x011d, 0x011e, 0x011f, 0x0121, 0x0122, 0x0123, 0x0124, 0x0129, + 0x012c, 0x012d, 0x012e, 0x0133, 0x0134, 0x0136, 0x013a, 0x013b, 0x013c, 0x013d, + }; + std::size_t accepted = 0; + for (std::uint32_t id = 0; id <= 0xffff; ++id) { + const std::uint16_t value = static_cast(id); + if (sceSysmoduleIsLoaded(value) != kInvalidValue) { + ++accepted; + continue; + } + Require(sceSysmoduleLoadModule(value) == kInvalidValue); + Require(sceSysmoduleUnloadModule(value) == kInvalidValue); + } + Require(accepted == kAcceptedCount); + for (const std::uint16_t id : kAcceptedWithoutName) { + Require(kModuleTable.find(id) == kModuleTable.end()); + Require(sceSysmoduleIsLoaded(id) == 0); + Require(sceSysmoduleLoadModule(id) == 0); + Require(sceSysmoduleUnloadModule(id) == 0); + } +} + int main() { CheckUnknownIds(); + CheckAcceptedIdSet(); Require(sceSysmoduleIsLoaded(0) == kInvalidValue); Require(sceSysmoduleLoadModule(0) == kInvalidValue); Require(sceSysmoduleUnloadModule(0) == kInvalidValue); From 8dbf191e0c9e843edcef1bc06a6c5be3c900e24f Mon Sep 17 00:00:00 2001 From: Lindamea1 <322051987+Lindamea1@users.noreply.github.com> Date: Thu, 29 Oct 2026 08:13:58 +0000 Subject: [PATCH 141/612] feat(libSceFreeType): implement FT_Stroker_ParseOutline, FT_Stroker_Export and FT_Stroker_ExportBorder Add the three FT_Stroker_* calls #10598 left for a follow-up, with the behaviour of the bundled FreeType 2.15.0 (src/base/ftstroke.c). With a 64-bit FT_Pos they pass FreeType a native outline header that shares the guest arrays, so errors, stroker state and the exported points, tags, contour ends and 16-bit counts are those of native FreeType. On every host ParseOutline with a stroker throws before the stroker is rewound for null arrays in an outline with contours and for contour ends at or beyond n_points, which FreeType reads unchecked, and for a coordinate outside int32_t in a contour that FreeType strokes: ft_trig_prenorm truncates point differences to 32 bits and is undefined when that leaves zero. With the 32-bit Windows FT_Pos, ParseOutline returns the native error for a null outline or stroker and otherwise converts the points before the call, throwing for coordinates outside 32 bits. The exports return for a null stroker or outline and for a border other than 0 or 1, and throw in every other case, even for an empty or uncounted border. The libSceFreeTypeOt aliases of the three NIDs are renamed after the calls and still throw. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceFreeType/include/Api.hpp | 3 + core/libs/prx/libSceFreeType/src/Stroker.cpp | 84 +++++ core/libs/prx/libSceFreeTypeOt/Export.cpp | 12 +- core/libs/tests/GuestFreeTypeStroker.cpp | 362 +++++++++++++++++++ docs/dev/debt/libSceFreeType.md | 2 + docs/dev/debt/libSceFreeTypeOt.md | 4 +- 6 files changed, 460 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceFreeType/include/Api.hpp b/core/libs/prx/libSceFreeType/include/Api.hpp index 27187273b3..82f07451c4 100644 --- a/core/libs/prx/libSceFreeType/include/Api.hpp +++ b/core/libs/prx/libSceFreeType/include/Api.hpp @@ -92,6 +92,9 @@ int APS5_VABI FT_Stroker_GetCounts_nid_postfix(void* stroker, std::uint32_t* poi int APS5_VABI FT_Stroker_CubicTo_nid_postfix(void* stroker, const FreeType::GuestVector* control1, const FreeType::GuestVector* control2, const FreeType::GuestVector* to); void APS5_VABI FT_Stroker_Done_nid_postfix(void* stroker); int APS5_VABI FT_Stroker_GetBorderCounts_nid_postfix(void* stroker, std::int32_t border, std::uint32_t* points, std::uint32_t* contours); +int APS5_VABI FT_Stroker_ParseOutline_nid_postfix(void* stroker, const FreeType::GuestOutline* outline, std::uint8_t opened); +void APS5_VABI FT_Stroker_ExportBorder_nid_postfix(void* stroker, std::int32_t border, FreeType::GuestOutline* outline); +void APS5_VABI FT_Stroker_Export_nid_postfix(void* stroker, FreeType::GuestOutline* outline); int APS5_VABI FT_Stroker_EndSubPath_nid_postfix(void* stroker); int APS5_VABI FT_Stroker_LineTo_nid_postfix(void* stroker, const FreeType::GuestVector* to); int APS5_VABI FT_Stroker_New_nid_postfix(FreeType::GuestLibrary* library, void** stroker); diff --git a/core/libs/prx/libSceFreeType/src/Stroker.cpp b/core/libs/prx/libSceFreeType/src/Stroker.cpp index ca2fe64191..a63f830968 100644 --- a/core/libs/prx/libSceFreeType/src/Stroker.cpp +++ b/core/libs/prx/libSceFreeType/src/Stroker.cpp @@ -1,19 +1,27 @@ #include "prx/libSceFreeType/include/Api.hpp" #include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include FT_STROKER_H +#include #include #include #include #include +#include using namespace FreeType; namespace { +constexpr bool SharedPoints = sizeof(FT_Pos) == sizeof(std::int64_t); + +static_assert(!SharedPoints || (sizeof(FT_Vector) == sizeof(GuestVector) && offsetof(FT_Vector, x) == offsetof(GuestVector, x) && + offsetof(FT_Vector, y) == offsetof(GuestVector, y))); + FT_Pos HostCoordinate(std::int64_t value, const char* function) { if (value < std::numeric_limits::min() || value > std::numeric_limits::max()) { throw std::runtime_error(std::string(function) + ": a value outside the host FT_Pos range is not implemented"); @@ -38,6 +46,57 @@ void WriteCount(std::uint32_t* target, FT_UInt value) { *target = value; } +FT_Outline NativeOutline(const GuestOutline& outline, FT_Vector* points) { + return {outline.n_contours, outline.n_points, points, outline.tags, outline.contours, outline.flags}; +} + +bool FitsInt32(std::int64_t value) { + return value >= std::numeric_limits::min() && value <= std::numeric_limits::max(); +} + +void CheckOutlineArrays(const GuestOutline& outline, const char* function) { + if (outline.n_contours == 0) return; + if (!outline.points || !outline.tags || !outline.contours) NotImplemented_nid_no_patch(function); + for (std::size_t contour = 0; contour < outline.n_contours; ++contour) { + if (outline.contours[contour] >= outline.n_points) NotImplemented_nid_no_patch(function); + } +} + +bool StrokedPointsFitInt32(const GuestOutline& outline) { + std::int32_t last = -1; + for (std::size_t contour = 0; contour < outline.n_contours; ++contour) { + const std::int32_t first = last + 1; + last = outline.contours[contour]; + if (last <= first) continue; + for (std::int32_t index = first; index <= last; ++index) { + if (!FitsInt32(outline.points[index].x) || !FitsInt32(outline.points[index].y)) return false; + } + } + return true; +} + +std::vector NativePoints(const GuestOutline& outline, const char* function) { + std::vector points; + if (outline.n_contours == 0) return points; + points.reserve(outline.n_points); + for (std::size_t index = 0; index < outline.n_points; ++index) { + points.push_back({HostCoordinate(outline.points[index].x, function), HostCoordinate(outline.points[index].y, function)}); + } + return points; +} + +void ExportBorder(void* stroker, std::int32_t border, GuestOutline* outline, const char* function) { + if (!stroker || !outline || (border != FT_STROKER_BORDER_LEFT && border != FT_STROKER_BORDER_RIGHT)) return; + if constexpr (SharedPoints) { + FT_Outline native = NativeOutline(*outline, reinterpret_cast(outline->points)); + FT_Stroker_ExportBorder(static_cast(stroker), static_cast(border), &native); + outline->n_contours = native.n_contours; + outline->n_points = native.n_points; + } else { + NotImplemented_nid_no_patch(function); + } +} + } extern "C" { @@ -108,4 +167,29 @@ int APS5_VABI FT_Stroker_CubicTo_nid_postfix(void* stroker, const GuestVector* c return FT_Stroker_CubicTo(static_cast(stroker), first.pointer, second.pointer, end.pointer); } +int APS5_VABI FT_Stroker_ParseOutline_nid_postfix(void* stroker, const GuestOutline* outline, std::uint8_t opened) { + if (!outline) return FT_Stroker_ParseOutline(static_cast(stroker), nullptr, opened); + if (stroker) CheckOutlineArrays(*outline, __func__); + std::vector converted; + FT_Vector* points = nullptr; + if constexpr (SharedPoints) { + if (stroker && !StrokedPointsFitInt32(*outline)) NotImplemented_nid_no_patch(__func__); + points = reinterpret_cast(outline->points); + } else if (stroker) { + converted = NativePoints(*outline, __func__); + points = converted.data(); + } + FT_Outline native = NativeOutline(*outline, points); + return FT_Stroker_ParseOutline(static_cast(stroker), &native, opened); +} + +void APS5_VABI FT_Stroker_ExportBorder_nid_postfix(void* stroker, std::int32_t border, GuestOutline* outline) { + ExportBorder(stroker, border, outline, __func__); +} + +void APS5_VABI FT_Stroker_Export_nid_postfix(void* stroker, GuestOutline* outline) { + ExportBorder(stroker, FT_STROKER_BORDER_LEFT, outline, __func__); + ExportBorder(stroker, FT_STROKER_BORDER_RIGHT, outline, __func__); +} + } diff --git a/core/libs/prx/libSceFreeTypeOt/Export.cpp b/core/libs/prx/libSceFreeTypeOt/Export.cpp index 91243f68ee..2d2f5522df 100644 --- a/core/libs/prx/libSceFreeTypeOt/Export.cpp +++ b/core/libs/prx/libSceFreeTypeOt/Export.cpp @@ -424,15 +424,15 @@ int APS5_VABI sceFreeTypeOtStrokerEndSubPath_nid_no_patch() { } APS5_EXPORT("pBqh-Uh-gns", sceFreeTypeOtStrokerEndSubPath_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_2rcvKZfm5rI_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerExport_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("2rcvKZfm5rI", sceFreeTypeOtUnknown_2rcvKZfm5rI_nid_no_patch); +APS5_EXPORT("2rcvKZfm5rI", sceFreeTypeOtStrokerExport_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_7cIS2cFG3PE_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerExportBorder_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("7cIS2cFG3PE", sceFreeTypeOtUnknown_7cIS2cFG3PE_nid_no_patch); +APS5_EXPORT("7cIS2cFG3PE", sceFreeTypeOtStrokerExportBorder_nid_no_patch); int APS5_VABI sceFreeTypeOtStrokerGetBorderCounts_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -454,10 +454,10 @@ int APS5_VABI sceFreeTypeOtStrokerNew_nid_no_patch() { } APS5_EXPORT("eqGCaeizvWM", sceFreeTypeOtStrokerNew_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_RvFx7dv9ThU_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerParseOutline_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("RvFx7dv9ThU", sceFreeTypeOtUnknown_RvFx7dv9ThU_nid_no_patch); +APS5_EXPORT("RvFx7dv9ThU", sceFreeTypeOtStrokerParseOutline_nid_no_patch); int APS5_VABI sceFreeTypeOtStrokerRewind_nid_no_patch() { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/tests/GuestFreeTypeStroker.cpp b/core/libs/tests/GuestFreeTypeStroker.cpp index 55e5567685..1c6b22e448 100644 --- a/core/libs/tests/GuestFreeTypeStroker.cpp +++ b/core/libs/tests/GuestFreeTypeStroker.cpp @@ -4,9 +4,14 @@ #include FT_STROKER_H +#include #include #include #include +#include +#include +#include +#include using namespace FreeType; @@ -18,6 +23,114 @@ static void Check(bool value, int line) { } #define Require(value) Check((value), __LINE__) +constexpr bool SharedPoints = sizeof(FT_Pos) == sizeof(std::int64_t); + +template +static bool Throws(TCall call) { + try { + call(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +struct Shape { + std::vector points; + std::vector tags; + std::vector contours; + std::vector nativePoints; + + Shape(std::vector guestPoints, std::vector pointTags, std::vector contourEnds) + : points(std::move(guestPoints)), tags(std::move(pointTags)), contours(std::move(contourEnds)) { + for (const GuestVector& point : points) nativePoints.push_back({static_cast(point.x), static_cast(point.y)}); + } + + GuestOutline Guest() { + return {static_cast(contours.size()), static_cast(points.size()), points.data(), tags.data(), contours.data(), 0}; + } + + FT_Outline Native() { + return {static_cast(contours.size()), static_cast(points.size()), nativePoints.data(), tags.data(), contours.data(), 0}; + } +}; + +static Shape SquareOutline() { + return Shape({{0, 0}, {640, 0}, {640, 640}, {0, 640}}, {1, 1, 1, 1}, {3}); +} + +static std::vector Shapes() { + std::vector shapes; + shapes.push_back(SquareOutline()); + shapes.push_back(Shape({{0, 320}, {320, 640}, {640, 320}, {320, 0}}, {0, 1, 0, 1}, {3})); + shapes.push_back(Shape({{-321, 5}, {320, 641}, {639, 320}, {-2, -7}}, {0, 0, 0, 0}, {3})); + shapes.push_back(Shape({{0, 0}, {0, 640}, {640, 640}, {640, 0}, {640, -640}, {0, -640}}, {1, 2, 2, 1, 2, 2}, {5})); + shapes.push_back(Shape({{5, 5}, {0, 0}, {640, 0}, {640, 640}, {0, 640}, {100, 100}, {200, 300}, {300, 100}}, + {1, 0x09, 1, 0x11, 1, 0x05, 0, 1}, {0, 4, 7})); + return shapes; +} + +struct Target { + std::uint16_t prefix; + std::vector guestPoints; + std::vector nativePoints; + std::vector guestTags; + std::vector nativeTags; + std::vector guestContours; + std::vector nativeContours; + GuestOutline guest{}; + FT_Outline native{}; + + Target(std::uint16_t prefixPoints, std::size_t points, std::size_t contours) + : prefix(prefixPoints), guestPoints(prefixPoints + points), nativePoints(guestPoints.size()), guestTags(guestPoints.size()), + nativeTags(guestPoints.size()), guestContours(contours + 1), nativeContours(contours + 1) { + for (std::size_t index = 0; index < guestPoints.size(); ++index) { + guestPoints[index] = Point(index); + nativePoints[index] = {static_cast(guestPoints[index].x), static_cast(guestPoints[index].y)}; + guestTags[index] = nativeTags[index] = Tag(index); + } + for (std::size_t index = 0; index < guestContours.size(); ++index) guestContours[index] = nativeContours[index] = Contour(index); + guest = {1, prefix, guestPoints.data(), guestTags.data(), guestContours.data(), 0}; + native = {1, prefix, nativePoints.data(), nativeTags.data(), nativeContours.data(), 0}; + } + + GuestVector Point(std::size_t index) const { + if (index >= prefix) return {-77, 99}; + return {static_cast(index % 640), -static_cast(index % 320) - 2}; + } + + unsigned char Tag(std::size_t index) const { + return index < prefix ? 1 : 0xa5; + } + + std::uint16_t Contour(std::size_t index) const { + return index == 0 ? static_cast(prefix - 1) : 0xbeef; + } + + bool Matches() const { + bool same = guest.n_points == native.n_points && guest.n_contours == native.n_contours && guest.flags == native.flags; + for (std::size_t index = 0; index < guestPoints.size(); ++index) { + same = same && guestPoints[index].x == nativePoints[index].x && guestPoints[index].y == nativePoints[index].y && + guestTags[index] == nativeTags[index]; + } + for (std::size_t index = 0; index < guestContours.size(); ++index) same = same && guestContours[index] == nativeContours[index]; + return same; + } + + bool Pristine() const { + bool same = guest.n_points == prefix && guest.n_contours == 1 && native.n_points == prefix && native.n_contours == 1; + for (std::size_t index = 0; index < guestPoints.size(); ++index) { + const GuestVector point = Point(index); + same = same && guestPoints[index].x == point.x && guestPoints[index].y == point.y && guestTags[index] == Tag(index); + same = same && nativePoints[index].x == point.x && nativePoints[index].y == point.y && nativeTags[index] == Tag(index); + } + for (std::size_t index = 0; index < guestContours.size(); ++index) { + same = same && guestContours[index] == Contour(index) && nativeContours[index] == Contour(index); + } + return same; + } +}; + struct Pair { void* guest = nullptr; FT_Stroker native = nullptr; @@ -43,6 +156,24 @@ struct Pair { Require(points == nativePoints && contours == nativeContours); } + void BorderCountsMatch() const { + for (const std::int32_t border : {0, 1}) { + std::uint32_t points = 11; + std::uint32_t contours = 13; + FT_UInt nativePoints = 11; + FT_UInt nativeContours = 13; + Require(FT_Stroker_GetBorderCounts_nid_postfix(guest, border, &points, &contours) == + FT_Stroker_GetBorderCounts(native, static_cast(border), &nativePoints, &nativeContours)); + Require(points == nativePoints && contours == nativeContours); + } + } + + int Parse(GuestOutline outline, FT_Outline nativeOutline, std::uint8_t opened) const { + const int error = FT_Stroker_ParseOutline_nid_postfix(guest, &outline, opened); + Require(error == FT_Stroker_ParseOutline(native, &nativeOutline, opened)); + return error; + } + void Square(std::uint8_t open, bool finish = true) const { const GuestVector corners[] = {{0, 0}, {640, 0}, {640, 640}, {0, 640}}; FT_Vector nativeStart{0, 0}; @@ -219,6 +350,231 @@ static void TestCubicTo(GuestLibrary& library, FT_Library native) { Require(FT_Stroker_CubicTo_nid_postfix(nullptr, &first, &second, &to) == FT_Stroker_CubicTo(nullptr, &nativeFirst, &nativeSecond, &nativeTo)); } +static int ParseFresh(GuestLibrary& library, FT_Library native, Shape& shape, std::uint8_t opened) { + Pair pair(library, native); + const int error = pair.Parse(shape.Guest(), shape.Native(), opened); + pair.CountsMatch(); + pair.BorderCountsMatch(); + return error; +} + +static void TestParseOutline(GuestLibrary& library, FT_Library native) { + for (Shape& shape : Shapes()) { + for (const std::uint8_t opened : {0, 1}) { + for (const std::int32_t join : {0, 1, 2}) { + Pair pair(library, native); + FT_Stroker_Set_nid_postfix(pair.guest, 48, FT_STROKER_LINECAP_ROUND, join, 0x20000); + FT_Stroker_Set(pair.native, 48, FT_STROKER_LINECAP_ROUND, static_cast(join), 0x20000); + pair.Square(1); + Require(pair.Parse(shape.Guest(), shape.Native(), opened) == 0); + pair.CountsMatch(); + pair.BorderCountsMatch(); + std::uint32_t points = 0; + std::uint32_t contours = 0; + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &points, &contours) == 0 && points != 0 && contours != 0); + } + } + } + std::vector invalid; + invalid.push_back(Shape({{0, 0}, {320, 640}, {640, 0}}, {1, 2, 1}, {2})); + invalid.push_back(Shape({{0, 0}, {320, 640}, {640, 0}}, {1, 0, 2}, {2})); + invalid.push_back(Shape({{0, 0}, {640, 0}, {640, 640}, {0, 640}, {320, 320}, {0, 0}}, {1, 1, 1, 1, 2, 1}, {3, 5})); + Shape cubicStart({{0, 0}, {320, 640}, {640, 0}}, {2, 2, 1}, {2}); + for (const std::uint8_t opened : {0, 1}) { + for (Shape& shape : invalid) Require(ParseFresh(library, native, shape, opened) == FT_Err_Invalid_Outline); + ParseFresh(library, native, cubicStart, opened); + } + Pair pair(library, native); + pair.Square(0); + Shape square = SquareOutline(); + const GuestOutline outline = square.Guest(); + FT_Outline nativeOutline = square.Native(); + Require(FT_Stroker_ParseOutline_nid_postfix(pair.guest, nullptr, 0) == FT_Err_Invalid_Outline); + Require(FT_Stroker_ParseOutline(pair.native, nullptr, 0) == FT_Err_Invalid_Outline); + Require(FT_Stroker_ParseOutline_nid_postfix(nullptr, nullptr, 1) == FT_Err_Invalid_Outline); + Require(FT_Stroker_ParseOutline_nid_postfix(nullptr, &outline, 0) == FT_Err_Invalid_Argument); + Require(FT_Stroker_ParseOutline(nullptr, &nativeOutline, 0) == FT_Err_Invalid_Argument); + std::uint32_t points = 0; + std::uint32_t contours = 0; + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &points, &contours) == 0 && points != 0 && contours != 0); + pair.CountsMatch(); + Shape empty({}, {}, {}); + Require(pair.Parse(empty.Guest(), empty.Native(), 0) == 0); + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &points, &contours) == 0 && points == 0 && contours == 0); + pair.CountsMatch(); +} + +static void ParseAtHostLimit(GuestLibrary& library, FT_Library native, GuestOutline outline, FT_Outline nativeOutline, bool forwarded) { + Pair pair(library, native); + pair.Square(0); + std::uint32_t points = 0; + std::uint32_t contours = 0; + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &points, &contours) == 0 && points != 0); + if (forwarded) { + Require(pair.Parse(outline, nativeOutline, 0) == 0); + pair.CountsMatch(); + } else { + Require(Throws([&] { FT_Stroker_ParseOutline_nid_postfix(pair.guest, &outline, 0); })); + std::uint32_t keptPoints = 0; + std::uint32_t keptContours = 0; + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &keptPoints, &keptContours) == 0 && keptPoints == points && keptContours == contours); + } +} + +static void TestParseOutlineHostLimits(GuestLibrary& library, FT_Library native) { + constexpr std::int64_t top = std::numeric_limits::max(); + constexpr std::int64_t bottom = std::numeric_limits::min(); + Shape aboveX({{0, 0}, {640, 0}, {640, 640}, {top + 1, 640}}, {1, 1, 1, 1}, {3}); + Shape belowY({{0, 0}, {640, bottom - 1}, {640, 640}, {0, 640}}, {1, 1, 1, 1}, {3}); + ParseAtHostLimit(library, native, aboveX.Guest(), aboveX.Native(), false); + ParseAtHostLimit(library, native, belowY.Guest(), belowY.Native(), false); + const GuestOutline wide = aboveX.Guest(); + Require(FT_Stroker_ParseOutline_nid_postfix(nullptr, &wide, 0) == FT_Err_Invalid_Argument); + Shape skipped({{top + 1, bottom - 1}, {0, 0}, {640, 0}, {640, 640}, {0, 640}}, {1, 1, 1, 1, 1}, {0, 4}); + ParseAtHostLimit(library, native, skipped.Guest(), skipped.Native(), SharedPoints); + if constexpr (SharedPoints) { + Shape edge({{0, 0}, {top, 0}, {top, 640}, {0, bottom}}, {1, 1, 1, 1}, {3}); + ParseAtHostLimit(library, native, edge.Guest(), edge.Native(), true); + } +} + +static void TestParseOutlineArrays(GuestLibrary& library, FT_Library native) { + Shape square = SquareOutline(); + const GuestOutline valid = square.Guest(); + const FT_Outline nativeValid = square.Native(); + GuestOutline noContours = valid; + noContours.contours = nullptr; + GuestOutline noPoints = valid; + noPoints.points = nullptr; + GuestOutline noTags = valid; + noTags.tags = nullptr; + GuestOutline endAtCount = valid; + endAtCount.n_points = 3; + GuestOutline endPastCount = valid; + endPastCount.n_points = 2; + for (const GuestOutline& outline : {noContours, noPoints, noTags, endAtCount, endPastCount}) { + ParseAtHostLimit(library, native, outline, nativeValid, false); + Require(FT_Stroker_ParseOutline_nid_postfix(nullptr, &outline, 0) == FT_Err_Invalid_Argument); + } + std::uint16_t end = 0; + ParseAtHostLimit(library, native, GuestOutline{1, 1, nullptr, nullptr, &end, 0}, FT_Outline{1, 1, nullptr, nullptr, &end, 0}, false); + ParseAtHostLimit(library, native, GuestOutline{0, 4, nullptr, nullptr, nullptr, 0}, FT_Outline{0, 4, nullptr, nullptr, nullptr, 0}, true); +} + +static void TestExportBorder(GuestLibrary& library, FT_Library native) { + std::size_t exported = 0; + for (Shape& shape : Shapes()) { + for (const std::uint8_t opened : {0, 1}) { + for (const std::int32_t border : {0, 1}) { + Pair pair(library, native); + Require(pair.Parse(shape.Guest(), shape.Native(), opened) == 0); + std::uint32_t points = 0; + std::uint32_t contours = 0; + FT_UInt nativePoints = 0; + FT_UInt nativeContours = 0; + Require(FT_Stroker_GetBorderCounts_nid_postfix(pair.guest, border, &points, &contours) == 0); + Require(FT_Stroker_GetBorderCounts(pair.native, static_cast(border), &nativePoints, &nativeContours) == 0); + Require(points == nativePoints && contours == nativeContours); + Target target(3, points, contours); + if constexpr (SharedPoints) { + FT_Stroker_ExportBorder_nid_postfix(pair.guest, border, &target.guest); + FT_Stroker_ExportBorder(pair.native, static_cast(border), &target.native); + Require(target.Matches() && target.guest.n_points == 3 + points && target.guest.n_contours == 1 + contours); + } else { + Require(Throws([&] { FT_Stroker_ExportBorder_nid_postfix(pair.guest, border, &target.guest); }) && target.Pristine()); + } + exported += points; + } + } + } + Require(exported != 0); +} + +static void TestExport(GuestLibrary& library, FT_Library native) { + for (Shape& shape : Shapes()) { + for (const std::uint8_t opened : {0, 1}) { + Pair pair(library, native); + Require(pair.Parse(shape.Guest(), shape.Native(), opened) == 0); + std::uint32_t points = 0; + std::uint32_t contours = 0; + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &points, &contours) == 0 && points != 0 && contours != 0); + pair.CountsMatch(); + Target target(3, points, contours); + if constexpr (SharedPoints) { + FT_Stroker_Export_nid_postfix(pair.guest, &target.guest); + FT_Stroker_Export(pair.native, &target.native); + Require(target.Matches() && target.guest.n_points == 3 + points && target.guest.n_contours == 1 + contours); + } else { + Require(Throws([&] { FT_Stroker_Export_nid_postfix(pair.guest, &target.guest); }) && target.Pristine()); + } + } + } + if constexpr (SharedPoints) { + Shape square = SquareOutline(); + Pair pair(library, native); + Require(pair.Parse(square.Guest(), square.Native(), 0) == 0); + std::uint32_t points = 0; + std::uint32_t contours = 0; + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &points, &contours) == 0 && points > 6); + pair.CountsMatch(); + Target target(65530, points, contours); + FT_Stroker_Export_nid_postfix(pair.guest, &target.guest); + FT_Stroker_Export(pair.native, &target.native); + Require(target.Matches() && target.guest.n_points == static_cast(65530 + points) && target.guest.n_points < 65530); + } +} + +static void TestExportUnchanged(GuestLibrary& library, FT_Library native) { + Shape square = SquareOutline(); + Pair sizing(library, native); + Require(sizing.Parse(square.Guest(), square.Native(), 0) == 0); + std::uint32_t points = 0; + std::uint32_t contours = 0; + Require(FT_Stroker_GetCounts_nid_postfix(sizing.guest, &points, &contours) == 0 && points != 0 && contours != 0); + Pair pair(library, native); + Require(pair.Parse(square.Guest(), square.Native(), 0) == 0); + Target target(3, points, contours); + if constexpr (SharedPoints) { + FT_Stroker_Export_nid_postfix(pair.guest, &target.guest); + FT_Stroker_Export(pair.native, &target.native); + for (const std::int32_t border : {0, 1}) { + FT_Stroker_ExportBorder_nid_postfix(pair.guest, border, &target.guest); + FT_Stroker_ExportBorder(pair.native, static_cast(border), &target.native); + } + } else { + Require(Throws([&] { FT_Stroker_Export_nid_postfix(pair.guest, &target.guest); })); + Require(Throws([&] { FT_Stroker_ExportBorder_nid_postfix(pair.guest, 0, &target.guest); })); + } + Require(target.Pristine()); + pair.CountsMatch(); + for (const std::int32_t border : {2, -1}) { + FT_Stroker_ExportBorder_nid_postfix(pair.guest, border, &target.guest); + FT_Stroker_ExportBorder(pair.native, static_cast(border), &target.native); + } + FT_Stroker_ExportBorder_nid_postfix(nullptr, 0, &target.guest); + FT_Stroker_ExportBorder(nullptr, FT_STROKER_BORDER_LEFT, &target.native); + FT_Stroker_ExportBorder_nid_postfix(pair.guest, 1, nullptr); + FT_Stroker_ExportBorder(pair.native, FT_STROKER_BORDER_RIGHT, nullptr); + FT_Stroker_Export_nid_postfix(nullptr, &target.guest); + FT_Stroker_Export(nullptr, &target.native); + FT_Stroker_Export_nid_postfix(pair.guest, nullptr); + FT_Stroker_Export(pair.native, nullptr); + Require(target.Pristine()); + if constexpr (SharedPoints) { + FT_Stroker_Export_nid_postfix(pair.guest, &target.guest); + FT_Stroker_Export(pair.native, &target.native); + Require(target.Matches() && target.guest.n_points == 3 + points && target.guest.n_contours == 1 + contours); + FT_Stroker_Rewind_nid_postfix(pair.guest); + FT_Stroker_Rewind(pair.native); + Target rewound(3, points, contours); + FT_Stroker_Export_nid_postfix(pair.guest, &rewound.guest); + FT_Stroker_Export(pair.native, &rewound.native); + Require(rewound.Pristine()); + } else { + Require(Throws([&] { FT_Stroker_ExportBorder_nid_postfix(pair.guest, 1, &target.guest); }) && target.Pristine()); + } +} + int main() { GuestLibrary library{}; FT_Library native = nullptr; @@ -234,5 +590,11 @@ int main() { TestGetBorderCounts(library, native); TestConicTo(library, native); TestCubicTo(library, native); + TestParseOutline(library, native); + TestParseOutlineHostLimits(library, native); + TestParseOutlineArrays(library, native); + TestExportBorder(library, native); + TestExport(library, native); + TestExportUnchanged(library, native); Require(FT_Done_FreeType(library.host) == 0 && FT_Done_FreeType(native) == 0); } diff --git a/docs/dev/debt/libSceFreeType.md b/docs/dev/debt/libSceFreeType.md index 5f6f103b47..81e6b266b4 100644 --- a/docs/dev/debt/libSceFreeType.md +++ b/docs/dev/debt/libSceFreeType.md @@ -19,6 +19,8 @@ - [FT_Stroker_Set](../../../core/libs/prx/libSceFreeType/src/Stroker.cpp) (libSceFreeType) - console signature is unverified; the adapter forwards to bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftstroke.c). A radius or miter limit outside the host `FT_Pos` range throws on Windows, where the native type is 32 bits - [FT_Stroker_Rewind](../../../core/libs/prx/libSceFreeType/src/Stroker.cpp) (libSceFreeType) - console signature is unverified; the adapter forwards to bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftstroke.c). The call keeps the radius, cap and join set by `FT_Stroker_Set` - [FT_Stroker_GetBorderCounts](../../../core/libs/prx/libSceFreeType/src/Stroker.cpp) (libSceFreeType) - console signature is unverified; the adapter forwards to bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftstroke.c). Counts are written only to non-null guest pointers +- [FT_Stroker_ParseOutline](../../../core/libs/prx/libSceFreeType/src/Stroker.cpp) (libSceFreeType) - console signature and FreeType version are unverified; the adapter forwards to bundled [FreeType 2.15.0](https://github.com/freetype/freetype/blob/37515cf56ba3349656049edbed89ffc6f6466f2d/src/base/ftstroke.c#L2050-L2264), which returns `Invalid_Outline` for a null outline where [2.4.12](https://github.com/freetype/freetype/blob/cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9/src/base/ftstroke.c#L2062-L2063) returns `Invalid_Argument`. Null arrays with contours and contour ends at or past `n_points` throw before the stroker is rewound, as does a coordinate outside `int32_t` in a stroked contour: [`ft_trig_prenorm`](https://github.com/freetype/freetype/blob/37515cf56ba3349656049edbed89ffc6f6466f2d/src/base/fttrigon.c#L131-L142) truncates differences to 32 bits and is undefined when that leaves zero. On Windows (32-bit `FT_Pos`) such coordinates anywhere also throw; curve midpoints use 32 bits +- [FT_Stroker_ExportBorder, FT_Stroker_Export](../../../core/libs/prx/libSceFreeType/src/Stroker.cpp) (libSceFreeType) - console signatures are unverified and the console's FreeType version is unknown. On Windows (32-bit native `FT_Pos`) both throw for a non-null stroker and outline (`FT_Stroker_ExportBorder`: border 0 or 1), even for an empty or uncounted border: a 32-bit copy needs the border's private point count, and counting marks the border valid. Elsewhere it forwards to bundled [FreeType 2.15.0](https://github.com/freetype/freetype/blob/37515cf56ba3349656049edbed89ffc6f6466f2d/src/base/ftstroke.c#L2013-L2041), which ignores a null stroker or outline and adds to unsigned 16-bit counts; [FreeType 2.4.12](https://github.com/freetype/freetype/blob/cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9/src/base/ftstroke.c#L2008-L2022) has no null checks and [signed 16-bit counts](https://github.com/freetype/freetype/blob/cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9/src/base/ftstroke.c#L700-L748) - [FT_Stroker_ConicTo](../../../core/libs/prx/libSceFreeType/src/Stroker.cpp) (libSceFreeType) - console signature is unverified; the adapter forwards to bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftstroke.c). A guest coordinate outside the host `FT_Pos` range throws on Windows, where the native type is 32 bits - [FT_Stroker_CubicTo](../../../core/libs/prx/libSceFreeType/src/Stroker.cpp) (libSceFreeType) - console signature is unverified; the adapter forwards to bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftstroke.c). A guest coordinate outside the host `FT_Pos` range throws on Windows, where the native type is 32 bits - [FT_Get_Sfnt_Name_Count, FT_Get_Sfnt_Name](../../../core/libs/prx/libSceFreeType/src/Names.cpp) (libSceFreeType) - console signatures, record layout and errors are unverified; the adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftsnames.c) and assumes the public LP64 `FT_SfntName` layout (24 bytes, string pointer at 8, byte length at 16). Strings retain their original encoding, borrow native face storage and remain valid only while the face is alive diff --git a/docs/dev/debt/libSceFreeTypeOt.md b/docs/dev/debt/libSceFreeTypeOt.md index 8de27f976e..ef9ca7f51c 100644 --- a/docs/dev/debt/libSceFreeTypeOt.md +++ b/docs/dev/debt/libSceFreeTypeOt.md @@ -1,7 +1,7 @@ # libSceFreeTypeOt technical debt - The FT_Outline_Get_Orientation, FT_Request_Size, FT_Sin, FT_Tan, FT_Vector_Unit, FT_New_Size, FT_Outline_Copy, FT_Outline_Decompose, FT_Stroker_New, FT_Stroker_Done, FT_Stroker_GetCounts, FT_Stroker_BeginSubPath, FT_Stroker_LineTo, FT_Stroker_EndSubPath, FT_Stroker_Set, FT_Stroker_Rewind, FT_Stroker_GetBorderCounts, FT_Stroker_ConicTo and FT_Stroker_CubicTo NIDs in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) are identified, but their provider-specific prototypes and behavior remain unverified. Their named helpers retain the explicit inventory aliases and throw; the separate libSceFreeType implementations do not supply this provider. -- The 73 `sceFreeTypeOtUnknown_*_nid_no_patch` exports in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) are present in the PS5 3.20 export inventory from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). Their prototypes and behavior are unverified. Each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. +- The 70 `sceFreeTypeOtUnknown_*_nid_no_patch` exports in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) are present in the PS5 3.20 export inventory from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). Their prototypes and behavior are unverified. Each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. ### Unknown function info - [FT_Outline_GetInsideBorder, FT_Outline_GetOutsideBorder](../../../core/libs/prx/libSceFreeTypeOt/CMakeLists.txt) (libSceFreeTypeOl, libSceFreeTypeOt) - console prototypes, enum/layout equivalence, FreeType configuration and module/library versions are unverified. The existing LP64 outline layout and unsigned 16-bit counts follow bundled FreeType 2.13.3; border selection follows [ftstroke.c](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftstroke.c), with the shifted polygon area from [ftoutln.c](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftoutln.c) accumulated in 64 bits. Null positive-count arrays and out-of-range or non-increasing contour endpoints throw when reached; their console behavior is unknown. Tags and flags are not read, and input arrays remain caller-owned. @@ -17,3 +17,5 @@ - [FT_SqrtFixed, FT_Sqrt32](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) (libSceFreeTypeOt) - the exports carry the NIDs of these FreeType names; console prototypes and FreeType version equivalence are unverified. `FT_Property_Set` and `FT_Sqrt32` (`FT_CONFIG_OPTION_OLD_INTERNALS`, removed in 2.5.0) narrow this FreeType to 2.4.11-2.4.12 [ftcalc.c](https://github.com/freetype/freetype/blob/cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9/src/base/ftcalc.c#L155-L186). Both take and return 32-bit `FT_Int32` regardless of `FT_LONG64`: [FT_SqrtFixed](https://github.com/freetype/freetype/blob/cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9/src/base/ftcalc.c#L855-L885) returns 0 for non-positive inputs and floors `sqrt(x << 16)` for positive `x` (unlike the unsigned rounding `FT_SqrtFixed` in bundled 2.13.3), and `FT_Sqrt32` floors the square root of `x` cast to `FT_UInt32`. - [ft_mem_alloc, ft_mem_qalloc, ft_mem_realloc, ft_mem_qrealloc, ft_mem_free, ft_mem_dup, ft_mem_strdup, ft_mem_strcpyn](../../../core/libs/prx/libSceFreeTypeOt/src/Memory.cpp) (libSceFreeTypeOt) - the exports carry the NIDs of these FreeType names; console prototypes and FreeType version equivalence are unverified. The LP64 signatures, the guest `FT_MemoryRec` callbacks and the error codes (Invalid_Argument 0x06, Array_Too_Large 0x0A, Out_Of_Memory 0x40) follow FreeType 2.4.12 [ftutil.c](https://github.com/freetype/freetype/blob/cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9/src/base/ftutil.c#L49-L221). Bundled FreeType 2.13.3 differs only in `ft_mem_qrealloc` from a count of 0, which it does not zero. As in a release build, `ft_mem_qrealloc` does not check the `FT_ASSERT` that a block with no current items is null. The current byte size passed to `realloc` wraps where the source's signed product would overflow. + +- The FT_Stroker_Export, FT_Stroker_ExportBorder and FT_Stroker_ParseOutline NIDs (`2rcvKZfm5rI`, `7cIS2cFG3PE`, `RvFx7dv9ThU`) in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) are identified, but their provider-specific prototypes and behavior remain unverified. Their named helpers retain the explicit inventory aliases and throw; the separate libSceFreeType implementations do not supply this provider. From 54bf0d6058e06abae56bcf0c52acb3ca0a735422 Mon Sep 17 00:00:00 2001 From: akkadbakkad89 Date: Sat, 31 Oct 2026 21:20:14 +0530 Subject: [PATCH 142/612] feat(libSceAppContent): implement sceAppContentGetDownloadedStoreCountry contract --- core/libs/prx/libSceAppContent/Export.cpp | 10 +++++++--- core/libs/prx/libSceAppContent/include/Api.hpp | 2 +- core/libs/tests/GuestAppContent.cpp | 14 ++++++++++++++ docs/dev/debt/libSceAppContent.md | 1 + 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceAppContent/Export.cpp b/core/libs/prx/libSceAppContent/Export.cpp index 478f72de02..7f40ba5747 100644 --- a/core/libs/prx/libSceAppContent/Export.cpp +++ b/core/libs/prx/libSceAppContent/Export.cpp @@ -21,6 +21,7 @@ static constexpr int SCE_APP_CONTENT_ERROR_BUSY = static_cast(0x80D90003); static constexpr int SCE_APP_CONTENT_ERROR_NOT_MOUNTED = static_cast(0x80D90004); static constexpr int SCE_APP_CONTENT_ERROR_NOT_FOUND = static_cast(0x80D90005); static constexpr int SCE_APP_CONTENT_ERROR_DRM_NO_ENTITLEMENT = static_cast(0x80D90007); +static constexpr int SCE_APP_CONTENT_ERROR_GET_STORE_COUNTRY = static_cast(0x80D91000); static constexpr uint32_t APPPARAM_ID_SKU_FLAG = 0; static constexpr int32_t SKU_FLAG_FULL = 3; @@ -315,9 +316,12 @@ int APS5_VABI sceAppContentGetAddcontInfoListByIroTag(uint32_t iroTag, AppConten return 0; } -int APS5_VABI sceAppContentGetDownloadedStoreCountry(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAppContentGetDownloadedStoreCountry(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, void* storeCountry) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; + (void)serviceLabel; + if (!entitlementLabel || !storeCountry) return SCE_APP_CONTENT_ERROR_PARAMETER; + if (!validEntitlementLabel(*entitlementLabel, 0x01500000)) return SCE_APP_CONTENT_ERROR_PARAMETER; + return SCE_APP_CONTENT_ERROR_GET_STORE_COUNTRY; } int APS5_VABI sceAppContentGetEntitlementKey(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, AppContentEntitlementKey* key) { diff --git a/core/libs/prx/libSceAppContent/include/Api.hpp b/core/libs/prx/libSceAppContent/include/Api.hpp index a48561a66e..10c82cdecc 100644 --- a/core/libs/prx/libSceAppContent/include/Api.hpp +++ b/core/libs/prx/libSceAppContent/include/Api.hpp @@ -39,7 +39,7 @@ int APS5_VABI sceAppContentDownload1Expand(void); int APS5_VABI sceAppContentDownload1Shrink(void); int APS5_VABI sceAppContentGetAddcontInfoByEntitlementId(const char* entitlementId, AppContentAddcontInfo* info); int APS5_VABI sceAppContentGetAddcontInfoListByIroTag(uint32_t iroTag, AppContentAddcontInfo* list, uint32_t listNum, uint32_t* hitNum); -int APS5_VABI sceAppContentGetDownloadedStoreCountry(void); +int APS5_VABI sceAppContentGetDownloadedStoreCountry(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, void* storeCountry); int APS5_VABI sceAppContentGetEntitlementKey(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, AppContentEntitlementKey* key); int APS5_VABI sceAppContentGetPftFlag(uint32_t* pftFlag); int APS5_VABI sceAppContentGetRegion(void); diff --git a/core/libs/tests/GuestAppContent.cpp b/core/libs/tests/GuestAppContent.cpp index 709b87bd6a..afcce0269e 100644 --- a/core/libs/tests/GuestAppContent.cpp +++ b/core/libs/tests/GuestAppContent.cpp @@ -15,6 +15,7 @@ static constexpr int ErrorNotFound = static_cast(0x80D90005); static constexpr int ErrorDrmNoEntitlement = static_cast(0x80D90007); static constexpr int ErrorBusy = static_cast(0x80D90003); static constexpr int ErrorNotMounted = static_cast(0x80D90004); +static constexpr int ErrorGetStoreCountry = static_cast(0x80D91000); static void Require(bool value) { if (!value) std::abort(); } int main() { @@ -50,6 +51,7 @@ int main() { std::uint32_t pftFlag = 0x5a5a5a5a; Require(sceAppContentGetPftFlag(&pftFlag) == notInitialized); Require(pftFlag == 0x5a5a5a5a); + Require(sceAppContentGetDownloadedStoreCountry(0, nullptr, nullptr) == notInitialized); const auto original = std::filesystem::current_path(); const auto directory = original / ("appcontent-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); Require(std::filesystem::create_directory(directory)); @@ -238,6 +240,15 @@ int main() { Require(sceAppContentGetEntitlementKey(0, nullptr, &key) == ErrorParameter); Require(std::memcmp(&key, &untouchedKey, sizeof(key)) == 0); Require(sceAppContentGetEntitlementKey(0, &label, nullptr) == ErrorParameter); + unsigned char country[24]; + std::memset(country, 0x5a, sizeof(country)); + unsigned char untouchedCountry[sizeof(country)]; + std::memcpy(untouchedCountry, country, sizeof(country)); + Require(sceAppContentGetDownloadedStoreCountry(0, nullptr, country) == ErrorParameter); + Require(sceAppContentGetDownloadedStoreCountry(0, &label, nullptr) == ErrorParameter); + Require(sceAppContentGetDownloadedStoreCountry(0, &label, country) == ErrorGetStoreCountry); + Require(sceAppContentGetDownloadedStoreCountry(7, &label, country) == ErrorGetStoreCountry); + Require(std::memcmp(country, untouchedCountry, sizeof(country)) == 0); for (const std::uint32_t version : {0x014fffffu, 0x01500000u, 0x034fffffu, 0x03500000u}) { std::memcpy(process.data() + 16, &version, sizeof(version)); @@ -246,7 +257,10 @@ int main() { const int earlyError = version >= 0x01500000 ? ErrorParameter : ErrorNotFound; const int infoError = version >= 0x01500000 ? ErrorParameter : ErrorDrmNoEntitlement; const int lateError = version >= 0x03500000 ? ErrorParameter : ErrorNotFound; + const int countryError = version >= 0x01500000 ? ErrorParameter : ErrorGetStoreCountry; Require(sceAppContentAddcontMount(0, &label, &mountPoint) == earlyError); + Require(sceAppContentGetDownloadedStoreCountry(0, &label, info) == countryError); + Require(std::memcmp(info, untouchedInfo, sizeof(info)) == 0); Require(sceAppContentGetAddcontInfo(0, &label, reinterpret_cast(info)) == infoError); Require(sceAppContentGetEntitlementKey(0, &label, &key) == infoError); Require(std::memcmp(&key, &untouchedKey, sizeof(key)) == 0); diff --git a/docs/dev/debt/libSceAppContent.md b/docs/dev/debt/libSceAppContent.md index c75f621a36..969273f4e6 100644 --- a/docs/dev/debt/libSceAppContent.md +++ b/docs/dev/debt/libSceAppContent.md @@ -18,6 +18,7 @@ - [libSceAppContent pending exports](../../../core/libs/prx/libSceAppContent/Export.cpp) - 8 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAppContent.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. ### Functional +- [sceAppContentGetDownloadedStoreCountry](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - the firmware 4.03 `libSceAppContent.sprx` body is a thin RPC client: initialization guard, null-parameter checks, an entitlement-label padding gate at SDK 0x01500000, then a vtable call marshalling a system-service request (selector `0x20005`, request `{serviceLabel}`, a 0x14-byte label input and a 0x18-byte reply buffer) and translating the reply status into the `0x80d9xxxx` family; its translator passes `0x80d91000` GET_STORE_COUNTRY through unchanged. The export signature is thunk-verified (`uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, void* storeCountry`; the former `()` declaration was an inventory placeholder). No system service exists on host, so after the verified checks it answers `GET_STORE_COUNTRY`, the dedicated error the firmware translator reserves for this query; the real service replies and the 0x18-byte reply layout are unverified. - [Initialization](../../../core/libs/prx/libSceAppContent/Export.cpp) - service readiness and boot attributes are not modeled; successful initialization returns a zero boot record. Initialization-state guards, SDK 0x01500000 repeated-initialization errors and reserved-field checks are recovered. SDK-dependent service-error translation remains incomplete. - [Additional content](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out; `sceAppContentGetAddcontInfo` answers `DRM_NO_ENTITLEMENT` for every label and `sceAppContentGetAddcontInfoList` reports 0 entries; `sceAppContentAddcontEnqueueDownload` answers `PARAMETER` for a null label and `NOT_FOUND` otherwise, since no download is ever queued; `sceAppContentAddcontDelete` answers `PARAMETER` for a null label and `NOT_FOUND` otherwise, since no additional content is ever installed. `sceAppContentGetAddcontDownloadProgress` answers `PARAMETER` for a null label or progress and `NOT_FOUND` otherwise, as [dlc_emu](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.cpp#L2328-L2341) does for a label that is not installed additional content, and never writes the progress. Its 16-byte layout (`DataSize`, then `DownloadedSize`, both 64-bit) follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/AppContent/AppContent.cs#L45-L54) and is unused here. The signatures of `sceAppContentAddcontEnqueueDownload` and `sceAppContentGetAddcontDownloadProgress` follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/AppContent/AppContent.cs#L140-L148), and the signature of `sceAppContentAddcontDelete` follows [dlc_emu](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.h#L91-L92); dlc_emu is not a console measurement and no console source gives the error codes, so the signatures and the error codes are unverified From 9a5fa0ea99e06c824072cd33ddb655011e45402a Mon Sep 17 00:00:00 2001 From: xXPBSXx <20614131+xXPBSXx@users.noreply.github.com> Date: Sat, 31 Oct 2026 16:54:51 +0100 Subject: [PATCH 143/612] docs(debt): name the measured sceSslInit result next to the sceHttpInit pool size of 0 Co-Authored-By: Claude Sonnet 5.5 --- docs/dev/debt/libSceHttp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceHttp.md b/docs/dev/debt/libSceHttp.md index 694d58f273..fd6fb7b024 100644 --- a/docs/dev/debt/libSceHttp.md +++ b/docs/dev/debt/libSceHttp.md @@ -26,7 +26,7 @@ - [sceHttpDbgEnableProfile](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the export is absent from the supplied native module; its signature remains unresolved. - [sceHttpDbgSetPrintf](../../../core/libs/prx/libSceHttp/Export.cpp) returns constant success without consuming arguments in the supplied module; the callback contract remains unresolved. - [sceHttpDbgShowStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the native diagnostic consumes no arguments; it prints library state. The throwing implementation remains unchanged. -- [sceHttpInit](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - a pool size of 0 returns a context id like any other size. That is an assumption that no console run has checked. shadPS4 (PS4, `src/core/libraries/network/http.cpp`, commit 0614ce324be092791b758d79f93d9f1ca5b10a81) returns the kernel `EINVAL` for 0, fpPS4 (PS4, `src/ps4_libscehttp.pas`, commit 04cefd43e6fddd1ab033e7980cd356d14c964905) accepts any size, and KytyPS5 (PS5, `src/libs/network.cpp`, commit 5e837a9beac82c031868eaad9348d6a48245fb99) treats 0 as not implemented (`EXIT_NOT_IMPLEMENTED`) and returns no value. Our `sceSslInit` returns the kernel `EINVAL` for 0 (added in commit 2798e6a6fd), but neither that commit nor the libSceSsl entries say a console run measured it. Which result is right is not decided. Twenty-nine of the 53 local titles import `sceHttpInit`. +- [sceHttpInit](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - a pool size of 0 returns a context id like any other size. That is an assumption that no console run has checked. shadPS4 (PS4, `src/core/libraries/network/http.cpp`, commit 0614ce324be092791b758d79f93d9f1ca5b10a81) returns the kernel `EINVAL` for 0, fpPS4 (PS4, `src/ps4_libscehttp.pas`, commit 04cefd43e6fddd1ab033e7980cd356d14c964905) accepts any size, and KytyPS5 (PS5, `src/libs/network.cpp`, commit 5e837a9beac82c031868eaad9348d6a48245fb99) treats 0 as not implemented (`EXIT_NOT_IMPLEMENTED`) and returns no value. `sceSslInit` with a pool size of 0 returns the kernel `EINVAL` on a PS5 (system software 13.40, see the [libSceSsl connection entry](libSceSsl.md)); that is a neighbouring call, not a measurement of libhttp. Which result is right is not decided. Twenty-nine of the 53 local titles import `sceHttpInit`. - [sceHttpDbgShowConnectionStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the native diagnostic consumes no arguments; it prints library state. The throwing implementation remains unchanged. - [sceHttpDbgShowRequestStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the native diagnostic consumes no arguments; it prints library state. The throwing implementation remains unchanged. - [sceHttpDbgShowMemoryPoolStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the native diagnostic consumes no arguments; it prints library state. The throwing implementation remains unchanged. From 75a8c57ba471ded1d9bb6a7f3937a7b2b3070824 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Sat, 31 Oct 2026 12:29:04 -0400 Subject: [PATCH 144/612] fix(agc): reset the import OOM streak after other allocation errors --- .../Graphics/src/GuestBufferMemory.cpp | 1 + .../libSceAgcDriver/tests/BdaResources.cpp | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index dc2d09ce9b..31ec0945e5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -2203,6 +2203,7 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import const bool exhausted = result == VK_ERROR_OUT_OF_HOST_MEMORY || result == VK_ERROR_OUT_OF_DEVICE_MEMORY; if (std::string_view(step) == "vkAllocateMemory") { state.allocationFailed = true; + if (!exhausted) state.allocationFailures = 0; if (exhausted && (++state.allocationFailures >= 3 || state.liveBytes + bytes + budget / 8 >= budget)) { state.ceiling = std::max(state.liveBytes, 1); std::fprintf(stderr, "[gpu] host import budget lowered to the %.1f MiB live after vkAllocateMemory failed %s\n", state.liveBytes / 1048576.0, state.allocationFailures >= 3 ? "3 times in a row" : "within an eighth of the budget"); diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp index a00cc6e740..494dd80683 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp @@ -1968,6 +1968,48 @@ void importAllocationErrorTests(const Context& context, VkResult failure, bool n ::operator delete(block, std::align_val_t{piece}); } +void importMixedAllocationErrorTests(const Context& context) { + constexpr std::size_t piece = 65536; + constexpr std::size_t count = 16; + auto* block = static_cast(::operator new(2 * count * piece, std::align_val_t{piece})); + auto importing = context; + importing.hostImportAlignment = piece; + ImportAllocationProbe probe(importing); + SetHostImportWindow(0); + ClearHostImports(importing.device); + { + GuestAllocations::Mutation mutation; + for (std::size_t index = 0; index < count; ++index) mutation.Add(block + 2 * index * piece, piece, true, true, true); + } + SetHostImportBudget(64 * piece); + std::size_t next = 0; + const auto lookup = [&] { return HostImportFor(importing, reinterpret_cast(block + 2 * next++ * piece), 16) != nullptr; }; + const auto counted = [] { return HostImportCounters().requesters[static_cast(ImportRequester::Access)]; }; + Require(lookup(), "the mixed allocation error test's first import was not made"); + const auto before = counted(); + for (const auto failure : {VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_INVALID_EXTERNAL_HANDLE, VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_UNKNOWN, VK_ERROR_OUT_OF_HOST_MEMORY}) { + probe.failure = failure; + Require(!lookup(), "an import with an injected mixed allocation failure was made"); + } + const auto after = counted(); + Require(after.failed - before.failed == 6 && after.refused == before.refused, "interrupted OOM failures lowered the import ceiling"); + probe.failure = VK_SUCCESS; + Require(lookup(), "nonconsecutive OOM failures prevented a later successful import"); + for (const auto failure : {VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_OUT_OF_HOST_MEMORY}) { + probe.failure = failure; + Require(!lookup(), "an import with an injected consecutive OOM was made"); + } + probe.failure = VK_SUCCESS; + Require(!lookup(), "three consecutive OOM failures did not lower the import ceiling"); + { + GuestAllocations::Mutation mutation; + for (std::size_t index = 0; index < count; ++index) mutation.Remove(block + 2 * index * piece); + } + ClearHostImports(importing.device); + SetHostImportBudget(0); + ::operator delete(block, std::align_val_t{piece}); +} + void importCeilingTests(const Context& context, const BdaTestAccess& access, bool nearBudget) { constexpr std::size_t piece = 65536; constexpr std::size_t count = 24; @@ -4581,6 +4623,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { evictingRefusalJoinTests(context); evictingRefusalStepDownTests(context); importDeviceBudgetTests(); + importMixedAllocationErrorTests(context); importCeilingTests(context, access, false); importCeilingTests(context, access, true); for (const auto failure : {VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_INVALID_EXTERNAL_HANDLE, VK_ERROR_UNKNOWN}) { From c0bf83452721f2f9facd043ef278c7df5f47a00e Mon Sep 17 00:00:00 2001 From: Daniel Horn <73882221+danielhorngt@users.noreply.github.com> Date: Sat, 31 Oct 2026 17:01:48 +0000 Subject: [PATCH 145/612] feat(libSceIcu): implement locale_getKeywordsStart Return the first '@' of a locale ID, or null when it has none, as ICU 68.2 does. The firmware 3.20 module is built from ICU 68: the inventory NID BufsAiaX0+s is the hash of the icu_68 BreakIterator symbol. ICU 77 moved this function to std::string_view, so the const char* signature is the one that applies. Record that later firmware is unverified. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceIcu/CMakeLists.txt | 14 ++++++ core/libs/prx/libSceIcu/Export.cpp | 4 -- core/libs/prx/libSceIcu/Identified.hpp | 2 +- core/libs/prx/libSceIcu/Locale.cpp | 8 ++++ core/libs/prx/libSceIcu/tests/Locale.cpp | 59 ++++++++++++++++++++++++ docs/dev/debt/libSceIcu.md | 6 ++- 6 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceIcu/Locale.cpp create mode 100644 core/libs/prx/libSceIcu/tests/Locale.cpp diff --git a/core/libs/prx/libSceIcu/CMakeLists.txt b/core/libs/prx/libSceIcu/CMakeLists.txt index 0bbdfb93f1..3d96fd1f70 100644 --- a/core/libs/prx/libSceIcu/CMakeLists.txt +++ b/core/libs/prx/libSceIcu/CMakeLists.txt @@ -8,6 +8,20 @@ target_sources(libSceIcu PRIVATE CompareIter.cpp) target_sources(libSceIcu PRIVATE Space.cpp) target_sources(libSceIcu PRIVATE Utf8.cpp) target_sources(libSceIcu PRIVATE Resource.cpp) +target_sources(libSceIcu PRIVATE Locale.cpp) + +if(BUILD_TESTING) + add_test_executable(guest_icu_locale_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Locale.cpp) + target_include_directories(guest_icu_locale_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_locale_tests PRIVATE libSceIcu) + configure_windows_unwind(guest_icu_locale_tests) + add_test(NAME guest_icu_locale COMMAND guest_icu_locale_tests) + set_tests_properties(guest_icu_locale PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_icu_locale PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() if(BUILD_TESTING) add_test_executable(guest_icu_utf8_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuUtf8.cpp) diff --git a/core/libs/prx/libSceIcu/Export.cpp b/core/libs/prx/libSceIcu/Export.cpp index bf098b424a..5048626eeb 100644 --- a/core/libs/prx/libSceIcu/Export.cpp +++ b/core/libs/prx/libSceIcu/Export.cpp @@ -45,10 +45,6 @@ int APS5_VABI UCNV_TO_U_CALLBACK_SUBSTITUTE_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI locale_getKeywordsStart_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI res_countArrayItems_nid_postfix() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceIcu/Identified.hpp b/core/libs/prx/libSceIcu/Identified.hpp index 0be2f37e55..a5929da370 100644 --- a/core/libs/prx/libSceIcu/Identified.hpp +++ b/core/libs/prx/libSceIcu/Identified.hpp @@ -7,7 +7,7 @@ int APS5_VABI UCNV_FROM_U_CALLBACK_ESCAPE_nid_postfix(); int APS5_VABI UCNV_FROM_U_CALLBACK_SUBSTITUTE_nid_postfix(); int APS5_VABI UCNV_TO_U_CALLBACK_ESCAPE_nid_postfix(); int APS5_VABI UCNV_TO_U_CALLBACK_SUBSTITUTE_nid_postfix(); -int APS5_VABI locale_getKeywordsStart_nid_postfix(); +const char* APS5_VABI locale_getKeywordsStart_nid_postfix(const char* localeID); int APS5_VABI res_countArrayItems_nid_postfix(); int APS5_VABI res_getAlias_nid_postfix(); int APS5_VABI res_getArrayItem_nid_postfix(); diff --git a/core/libs/prx/libSceIcu/Locale.cpp b/core/libs/prx/libSceIcu/Locale.cpp new file mode 100644 index 0000000000..bc951aa45a --- /dev/null +++ b/core/libs/prx/libSceIcu/Locale.cpp @@ -0,0 +1,8 @@ +#include "prx/libSceIcu/Identified.hpp" +#include "prx/libc/include/General.hpp" +#include + +extern "C" const char* APS5_VABI locale_getKeywordsStart_nid_postfix(const char* localeID) { + if (!localeID) APS5_INVALID_ARG_EX; + return std::strchr(localeID, '@'); +} diff --git a/core/libs/prx/libSceIcu/tests/Locale.cpp b/core/libs/prx/libSceIcu/tests/Locale.cpp new file mode 100644 index 0000000000..cc20c099d3 --- /dev/null +++ b/core/libs/prx/libSceIcu/tests/Locale.cpp @@ -0,0 +1,59 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include +#include +#include +#include + +static_assert(std::is_same_v); + +namespace { + +void Check(const char* localeID, const char* expected) { + const char* actual = locale_getKeywordsStart_nid_postfix(localeID); + if (actual != expected) { + std::fprintf(stderr, "locale \"%s\": expected offset %td, got %td\n", localeID, expected - localeID, actual - localeID); + std::abort(); + } +} + +void CheckNullThrows() { + try { + locale_getKeywordsStart_nid_postfix(nullptr); + } catch (const std::invalid_argument&) { + return; + } + std::fputs("a null locale did not throw\n", stderr); + std::abort(); +} + +} + +int main() { + const char keywords[] = "de_DE@collation=phonebook;currency=EUR"; + Check(keywords, keywords + 5); + const char twoSigns[] = "en@a=b@c=d"; + Check(twoSigns, twoSigns + 2); + const char leading[] = "@calendar=buddhist"; + Check(leading, leading); + const char trailing[] = "sr_Latn_RS@"; + Check(trailing, trailing + 10); + const char highBytes[] = "\xff\x80@x=y"; + Check(highBytes, highBytes + 2); + const char pastTerminator[] = "fr\0@x=y"; + Check(pastTerminator, nullptr); + Check("", nullptr); + Check("en_US_POSIX", nullptr); + Check("ja_JP;calendar=japanese", nullptr); + + for (const std::size_t position : {0u, 1u, 4095u, 65535u}) { + std::string locale(65536, 'a'); + locale[position] = '@'; + Check(locale.c_str(), locale.c_str() + position); + } + const std::string noKeywords(65536, 'a'); + Check(noKeywords.c_str(), nullptr); + + CheckNullThrows(); + std::puts("ICU locale keyword tests passed"); +} diff --git a/docs/dev/debt/libSceIcu.md b/docs/dev/debt/libSceIcu.md index 8b1b49669f..54c6a47427 100644 --- a/docs/dev/debt/libSceIcu.md +++ b/docs/dev/debt/libSceIcu.md @@ -37,7 +37,9 @@ ### Known export names; ABI prototypes unverified -- The 93 `sceIcuUnknown_*_nid_no_patch` and named `*_nid_postfix` stubs in [Export.cpp](../../../core/libs/prx/libSceIcu/Export.cpp) are present in the PS5 3.20 export inventory from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). Their prototypes and behavior are unverified. Each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. -- The 73 named stubs declared in [Identified.hpp](../../../core/libs/prx/libSceIcu/Identified.hpp) carry the ICU C API names whose `tools/nid_names.py` hash equals the inventory NID; the names were taken from the identifiers of the ICU 78.1 `common`, `i18n` and `io` sources. A name match does not show that the console function has ICU's prototype or behavior. Most of them read Unicode property, case-folding or resource data that no host library provides. +- The 92 `sceIcuUnknown_*_nid_no_patch` and named `*_nid_postfix` stubs in [Export.cpp](../../../core/libs/prx/libSceIcu/Export.cpp) are present in the PS5 3.20 export inventory from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). Their prototypes and behavior are unverified. Each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. +- The 72 named stubs declared in [Identified.hpp](../../../core/libs/prx/libSceIcu/Identified.hpp) carry the ICU C API names whose `tools/nid_names.py` hash equals the inventory NID; the names were taken from the identifiers of the ICU 78.1 `common`, `i18n` and `io` sources. A name match does not show that the console function has ICU's prototype or behavior. Most of them read Unicode property, case-folding or resource data that no host library provides. - [Resource type classification](../../../core/libs/prx/libSceIcu/Resource.cpp) has no console ABI or behavior verification. `res_getPublicType` (NID `WPrktCwd+iw` in the PS5 3.20 inventory above) takes an unsigned 32-bit resource and returns a signed 32-bit public type, following ICU 65.1 and [ICU 78.1](https://github.com/unicode-org/icu/blob/049e0d6a420629ac7db77256987d083a563287b5/icu4c/source/common/uresdata.cpp). Both use the same mapping of the high four bits; the low 28 bits do not affect the result. Cross-firmware export availability and native behavior remain unverified. Closes when: compared with the console. + +- [Locale keyword search](../../../core/libs/prx/libSceIcu/Locale.cpp) has no console ABI or behavior verification. `locale_getKeywordsStart` (NID `wlK146pHnQ4` in the PS5 3.20 inventory above) takes a `const char*` and returns the first `@` or null, following [ICU 68.2](https://github.com/unicode-org/icu/blob/release-68-2/icu4c/source/common/uloc.cpp): the 3.20 inventory NID `BufsAiaX0+s` is the hash of the `icu_68` `BreakIterator::createCharacterInstance` symbol. ICU 77 and later take a `std::string_view`, so a module built from those would accept a string that is not terminated; which ICU version later firmware uses is unverified. ICU dereferences a null locale, which throws here. Closes when: compared with the console. From e4e041f6225758efaa10c8aae8dee95b3f56f0fd Mon Sep 17 00:00:00 2001 From: akkadbakkad89 Date: Sat, 31 Oct 2026 22:36:15 +0530 Subject: [PATCH 146/612] docs(libSceAppContent): record store-country evidence limits Split the entry under the length warning, add the module SHA-256 in full, state that no 13.60 body comparison exists (the inventory check is symbol presence only), and describe GET_STORE_COUNTRY as the chosen host fallback for an absent service rather than a measured result. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- docs/dev/debt/libSceAppContent.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceAppContent.md b/docs/dev/debt/libSceAppContent.md index 969273f4e6..d511282b57 100644 --- a/docs/dev/debt/libSceAppContent.md +++ b/docs/dev/debt/libSceAppContent.md @@ -18,7 +18,8 @@ - [libSceAppContent pending exports](../../../core/libs/prx/libSceAppContent/Export.cpp) - 8 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAppContent.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. ### Functional -- [sceAppContentGetDownloadedStoreCountry](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - the firmware 4.03 `libSceAppContent.sprx` body is a thin RPC client: initialization guard, null-parameter checks, an entitlement-label padding gate at SDK 0x01500000, then a vtable call marshalling a system-service request (selector `0x20005`, request `{serviceLabel}`, a 0x14-byte label input and a 0x18-byte reply buffer) and translating the reply status into the `0x80d9xxxx` family; its translator passes `0x80d91000` GET_STORE_COUNTRY through unchanged. The export signature is thunk-verified (`uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, void* storeCountry`; the former `()` declaration was an inventory placeholder). No system service exists on host, so after the verified checks it answers `GET_STORE_COUNTRY`, the dedicated error the firmware translator reserves for this query; the real service replies and the 0x18-byte reply layout are unverified. +- [sceAppContentGetDownloadedStoreCountry](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - the firmware 4.03 `libSceAppContent.sprx` body (SHA-256 `36039805f77483b32861b7936ddcd859708b764682984a4625cb7f8d1efdabc4`) is a thin RPC client: initialization guard, null-parameter checks, an entitlement-label padding gate at SDK 0x01500000, then a vtable call marshalling a system-service request (selector `0x20005`, `{serviceLabel}` + a 0x14-byte label input + a 0x18-byte reply buffer) and translating the reply status into the `0x80d9xxxx` family; the translator passes `0x80d91000` GET_STORE_COUNTRY through unchanged. + - The export signature is thunk-verified (`uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, void* storeCountry`); the body was not compared with 13.60 — its export inventory establishes symbol presence only. With no host system service the export answers `GET_STORE_COUNTRY` after the verified checks as the chosen fallback for an absent service, not a measured native result; the real service replies and the 0x18-byte reply layout are unverified. - [Initialization](../../../core/libs/prx/libSceAppContent/Export.cpp) - service readiness and boot attributes are not modeled; successful initialization returns a zero boot record. Initialization-state guards, SDK 0x01500000 repeated-initialization errors and reserved-field checks are recovered. SDK-dependent service-error translation remains incomplete. - [Additional content](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out; `sceAppContentGetAddcontInfo` answers `DRM_NO_ENTITLEMENT` for every label and `sceAppContentGetAddcontInfoList` reports 0 entries; `sceAppContentAddcontEnqueueDownload` answers `PARAMETER` for a null label and `NOT_FOUND` otherwise, since no download is ever queued; `sceAppContentAddcontDelete` answers `PARAMETER` for a null label and `NOT_FOUND` otherwise, since no additional content is ever installed. `sceAppContentGetAddcontDownloadProgress` answers `PARAMETER` for a null label or progress and `NOT_FOUND` otherwise, as [dlc_emu](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.cpp#L2328-L2341) does for a label that is not installed additional content, and never writes the progress. Its 16-byte layout (`DataSize`, then `DownloadedSize`, both 64-bit) follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/AppContent/AppContent.cs#L45-L54) and is unused here. The signatures of `sceAppContentAddcontEnqueueDownload` and `sceAppContentGetAddcontDownloadProgress` follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/AppContent/AppContent.cs#L140-L148), and the signature of `sceAppContentAddcontDelete` follows [dlc_emu](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.h#L91-L92); dlc_emu is not a console measurement and no console source gives the error codes, so the signatures and the error codes are unverified From e1cbe062424d2a1bebea2ace9fa5822f7541e2c8 Mon Sep 17 00:00:00 2001 From: xXPBSXx <20614131+xXPBSXx@users.noreply.github.com> Date: Sat, 31 Oct 2026 18:43:40 +0100 Subject: [PATCH 147/612] test(libSceSysmodule): compare the whole set of accepted module ids Co-Authored-By: Claude Sonnet 5.5 --- core/libs/tests/GuestSysmodule.cpp | 33 ++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/core/libs/tests/GuestSysmodule.cpp b/core/libs/tests/GuestSysmodule.cpp index 814bae392d..3e2f3b81e4 100644 --- a/core/libs/tests/GuestSysmodule.cpp +++ b/core/libs/tests/GuestSysmodule.cpp @@ -3,10 +3,12 @@ #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceSysmodule/ModuleTable.hpp" +#include #include #include #include #include +#include #include #include #ifdef _WIN32 @@ -261,23 +263,38 @@ void CheckUnwindInfo() { } void CheckAcceptedIdSet() { - constexpr std::size_t kAcceptedCount = 141; + constexpr std::uint16_t kAccepted[] = { + 0x0005, 0x0006, 0x0007, 0x000b, 0x0017, 0x0019, 0x001a, 0x001b, 0x001c, 0x001e, 0x0021, 0x002c, + 0x0038, 0x0081, 0x0083, 0x0084, 0x0085, 0x0088, 0x008a, 0x008b, 0x008c, 0x008d, 0x0095, 0x0096, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009f, 0x00a0, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00b4, 0x00b6, 0x00b7, 0x00b9, 0x00ba, 0x00bc, + 0x00bd, 0x00be, 0x00c0, 0x00c1, 0x00c2, 0x00c4, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00cc, 0x00cd, + 0x00ce, 0x00cf, 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d8, 0x00da, 0x00de, + 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e7, 0x00e9, 0x00ee, 0x00ef, 0x00f0, 0x00f1, 0x00fa, + 0x00fb, 0x00fe, 0x00ff, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x010a, 0x010b, 0x010c, 0x010d, + 0x010e, 0x010f, 0x0110, 0x0111, 0x0112, 0x0113, 0x0114, 0x0115, 0x0116, 0x0117, 0x011a, 0x011b, + 0x011c, 0x011d, 0x011e, 0x011f, 0x0120, 0x0121, 0x0122, 0x0123, 0x0124, 0x0125, 0x0126, 0x0127, + 0x0128, 0x0129, 0x012a, 0x012b, 0x012c, 0x012d, 0x012e, 0x012f, 0x0130, 0x0132, 0x0133, 0x0134, + 0x0135, 0x0136, 0x0137, 0x0138, 0x0139, 0x013a, 0x013b, 0x013c, 0x013d, + }; + static_assert(sizeof(kAccepted) / sizeof(kAccepted[0]) == 141); constexpr std::uint16_t kAcceptedWithoutName[] = { 0x0111, 0x0117, 0x011c, 0x011d, 0x011e, 0x011f, 0x0121, 0x0122, 0x0123, 0x0124, 0x0129, 0x012c, 0x012d, 0x012e, 0x0133, 0x0134, 0x0136, 0x013a, 0x013b, 0x013c, 0x013d, }; - std::size_t accepted = 0; + const auto accepted = [&kAccepted](std::uint16_t id) { + return std::binary_search(std::begin(kAccepted), std::end(kAccepted), id); + }; for (std::uint32_t id = 0; id <= 0xffff; ++id) { const std::uint16_t value = static_cast(id); - if (sceSysmoduleIsLoaded(value) != kInvalidValue) { - ++accepted; - continue; + Require((sceSysmoduleIsLoaded(value) != kInvalidValue) == accepted(value)); + if (!accepted(value)) { + Require(sceSysmoduleLoadModule(value) == kInvalidValue); + Require(sceSysmoduleUnloadModule(value) == kInvalidValue); } - Require(sceSysmoduleLoadModule(value) == kInvalidValue); - Require(sceSysmoduleUnloadModule(value) == kInvalidValue); } - Require(accepted == kAcceptedCount); for (const std::uint16_t id : kAcceptedWithoutName) { + Require(accepted(id)); Require(kModuleTable.find(id) == kModuleTable.end()); Require(sceSysmoduleIsLoaded(id) == 0); Require(sceSysmoduleLoadModule(id) == 0); From 75e7b6424c8419983bf8c76f74cfbd811eccd084 Mon Sep 17 00:00:00 2001 From: frankea Date: Sat, 31 Oct 2026 13:57:51 -0400 Subject: [PATCH 148/612] feat(shader): translate ds_swizzle_b32 FFT mode Offsets 0xe000-0xffff follow the DS_SWIZZLE_B32 pseudocode of the RDNA 2 (12.13.1) and RDNA 4 ISA guides instead of throwing in the decoder. Rotate mode (0xc000-0xdfff) is unchanged. --- .../libSceAgcDriver/tests/ShaderMemory.cpp | 37 +++++ .../tests/execution/DsSwizzle.cpp | 146 +++++++++++++++++- .../RdnaDecoder/src/RdnaMemoryOpDecoder.cpp | 3 - .../src/SpirvMemory/SpirvFormatConvert.cpp | 8 + docs/dev/debt/shader.md | 1 + 5 files changed, 187 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 0f76b563f7..de304c9402 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -8175,6 +8175,42 @@ void verifyUnusedUnnormalizedSampler() { require(msaaArrayAllocation.bindings.size() == 1u && msaaArrayAllocation.bindings[0].imageShape == DescriptorImageShape::Image2DMultisampleArray, "multisampled array image resource did not receive Image2DMultisampleArray descriptor shape"); } +void verifyDsSwizzleFftMode() { + using namespace ShaderRecompiler; + static constexpr std::array capabilities{spv::CapabilityShader, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformShuffle}; + static constexpr std::array extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"}; + static std::array output{}; + const auto address = static_cast(reinterpret_cast(output.data())); + const std::array userData{static_cast(address), static_cast((address >> 32u) & 0xffffu), 128u, 0x31016facu}; + const auto compiled = [&userData](std::uint32_t offset) { + const std::vector code{0xd8d40000u | offset, 0x01000000u, 0x34040082u, 0xbf8cc07fu, 0xe0701000u, 0x80000102u, 0xbf810000u}; + RecompileRequest request{}; + request.shader = {ShaderStage::Compute, 0x26000u, code, 0, {}}; + request.context.waveSize = 32; + request.context.userDataBaseRegister = 0; + request.context.userData = userData; + request.context.compute = ShaderComputeStageInfo{{32u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + request.target.vulkanVersion = 0x00401000u; + request.target.spirvVersion = 0x00010300u; + request.target.subgroupSize = 32u; + request.target.supportedCapabilities = capabilities; + request.target.supportedExtensions = extensions; + request.target.bdaAbiVersion = BdaAbi::Version; + request.layout.pushConstantSizeBytes = 128; + request.useCache = false; + return Recompile(request).spirv.Copy(); + }; + const auto reversesLanes = [](std::span spirv) { + for (std::size_t cursor = 5; cursor < spirv.size() && (spirv[cursor] >> 16u) != 0u; cursor += spirv[cursor] >> 16u) { + if ((spirv[cursor] & 0xffffu) == spv::OpBitReverse) return true; + } + return false; + }; + for (const auto offset : {0xe000u, 0xe010u, 0xe018u, 0xe001u}) require(reversesLanes(compiled(offset)), "ds_swizzle FFT mode: an offset with a mask below 0x1f did not reverse the lane bits"); + for (const auto offset : {0xe01fu, 0xc000u, 0xc021u, 0xdfffu}) require(!reversesLanes(compiled(offset)), "ds_swizzle FFT mode: mask 0x1f or a rotate offset reversed the lane bits"); + require(compiled(0xffe0u) == compiled(0xe000u) && compiled(0xf550u) == compiled(0xe010u), "ds_swizzle FFT mode: offset bits 12:5 changed the swizzle"); +} + void verifyWaveUniformValues() { using namespace ShaderRecompiler; IrProgram program; @@ -9759,6 +9795,7 @@ int main(int argc, char** argv) { verifyUnnormalizedSamplers(); verifyUnusedUnnormalizedSampler(); verifyNullVolumeUnnormalizedSampler(); + verifyDsSwizzleFftMode(); verifyWaveUniformValues(); verifyTwoLaneUniformValues(); verifyBdaReadFallbackFunctions(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp index bfae3c11cd..7d1ded3cd2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp @@ -51,6 +51,34 @@ alignas(256) constexpr std::array Wave64Code{ 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, }; +alignas(256) constexpr std::array FftWave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xd8d4e000, 0x0a000004, 0xd8d4e010, + 0x0b000004, 0xd8d4e01f, 0x0c000004, 0xd8d4e018, 0x0d000004, 0xd8d4e01c, 0x0e000004, 0xd8d4e01e, + 0x0f000004, 0xd8d4e001, 0x10000004, 0xd8d4e00f, 0x11000004, 0xd8d4e015, 0x12000004, 0xd8d4e008, + 0x13000004, 0xd8d4ffe0, 0x14000004, 0xd8d4f550, 0x15000004, 0xbefe03ff, 0x33cc0ff3, 0xd8d4e000, + 0x16000004, 0xd8d4e010, 0x17000004, 0xd8d4e018, 0x18000004, 0xd8d4e001, 0x19000004, 0xbf8cc07f, + 0xbefe03c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +alignas(256) constexpr std::array FftWave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xd8d4e000, 0x0a000004, 0xd8d4e010, + 0x0b000004, 0xd8d4e01f, 0x0c000004, 0xd8d4e018, 0x0d000004, 0xd8d4e01c, 0x0e000004, 0xd8d4e01e, + 0x0f000004, 0xd8d4e001, 0x10000004, 0xd8d4e00f, 0x11000004, 0xd8d4e015, 0x12000004, 0xd8d4e008, + 0x13000004, 0xd8d4ffe0, 0x14000004, 0xd8d4f550, 0x15000004, 0xbefe03ff, 0x33cc0ff3, 0xbeff03ff, + 0xc3a5960f, 0xd8d4e000, 0x16000004, 0xd8d4e010, 0x17000004, 0xd8d4e018, 0x18000004, 0xd8d4e001, + 0x19000004, 0xbf8cc07f, 0xbefe04c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, + 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + constexpr std::uint32_t Rows[64][4] = { {0x1e7ea419u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x51c9bc70u, 0x00000000u, 0x00000000u, 0x00000000u}, @@ -219,7 +247,109 @@ constexpr std::uint32_t Expected64[64][16] = { {0x89be9c1cu, 0xbef7e268u, 0x16f44881u, 0x1c593af5u, 0x8eb5140fu, 0x16f44881u, 0x16f44881u, 0x1c593af5u, 0x57241955u, 0x8eb5140fu, 0x8eb5140fu, 0xd0944996u, 0xbef7e268u, 0x00000000u, 0x00000000u, 0x00000000u} }; +constexpr std::uint32_t FftExpected32[32][16] = { + {0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u}, + {0xc88b2875u, 0x1a466884u, 0x51c9bc70u, 0x8306d03bu, 0x80a4df5au, 0x51c9bc70u, 0xe255accbu, 0x51c9bc70u, 0xf38b2ffcu, 0x1a466884u, 0xc88b2875u, 0x1a466884u, 0x00000000u, 0x1a466884u, 0x8306d03bu, 0xe255accbu}, + {0x1a466884u, 0x8306d03bu, 0x80a4df5au, 0x80a4df5au, 0x51c9bc70u, 0x80a4df5au, 0x8306d03bu, 0x80a4df5au, 0x51c9bc70u, 0x8306d03bu, 0x1a466884u, 0x8306d03bu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc0433cbdu, 0x99dd251du, 0xf38b2ffcu, 0xdc28ff90u, 0xf38b2ffcu, 0xf38b2ffcu, 0x9f199504u, 0xf38b2ffcu, 0xf38b2ffcu, 0x99dd251du, 0xc0433cbdu, 0x99dd251du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x51c9bc70u, 0x8306d03bu, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x00000000u, 0x51c9bc70u, 0x00000000u}, + {0xbb049a79u, 0x39292d22u, 0xa5aec797u, 0xa5aec797u, 0xdc28ff90u, 0xa5aec797u, 0xe5121482u, 0xa5aec797u, 0xf3f49249u, 0x39292d22u, 0xbb049a79u, 0x39292d22u, 0x00000000u, 0x39292d22u, 0xa5aec797u, 0xe5121482u}, + {0x99dd251du, 0xdc28ff90u, 0xdc28ff90u, 0xf38b2ffcu, 0xa5aec797u, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0xa5aec797u, 0xdc28ff90u, 0x99dd251du, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u}, + {0x3d6c51e3u, 0x8e7aa6e9u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0x6bad6be2u, 0xf3f49249u, 0xf3f49249u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x00000000u, 0xf3f49249u, 0x00000000u}, + {0x80a4df5au, 0x51c9bc70u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x51c9bc70u, 0x1a466884u, 0x1e7ea419u, 0xe255accbu, 0x80a4df5au, 0x51c9bc70u, 0x00000000u, 0x51c9bc70u, 0x1a466884u, 0x51c9bc70u}, + {0x8c3d5f16u, 0xe255accbu, 0xe255accbu, 0x99dd251du, 0x39292d22u, 0xe255accbu, 0xe255accbu, 0xe255accbu, 0xf38b2ffcu, 0xe255accbu, 0x8c3d5f16u, 0xe255accbu, 0x8c3d5f16u, 0xe255accbu, 0x00000000u, 0xe255accbu}, + {0x39292d22u, 0xa5aec797u, 0x39292d22u, 0x39292d22u, 0xe255accbu, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0x51c9bc70u, 0x9f199504u, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0xa5aec797u}, + {0x96263ae6u, 0x9f199504u, 0xe5121482u, 0x8e7aa6e9u, 0xe5121482u, 0xe5121482u, 0x9f199504u, 0xe5121482u, 0xf38b2ffcu, 0x9f199504u, 0x96263ae6u, 0x9f199504u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xdc28ff90u, 0xf38b2ffcu, 0x99dd251du, 0xe255accbu, 0x99dd251du, 0x99dd251du, 0xf38b2ffcu, 0x99dd251du, 0x8306d03bu, 0xe5121482u, 0xdc28ff90u, 0xf38b2ffcu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc4a334bfu, 0xe5121482u, 0x9f199504u, 0x9f199504u, 0x8e7aa6e9u, 0x9f199504u, 0xe5121482u, 0x9f199504u, 0xf3f49249u, 0xe5121482u, 0xc4a334bfu, 0xe5121482u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0xf3f49249u, 0x8e7aa6e9u, 0xe5121482u, 0x9f199504u, 0x8e7aa6e9u, 0xf3f49249u, 0x8e7aa6e9u, 0xa5aec797u, 0x6bad6be2u, 0x8e7aa6e9u, 0xf3f49249u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9d3c7decu, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0xf3f49249u, 0x6bad6be2u, 0x9d3c7decu, 0x6bad6be2u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x51c9bc70u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0x1e7ea419u, 0x1e7ea419u, 0xc88b2875u, 0x1e7ea419u, 0x51c9bc70u, 0xc88b2875u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9293de8fu, 0xc0433cbdu, 0x9293de8fu, 0xbb049a79u, 0x8c3d5f16u, 0x9293de8fu, 0xe255accbu, 0x51c9bc70u, 0xd7a7a3ccu, 0x1a466884u, 0x9293de8fu, 0xc0433cbdu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xe255accbu, 0xbb049a79u, 0x8c3d5f16u, 0x8c3d5f16u, 0x9293de8fu, 0x8c3d5f16u, 0x8306d03bu, 0x80a4df5au, 0x9293de8fu, 0x8306d03bu, 0xe255accbu, 0xbb049a79u, 0xe255accbu, 0x00000000u, 0x8c3d5f16u, 0x8306d03bu}, + {0xc5e818fau, 0x3d6c51e3u, 0xd7a7a3ccu, 0xc4a334bfu, 0xd7a7a3ccu, 0xd7a7a3ccu, 0x9f199504u, 0xf38b2ffcu, 0xd7a7a3ccu, 0x99dd251du, 0xc5e818fau, 0x3d6c51e3u, 0xc5e818fau, 0x3d6c51e3u, 0xc4a334bfu, 0x00000000u}, + {0xa5aec797u, 0x8c3d5f16u, 0xbb049a79u, 0x9293de8fu, 0xbb049a79u, 0xbb049a79u, 0x80a4df5au, 0x8306d03bu, 0xbb049a79u, 0x80a4df5au, 0xa5aec797u, 0x8c3d5f16u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc6cd75e9u, 0x96263ae6u, 0xc6cd75e9u, 0xc6cd75e9u, 0xc4a334bfu, 0xc6cd75e9u, 0xe5121482u, 0xa5aec797u, 0x7dabe929u, 0x39292d22u, 0xc6cd75e9u, 0x96263ae6u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9f199504u, 0xc4a334bfu, 0xc4a334bfu, 0xd7a7a3ccu, 0xc6cd75e9u, 0xc4a334bfu, 0xdc28ff90u, 0xdc28ff90u, 0xc6cd75e9u, 0xdc28ff90u, 0x9f199504u, 0xc4a334bfu, 0x00000000u, 0xc4a334bfu, 0xd7a7a3ccu, 0xdc28ff90u}, + {0x00a61f93u, 0x9d3c7decu, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u, 0x6bad6be2u, 0xf3f49249u, 0x7dabe929u, 0x8e7aa6e9u, 0x00a61f93u, 0x9d3c7decu, 0x00a61f93u, 0x00000000u, 0x7dabe929u, 0x00000000u}, + {0xf38b2ffcu, 0x9293de8fu, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu, 0x51c9bc70u, 0x1a466884u, 0xc88b2875u, 0xe255accbu, 0xf38b2ffcu, 0x9293de8fu, 0x00000000u, 0x00000000u, 0xc0433cbdu, 0x51c9bc70u}, + {0xd7a7a3ccu, 0xc5e818fau, 0xc5e818fau, 0x3d6c51e3u, 0x96263ae6u, 0xc5e818fau, 0xe255accbu, 0xe255accbu, 0xd7a7a3ccu, 0xe255accbu, 0xd7a7a3ccu, 0xc5e818fau, 0xd7a7a3ccu, 0xc5e818fau, 0x3d6c51e3u, 0xe255accbu}, + {0xe5121482u, 0xc6cd75e9u, 0x96263ae6u, 0x96263ae6u, 0xc5e818fau, 0x96263ae6u, 0xa5aec797u, 0x39292d22u, 0x9293de8fu, 0x9f199504u, 0xe5121482u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x70eb9a0au, 0x00a61f93u, 0x70eb9a0au, 0x9d3c7decu, 0x70eb9a0au, 0x70eb9a0au, 0x9f199504u, 0xe5121482u, 0xd7a7a3ccu, 0x9f199504u, 0x70eb9a0au, 0x00a61f93u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xf3f49249u, 0xd7a7a3ccu, 0x3d6c51e3u, 0xc5e818fau, 0x3d6c51e3u, 0x3d6c51e3u, 0xf38b2ffcu, 0x99dd251du, 0xbb049a79u, 0xe5121482u, 0xf3f49249u, 0xd7a7a3ccu, 0xf3f49249u, 0xd7a7a3ccu, 0xc5e818fau, 0x00000000u}, + {0x7dabe929u, 0x70eb9a0au, 0x00a61f93u, 0x00a61f93u, 0x9d3c7decu, 0x00a61f93u, 0xe5121482u, 0x9f199504u, 0x7dabe929u, 0xe5121482u, 0x7dabe929u, 0x70eb9a0au, 0x7dabe929u, 0x00000000u, 0x00a61f93u, 0xe5121482u}, + {0x6bad6be2u, 0x7dabe929u, 0x9d3c7decu, 0x70eb9a0au, 0x00a61f93u, 0x9d3c7decu, 0xf3f49249u, 0x8e7aa6e9u, 0xc6cd75e9u, 0x6bad6be2u, 0x6bad6be2u, 0x7dabe929u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x6bad6be2u, 0x6bad6be2u, 0x7dabe929u, 0x6bad6be2u, 0x14aa4e71u, 0x14aa4e71u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +constexpr std::uint32_t FftExpected64[64][16] = { + {0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u}, + {0xc88b2875u, 0x1a466884u, 0x51c9bc70u, 0x8306d03bu, 0x80a4df5au, 0x51c9bc70u, 0xe255accbu, 0x51c9bc70u, 0xf38b2ffcu, 0x1a466884u, 0xc88b2875u, 0x1a466884u, 0x00000000u, 0x1a466884u, 0x8306d03bu, 0xe255accbu}, + {0x1a466884u, 0x8306d03bu, 0x80a4df5au, 0x80a4df5au, 0x51c9bc70u, 0x80a4df5au, 0x8306d03bu, 0x80a4df5au, 0x51c9bc70u, 0x8306d03bu, 0x1a466884u, 0x8306d03bu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc0433cbdu, 0x99dd251du, 0xf38b2ffcu, 0xdc28ff90u, 0xf38b2ffcu, 0xf38b2ffcu, 0x9f199504u, 0xf38b2ffcu, 0xf38b2ffcu, 0x99dd251du, 0xc0433cbdu, 0x99dd251du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x51c9bc70u, 0x8306d03bu, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x00000000u, 0x51c9bc70u, 0x00000000u}, + {0xbb049a79u, 0x39292d22u, 0xa5aec797u, 0xa5aec797u, 0xdc28ff90u, 0xa5aec797u, 0xe5121482u, 0xa5aec797u, 0xf3f49249u, 0x39292d22u, 0xbb049a79u, 0x39292d22u, 0x00000000u, 0x39292d22u, 0xa5aec797u, 0xe5121482u}, + {0x99dd251du, 0xdc28ff90u, 0xdc28ff90u, 0xf38b2ffcu, 0xa5aec797u, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0xa5aec797u, 0xdc28ff90u, 0x99dd251du, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u}, + {0x3d6c51e3u, 0x8e7aa6e9u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0x6bad6be2u, 0xf3f49249u, 0xf3f49249u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x00000000u, 0xf3f49249u, 0x00000000u}, + {0x80a4df5au, 0x51c9bc70u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x51c9bc70u, 0x1a466884u, 0x1e7ea419u, 0xe255accbu, 0x80a4df5au, 0x51c9bc70u, 0x00000000u, 0x51c9bc70u, 0x1a466884u, 0x51c9bc70u}, + {0x8c3d5f16u, 0xe255accbu, 0xe255accbu, 0x99dd251du, 0x39292d22u, 0xe255accbu, 0xe255accbu, 0xe255accbu, 0xf38b2ffcu, 0xe255accbu, 0x8c3d5f16u, 0xe255accbu, 0x8c3d5f16u, 0xe255accbu, 0x00000000u, 0xe255accbu}, + {0x39292d22u, 0xa5aec797u, 0x39292d22u, 0x39292d22u, 0xe255accbu, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0x51c9bc70u, 0x9f199504u, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0xa5aec797u}, + {0x96263ae6u, 0x9f199504u, 0xe5121482u, 0x8e7aa6e9u, 0xe5121482u, 0xe5121482u, 0x9f199504u, 0xe5121482u, 0xf38b2ffcu, 0x9f199504u, 0x96263ae6u, 0x9f199504u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xdc28ff90u, 0xf38b2ffcu, 0x99dd251du, 0xe255accbu, 0x99dd251du, 0x99dd251du, 0xf38b2ffcu, 0x99dd251du, 0x8306d03bu, 0xe5121482u, 0xdc28ff90u, 0xf38b2ffcu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc4a334bfu, 0xe5121482u, 0x9f199504u, 0x9f199504u, 0x8e7aa6e9u, 0x9f199504u, 0xe5121482u, 0x9f199504u, 0xf3f49249u, 0xe5121482u, 0xc4a334bfu, 0xe5121482u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0xf3f49249u, 0x8e7aa6e9u, 0xe5121482u, 0x9f199504u, 0x8e7aa6e9u, 0xf3f49249u, 0x8e7aa6e9u, 0xa5aec797u, 0x6bad6be2u, 0x8e7aa6e9u, 0xf3f49249u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9d3c7decu, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0xf3f49249u, 0x6bad6be2u, 0x9d3c7decu, 0x6bad6be2u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x51c9bc70u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0x1e7ea419u, 0x1e7ea419u, 0xc88b2875u, 0x1e7ea419u, 0x51c9bc70u, 0xc88b2875u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9293de8fu, 0xc0433cbdu, 0x9293de8fu, 0xbb049a79u, 0x8c3d5f16u, 0x9293de8fu, 0xe255accbu, 0x51c9bc70u, 0xd7a7a3ccu, 0x1a466884u, 0x9293de8fu, 0xc0433cbdu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xe255accbu, 0xbb049a79u, 0x8c3d5f16u, 0x8c3d5f16u, 0x9293de8fu, 0x8c3d5f16u, 0x8306d03bu, 0x80a4df5au, 0x9293de8fu, 0x8306d03bu, 0xe255accbu, 0xbb049a79u, 0xe255accbu, 0x00000000u, 0x8c3d5f16u, 0x8306d03bu}, + {0xc5e818fau, 0x3d6c51e3u, 0xd7a7a3ccu, 0xc4a334bfu, 0xd7a7a3ccu, 0xd7a7a3ccu, 0x9f199504u, 0xf38b2ffcu, 0xd7a7a3ccu, 0x99dd251du, 0xc5e818fau, 0x3d6c51e3u, 0xc5e818fau, 0x3d6c51e3u, 0xc4a334bfu, 0x00000000u}, + {0xa5aec797u, 0x8c3d5f16u, 0xbb049a79u, 0x9293de8fu, 0xbb049a79u, 0xbb049a79u, 0x80a4df5au, 0x8306d03bu, 0xbb049a79u, 0x80a4df5au, 0xa5aec797u, 0x8c3d5f16u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc6cd75e9u, 0x96263ae6u, 0xc6cd75e9u, 0xc6cd75e9u, 0xc4a334bfu, 0xc6cd75e9u, 0xe5121482u, 0xa5aec797u, 0x7dabe929u, 0x39292d22u, 0xc6cd75e9u, 0x96263ae6u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9f199504u, 0xc4a334bfu, 0xc4a334bfu, 0xd7a7a3ccu, 0xc6cd75e9u, 0xc4a334bfu, 0xdc28ff90u, 0xdc28ff90u, 0xc6cd75e9u, 0xdc28ff90u, 0x9f199504u, 0xc4a334bfu, 0x00000000u, 0xc4a334bfu, 0xd7a7a3ccu, 0xdc28ff90u}, + {0x00a61f93u, 0x9d3c7decu, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u, 0x6bad6be2u, 0xf3f49249u, 0x7dabe929u, 0x8e7aa6e9u, 0x00a61f93u, 0x9d3c7decu, 0x00a61f93u, 0x00000000u, 0x7dabe929u, 0x00000000u}, + {0xf38b2ffcu, 0x9293de8fu, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu, 0x51c9bc70u, 0x1a466884u, 0xc88b2875u, 0xe255accbu, 0xf38b2ffcu, 0x9293de8fu, 0x00000000u, 0x00000000u, 0xc0433cbdu, 0x51c9bc70u}, + {0xd7a7a3ccu, 0xc5e818fau, 0xc5e818fau, 0x3d6c51e3u, 0x96263ae6u, 0xc5e818fau, 0xe255accbu, 0xe255accbu, 0xd7a7a3ccu, 0xe255accbu, 0xd7a7a3ccu, 0xc5e818fau, 0xd7a7a3ccu, 0xc5e818fau, 0x3d6c51e3u, 0xe255accbu}, + {0xe5121482u, 0xc6cd75e9u, 0x96263ae6u, 0x96263ae6u, 0xc5e818fau, 0x96263ae6u, 0xa5aec797u, 0x39292d22u, 0x9293de8fu, 0x9f199504u, 0xe5121482u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x70eb9a0au, 0x00a61f93u, 0x70eb9a0au, 0x9d3c7decu, 0x70eb9a0au, 0x70eb9a0au, 0x9f199504u, 0xe5121482u, 0xd7a7a3ccu, 0x9f199504u, 0x70eb9a0au, 0x00a61f93u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xf3f49249u, 0xd7a7a3ccu, 0x3d6c51e3u, 0xc5e818fau, 0x3d6c51e3u, 0x3d6c51e3u, 0xf38b2ffcu, 0x99dd251du, 0xbb049a79u, 0xe5121482u, 0xf3f49249u, 0xd7a7a3ccu, 0xf3f49249u, 0xd7a7a3ccu, 0xc5e818fau, 0x00000000u}, + {0x7dabe929u, 0x70eb9a0au, 0x00a61f93u, 0x00a61f93u, 0x9d3c7decu, 0x00a61f93u, 0xe5121482u, 0x9f199504u, 0x7dabe929u, 0xe5121482u, 0x7dabe929u, 0x70eb9a0au, 0x7dabe929u, 0x00000000u, 0x00a61f93u, 0xe5121482u}, + {0x6bad6be2u, 0x7dabe929u, 0x9d3c7decu, 0x70eb9a0au, 0x00a61f93u, 0x9d3c7decu, 0xf3f49249u, 0x8e7aa6e9u, 0xc6cd75e9u, 0x6bad6be2u, 0x6bad6be2u, 0x7dabe929u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x6bad6be2u, 0x6bad6be2u, 0x7dabe929u, 0x6bad6be2u, 0x14aa4e71u, 0x14aa4e71u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u}, + {0xfadb8908u, 0xaf67d461u, 0x49889310u, 0x731c9452u, 0xd11745adu, 0x49889310u, 0x7d836e77u, 0x49889310u, 0x1919e93au, 0xaf67d461u, 0xfadb8908u, 0xaf67d461u, 0xfadb8908u, 0x00000000u, 0x00000000u, 0x7d836e77u}, + {0xaf67d461u, 0x731c9452u, 0xd11745adu, 0xd11745adu, 0x49889310u, 0xd11745adu, 0x731c9452u, 0xd11745adu, 0x49889310u, 0x731c9452u, 0xaf67d461u, 0x731c9452u, 0x00000000u, 0x00000000u, 0xd11745adu, 0x00000000u}, + {0x837a7935u, 0x35d14880u, 0x1919e93au, 0xd0944996u, 0x1919e93au, 0x1919e93au, 0x65b00a2du, 0x1919e93au, 0x1919e93au, 0x35d14880u, 0x837a7935u, 0x35d14880u, 0x837a7935u, 0x35d14880u, 0x00000000u, 0x00000000u}, + {0x731c9452u, 0xd11745adu, 0x731c9452u, 0x49889310u, 0x731c9452u, 0x731c9452u, 0xd11745adu, 0x731c9452u, 0x731c9452u, 0xd11745adu, 0x731c9452u, 0xd11745adu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xce1d62e0u, 0xadec6f11u, 0x02f0ee99u, 0x02f0ee99u, 0xd0944996u, 0x02f0ee99u, 0x5071950eu, 0x02f0ee99u, 0xe4163207u, 0xadec6f11u, 0xce1d62e0u, 0xadec6f11u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x35d14880u, 0xd0944996u, 0xd0944996u, 0x1919e93au, 0x02f0ee99u, 0xd0944996u, 0xd0944996u, 0xd0944996u, 0x02f0ee99u, 0xd0944996u, 0x35d14880u, 0xd0944996u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x57241955u, 0x406329bcu, 0xe4163207u, 0xe4163207u, 0xe4163207u, 0xe4163207u, 0x59001ac9u, 0xe4163207u, 0xe4163207u, 0x406329bcu, 0x57241955u, 0x406329bcu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xd11745adu, 0x49889310u, 0xaf67d461u, 0xaf67d461u, 0xaf67d461u, 0xaf67d461u, 0x49889310u, 0xaf67d461u, 0x1c593af5u, 0x7d836e77u, 0xd11745adu, 0x49889310u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xede7bd0cu, 0x7d836e77u, 0x7d836e77u, 0x35d14880u, 0xadec6f11u, 0x7d836e77u, 0x7d836e77u, 0x7d836e77u, 0x1919e93au, 0x7d836e77u, 0xede7bd0cu, 0x7d836e77u, 0xede7bd0cu, 0x7d836e77u, 0x35d14880u, 0x7d836e77u}, + {0xadec6f11u, 0x02f0ee99u, 0xadec6f11u, 0xadec6f11u, 0x7d836e77u, 0xadec6f11u, 0x02f0ee99u, 0xadec6f11u, 0x49889310u, 0x65b00a2du, 0xadec6f11u, 0x02f0ee99u, 0xadec6f11u, 0x00000000u, 0xadec6f11u, 0x00000000u}, + {0x1389b372u, 0x65b00a2du, 0x5071950eu, 0x406329bcu, 0x5071950eu, 0x5071950eu, 0x65b00a2du, 0x5071950eu, 0x1919e93au, 0x65b00a2du, 0x1389b372u, 0x65b00a2du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xd0944996u, 0x1919e93au, 0x35d14880u, 0x7d836e77u, 0x35d14880u, 0x35d14880u, 0x1919e93au, 0x35d14880u, 0x731c9452u, 0x5071950eu, 0xd0944996u, 0x1919e93au, 0x00000000u, 0x1919e93au, 0x7d836e77u, 0x1919e93au}, + {0xffe976abu, 0x5071950eu, 0x65b00a2du, 0x65b00a2du, 0x406329bcu, 0x65b00a2du, 0x5071950eu, 0x65b00a2du, 0xe4163207u, 0x5071950eu, 0xffe976abu, 0x5071950eu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x406329bcu, 0xe4163207u, 0x406329bcu, 0x5071950eu, 0x65b00a2du, 0x406329bcu, 0xe4163207u, 0x406329bcu, 0x02f0ee99u, 0x59001ac9u, 0x406329bcu, 0xe4163207u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8eb5140fu, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u, 0xe4163207u, 0x59001ac9u, 0x8eb5140fu, 0x59001ac9u, 0x8eb5140fu, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u}, + {0x49889310u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0x1c593af5u, 0x1c593af5u, 0xfadb8908u, 0x1c593af5u, 0x49889310u, 0xfadb8908u, 0x49889310u, 0xfadb8908u, 0xfadb8908u, 0x1c593af5u}, + {0xffb88309u, 0x837a7935u, 0xffb88309u, 0xce1d62e0u, 0xede7bd0cu, 0xffb88309u, 0x7d836e77u, 0x49889310u, 0x5b4c8012u, 0xaf67d461u, 0xffb88309u, 0x837a7935u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x7d836e77u, 0xce1d62e0u, 0xede7bd0cu, 0xede7bd0cu, 0xffb88309u, 0xede7bd0cu, 0x731c9452u, 0xd11745adu, 0xffb88309u, 0x731c9452u, 0x7d836e77u, 0xce1d62e0u, 0x7d836e77u, 0x00000000u, 0xede7bd0cu, 0x00000000u}, + {0xa341738cu, 0x57241955u, 0x5b4c8012u, 0xffe976abu, 0x5b4c8012u, 0x5b4c8012u, 0x65b00a2du, 0x1919e93au, 0x5b4c8012u, 0x35d14880u, 0xa341738cu, 0x57241955u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x02f0ee99u, 0xede7bd0cu, 0xce1d62e0u, 0xffb88309u, 0xce1d62e0u, 0xce1d62e0u, 0xd11745adu, 0x731c9452u, 0xce1d62e0u, 0xd11745adu, 0x02f0ee99u, 0xede7bd0cu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x60581ccau, 0x1389b372u, 0x60581ccau, 0x60581ccau, 0xffe976abu, 0x60581ccau, 0x5071950eu, 0x02f0ee99u, 0xbef7e268u, 0xadec6f11u, 0x60581ccau, 0x1389b372u, 0x60581ccau, 0x00000000u, 0x60581ccau, 0x00000000u}, + {0x65b00a2du, 0xffe976abu, 0xffe976abu, 0x5b4c8012u, 0x60581ccau, 0xffe976abu, 0xd0944996u, 0xd0944996u, 0x60581ccau, 0xd0944996u, 0x65b00a2du, 0xffe976abu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x16f44881u, 0x8eb5140fu, 0xbef7e268u, 0xbef7e268u, 0xbef7e268u, 0xbef7e268u, 0x59001ac9u, 0xe4163207u, 0xbef7e268u, 0x406329bcu, 0x16f44881u, 0x8eb5140fu, 0x00000000u, 0x8eb5140fu, 0xbef7e268u, 0x59001ac9u}, + {0x1919e93au, 0xffb88309u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0x49889310u, 0xaf67d461u, 0xfadb8908u, 0x7d836e77u, 0x1919e93au, 0xffb88309u, 0x1919e93au, 0x00000000u, 0x837a7935u, 0x49889310u}, + {0x5b4c8012u, 0xa341738cu, 0xa341738cu, 0x57241955u, 0x1389b372u, 0xa341738cu, 0x7d836e77u, 0x7d836e77u, 0x5b4c8012u, 0x7d836e77u, 0x5b4c8012u, 0xa341738cu, 0x00000000u, 0xa341738cu, 0x00000000u, 0x7d836e77u}, + {0x5071950eu, 0x60581ccau, 0x1389b372u, 0x1389b372u, 0xa341738cu, 0x1389b372u, 0x02f0ee99u, 0xadec6f11u, 0xffb88309u, 0x65b00a2du, 0x5071950eu, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb91dddd9u, 0x16f44881u, 0xb91dddd9u, 0x8eb5140fu, 0xb91dddd9u, 0xb91dddd9u, 0x65b00a2du, 0x5071950eu, 0x5b4c8012u, 0x65b00a2du, 0xb91dddd9u, 0x16f44881u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xe4163207u, 0x5b4c8012u, 0x57241955u, 0xa341738cu, 0x57241955u, 0x57241955u, 0x1919e93au, 0x35d14880u, 0xce1d62e0u, 0x5071950eu, 0xe4163207u, 0x5b4c8012u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xbef7e268u, 0xb91dddd9u, 0x16f44881u, 0x16f44881u, 0x8eb5140fu, 0x16f44881u, 0x5071950eu, 0x65b00a2du, 0xbef7e268u, 0x5071950eu, 0xbef7e268u, 0xb91dddd9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x59001ac9u, 0xbef7e268u, 0x8eb5140fu, 0xb91dddd9u, 0x16f44881u, 0x8eb5140fu, 0xe4163207u, 0x406329bcu, 0x60581ccau, 0x59001ac9u, 0x59001ac9u, 0xbef7e268u, 0x59001ac9u, 0xbef7e268u, 0x00000000u, 0x00000000u}, + {0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x59001ac9u, 0x59001ac9u, 0xbef7e268u, 0x59001ac9u, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x59001ac9u} +}; + constexpr const char* Names[Checked] = {"bitmask_identity", "bitmask_swap8", "bitmask_broadcast", "bitmask_reverse", "bitmask_mixed", "quad_perm", "quad_perm_high_bits", "rotate_left_1", "rotate_right_3", "rotate_left_31", "rotate_masked", "rotate_high_bits", "inactive_swap8", "inactive_quad_perm", "inactive_rotate", "inactive_broadcast"}; +constexpr const char* FftNames[Checked] = {"fft_00", "fft_10", "fft_1f", "fft_18", "fft_1c", "fft_1e", "fft_01", "fft_0f", "fft_15", "fft_08", "fft_00_high_bits", "fft_10_high_bits", "inactive_fft_00", "inactive_fft_10", "inactive_fft_18", "inactive_fft_01"}; std::array BufferDescriptor(const void* data, std::uint32_t bytes) { const auto address = reinterpret_cast(data); @@ -255,11 +385,11 @@ void Run(AgcDriver::VulkanDevice& device, std::span code, s } template -void Check(const std::uint32_t (&expected)[Lanes][Checked]) { +void Check(const char* const (&names)[Checked], const std::uint32_t (&expected)[Lanes][Checked]) { for (std::uint32_t tid = 0; tid < Lanes; ++tid) { for (std::uint32_t index = 0; index < Checked; ++index) { const auto actual = Output[tid * Results + index]; - Require(actual == expected[tid][index], "ds_swizzle wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + Require(actual == expected[tid][index], "ds_swizzle wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); } } } @@ -275,11 +405,17 @@ int main() { return VulkanTestSkipped; } Run(*device, Wave32Code, 32, device->ComputeTarget(32)); - Check(Expected32); + Check(Names, Expected32); + Run(*device, FftWave32Code, 32, device->ComputeTarget(32)); + Check(FftNames, FftExpected32); Run(*device, Wave64Code, 64, device->Target()); - Check(Expected64); + Check(Names, Expected64); + Run(*device, FftWave64Code, 64, device->Target()); + Check(FftNames, FftExpected64); Run(*device, Wave64Code, 64, device->ComputeTarget(32)); - Check(Expected64); + Check(Names, Expected64); + Run(*device, FftWave64Code, 64, device->ComputeTarget(32)); + Check(FftNames, FftExpected64); std::puts("ds_swizzle tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index c1e192ce0b..9a8b23cd5c 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -991,9 +991,6 @@ RdnaInstruction DecodeRdnaDs(std::uint32_t programCounter, std::span= 0xE000u) { - throw std::runtime_error("DS swizzle FFT mode is not supported"); - } if (gds && (info.opcode == RdnaOpcode::DsSwizzleB32 || info.opcode == RdnaOpcode::DsBpermuteB32 || info.opcode == RdnaOpcode::DsPermuteB32 || info.opcode == RdnaOpcode::DsWriteAddtidB32 || info.opcode == RdnaOpcode::DsReadAddtidB32)) { throw std::runtime_error("DS lane operation is available only for LDS"); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvFormatConvert.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvFormatConvert.cpp index c2497d5f54..9fb96b6406 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvFormatConvert.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvFormatConvert.cpp @@ -345,6 +345,14 @@ void EmitDeviceAtomicMemoryBarrier(SpirvEmitterState& state) { } std::uint32_t EmitDsSwizzleTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t control) { + if ((control & 0xe000u) == 0xe000u) { + const std::uint32_t mask = control & 0x1fu; + const auto shift = 27u + static_cast(std::popcount(mask)); + const auto kept = EmitAndConstant(state, subid, mask | 0xffffffe0u); + if (shift >= 32u) return kept; + const auto reversed = EmitBinaryU32(state, spv::OpShiftRightLogical, Unary(state, spv::OpBitReverse, TypeU32(state), subid), ConstantU32(state, shift)); + return EmitOrU32(state, kept, reversed); + } if ((control & 0xc000u) == 0xc000u) { const std::uint32_t mask = control & 0x1fu; const std::uint32_t rotate = (control >> 5u) & 0x1fu; diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index 868c60274f..d3ec1cdc3f 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -19,6 +19,7 @@ - [Packed pixel ancillary word](../../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) (shader recompiler) - the sample index at bits 8-11 and the render target index at bits 16-28 follow #295. The sample index is the SampleId builtin in a pixel shader the guest iterates per sample (`PS_ITER_SAMPLE` on a multisampled draw), an equality inferred, not measured. In any other pixel shader it is 0, the iterated sample number of a pixel the hardware does not iterate (also inferred), so reading it does not make the host shade per sample. The render target index is the Layer builtin when the draw exports it (a layered draw, `PA_CL_VS_OUT_CNTL` bit 18; the pipeline specialization constant `LayerExported`) and 0 otherwise, the first layer Vulkan renders when no pre-rasterization stage writes Layer. Reading the unwritten Layer builtin instead makes AMD's Windows driver drop the fragment's writes (#4800); with the constant at 0 the load is folded away. Mesa reads both fields from those bits, with the layer 14 bits wide only from GFX12 on ([ac_nir_lower_intrinsics_to_args.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/amd/common/nir/ac_nir_lower_intrinsics_to_args.c#L342-L368)). A value passed through a select, move or phi whose every read is an extract inside one of those fields is rebuilt from the two fields alone. Any other read the masked-select pass cannot drop gets the whole word: the primitive class at bits 0-1 (0 point, 1 line, 2 triangle, 3 rectangle), which the draw sets as a pipeline specialization constant ([RasterPrimitiveOf](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp)) in the shaders that read it, plus the two fields, with every other bit zero. That layout comes from the RDNA 3 and 3.5 ISA guides (prim type 1:0, VRS rates 3:2 and 5:4, eye id 7, sample 11:8, render target index 28:16). The GCN register guides give the same prim type and sample fields and put the render target index at 26:16, so the two sources agree for layers below 2048. AMD's LLPC reads the primitive class from bits 1:0 with the same encoding ([LowerGlCompatibility.cpp](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/llpc/lowering/LowerGlCompatibility.cpp#L830-L836)) and without a GFX-level check ([getPrimType](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/lgc/lowering/LowerInOut.cpp#L5307-L5315)). Nobody has checked it on gfx1013. In the whole word only bits 0-1, 8-11 and 16-28 have a source. Bits 2-7, 12-15 and 29-31 are assumed zero, not measured: the VRS rates (2-5) and the eye id (7) because the driver supports neither variable-rate shading nor multiview, so a title that enables either would read other bits on the console, and bits 6, 12-15 and 29-31 because no source gives them a meaning. The sample field reads SampleId only in iterated shaders. The whole word has the same render target index at bits 16-28 - [Front-face input with `FRONT_FACE_ALL_BITS`](../../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) (shader recompiler) - with `SPI_BARYC_CNTL.FRONT_FACE_ALL_BITS` set, a back face reads 0 and a front face 0xffffffff. Mesa 22.0 RADV sets the bit ([radv_pipeline.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-22.0.0/src/amd/vulkan/radv_pipeline.c#L5953)) and ACO tests the input with `v_cmp_lg_u32 0` ([aco_instruction_selection.cpp](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-22.0.0/src/amd/compiler/aco_instruction_selection.cpp#L8123-L8128)), so only back = 0 and front ≠ 0 are sourced; PPSA05684's two-sided cloth shaders test it with `v_cmp_lt_u32 0`. The exact front value is not measured. Without the bit the input is ±1.0, as current Mesa reads it (`fgt 0`, `fsign`; [ac_nir_lower_intrinsics_to_args.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/amd/common/nir/ac_nir_lower_intrinsics_to_args.c)) - [Color comparison descriptor forms](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp): for formats AnyPS5 can sample as floating-point from GFX10 codes 1–77, `image_sample_c*` returns 0 for every comparison function except codes 7 (`16_UNORM`) and 22 (`32_FLOAT`), which use the native comparison path. The split was measured with point sampling on RDNA2 (gfx1031 and gfx1036) and with point and bilinear sampling on RDNA3 (gfx1100); the PS5's gfx1013, cube comparisons and 2D-array comparisons of formats other than 1, 7, 22 and 56 remain unverified (on gfx1036, 1 and 56 return 0 for all eight functions with DIM 2D_ARRAY on 2D-array and 2D T#s). Codes 128–130 (8-bit sRGB) also return 0, measured on gfx1035 over references -0.5..1.5 and texel codes 0..255. Scaled, integer and formats requiring converted sampling remain unsupported, although the tested hardware formats also return 0 for them. Anisotropic comparison of the remaining formats has not been measured. +- [ds_swizzle_b32 FFT mode](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvFormatConvert.cpp) (shader recompiler) - offsets 0xe000-0xffff are not measured on hardware. Lane `i` reads lane `reverse_bits(i & 0x1f) >> count_ones(mask) | (i & mask) | (i & 0x20)` with `mask = offset[4:0]`, the DS_SWIZZLE_B32 pseudocode of AMD's RDNA 2 (section 12.13.1) and RDNA 4 ISA guides. Their examples cover only masks 0x00, 0x10 and 0x1f, which make `reverse_bits` a 5-bit reversal; every other mask, including those that give two lanes one source, and the ignored offset bits 12:5 follow the pseudocode alone, pinned in [DsSwizzle.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp) - [s_setkill](../../../core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp) (core/shader/recompiler) - decoded as `s_endpgm` for any immediate: measured on RDNA2 hardware (gfx1035), a store before it is visible and nothing after it runs ([#771](https://github.com/boykopovar/AnyPS5/pull/771)); whether the KILL status bit it sets is observable differently from `s_endpgm` is unverified - [s_sethalt](../../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) (core/shader/recompiler) - with SIMM16[0] clear it is a no-op: the RDNA 2 ISA guide describes that value as resuming a halted wave, and a wave that executes it is not halted; measured on a gfx1035 (RDNA 2) wave: `s_sethalt 0` followed by a VALU op completes and gives the normal result. Only SIMM16[0] is read; with it set the instruction throws - [s_subvector_loop_begin and s_subvector_loop_end](../../../core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp) (core/shader/recompiler) - follow AMD's RDNA 2 instruction set reference and a gfx1036 (wave64) measurement, pinned in [SubvectorLoop.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/SubvectorLoop.cpp); `S0` in the end instruction's `elif(S0 == 0)` is read as its SGPR operand. A wave32 shader and a destination other than an SGPR throw because neither is measured. The begin instruction tests for an empty mask as `(exec_lo | exec_hi) == 0`: AMD's Windows driver (32.0.21045.5002 and 32.0.21042.62) compiled `exec_lo == 0 && exec_hi == 0` as a test of the whole 64-bit mask when guest loads use selects, as they do with `robustBufferAccess2` (#7376, #7623). Measured on PC RDNA2 (Radeon 610M), not on a PS5. The [specialization pass](../../../core/shader/recompiler/SpirvBackend/src/SpirvSpecialization.cpp) keeps a constant-condition branch whose untaken target is a loop header, since folding it removes the loop's back edge, which SPIR-V requires exactly once per loop header (SPIRV-Tools rejects the module and the NVIDIA driver 615.78.08 crashes compiling it); a loop whose back-edge block becomes unreachable in another way is not handled From f18a18ee4508e0e602806ea8432ea3e8f714409a Mon Sep 17 00:00:00 2001 From: Jamie <55252868+chitters2004@users.noreply.github.com> Date: Sat, 31 Oct 2026 18:20:13 +0000 Subject: [PATCH 149/612] fix(agc): shut the driver down before frame replay exits with an error Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp index 0ddce118db..fcd1fa665a 100644 --- a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp @@ -785,13 +785,16 @@ int main(int argc, char** argv) { int code = 2; try { code = run(options); - if (code == 0) { - AgcDriverShutdown_nid_postfix(); - return 0; - } } catch (const std::exception& error) { std::fprintf(stderr, "agc_frame_replay: %s\n", error.what()); } + try { + AgcDriverShutdown_nid_postfix(); + } catch (const std::exception& error) { + std::fprintf(stderr, "agc_frame_replay: %s\n", error.what()); + if (code == 0) code = 2; + } + if (code == 0) return 0; std::fflush(stdout); std::fflush(stderr); std::_Exit(code); From 812b91f65b7f0cbfbcf4c9c3b66825c7d22e4182 Mon Sep 17 00:00:00 2001 From: slopgobblercobbler Date: Sat, 31 Oct 2026 20:14:11 +0100 Subject: [PATCH 150/612] fix(libSceAgcDriver): validate PM4 index base and indirect draw count address bounds --- core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp | 8 ++++++-- core/libs/prx/libSceAgcDriver/tests/Pm4.cpp | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index a4e136fdcf..effc1be399 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -312,7 +312,11 @@ void Validate(std::span packet, std::uint32_t queue) { } case 0x3f: size(4); break; case 0x13: case 0x2f: graphics(); size(2); break; - case 0x26: graphics(); size(3); break; + case 0x26: + graphics(); + size(3); + require((packet[1] & 1u) == 0 && packet[2] <= 0xffffu, "unsupported index base alignment or address bits"); + break; case 0x27: graphics(); size(6); @@ -354,7 +358,7 @@ void Validate(std::span packet, std::uint32_t queue) { require((packet[2] >> 16u) == 0 && (packet[3] >> 16u) == 0, "start-index location semantics are not implemented"); require(drawLocation(packet[2]) && drawLocation(packet[3]) && drawLocation(packet[4] & 0xffffu), "invalid indirect draw register location"); require((packet[4] & ~(0xffffu | (1u << 30u) | (1u << 31u))) == 0, "indirect multi-draw control bits are not implemented"); - if ((packet[4] & (1u << 30u)) != 0) require((packet[6] & 3u) == 0 && address(packet[6], packet[7]) != 0, "invalid indirect draw count address"); + if ((packet[4] & (1u << 30u)) != 0) require((packet[6] & 3u) == 0 && address(packet[6], packet[7]) != 0 && packet[7] <= 0xffffu, "invalid indirect draw count address"); else require(address(packet[6], packet[7]) == 0, "count address without an indirect draw count"); require((packet[8] & 3u) == 0 && packet[8] >= (opcode == 0x2c ? 16u : 20u), "invalid indirect draw stride"); require((packet[9] & ~0x20u) == (opcode == 0x2c ? 2u : 0u), "unsupported indirect draw initiator"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index cd642d38e9..9dae0a7244 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -256,6 +256,8 @@ void testContextAndBases() { AgcDriver::Pm4::Validate(makePacket(0x16, {0, 0x49}), 0); expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x15, {1, 1, 1, 0x51}), 0); }, "dispatch modifiers"); expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x16, {0, 0x51}), 0); }, "indirect dispatch modifiers"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x26, {0x1001, 1}), 0); }, "unsupported index base alignment or address bits"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x26, {0x1000, 0x10000}), 0); }, "unsupported index base alignment or address bits"); execute(state, makePacket(0x13, {32})); execute(state, makePacket(0x26, {0x1000, 1})); execute(state, makePacket(0x2a, {1})); @@ -447,6 +449,8 @@ void testIndirectDraw() { const auto counted = AgcDriver::Pm4::ResolveDraw(makePacket(0x2c, {0, 0x280, 0x280, 0x280 | (1u << 30u), 5, low(&countValue), high(&countValue), 16, 2}), state); check(counted.indirect->countIndirect && counted.indirect->count == 5 && counted.indirect->countAddress == reinterpret_cast(&countValue) && AgcDriver::Pm4::ReadDrawCount(*counted.indirect) == 2, "indirect draw count mismatch"); expectFailure([&] { AgcDriver::Pm4::ReadDrawCount(*local.indirect); }, "count address"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x2c, {0, 0x280, 0x280, 0x280 | (1u << 30u), 5, low(&countValue), 0x10000, 16, 2}), 0); }, "invalid indirect draw count address"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x38, {0, 0x280, 0x280, 0x280 | (1u << 30u), 5, low(&countValue), 0x10000, 20, 0}), 0); }, "invalid indirect draw count address"); } bool sameDraw(const AgcDriver::Pm4::DrawParameters& left, const AgcDriver::Pm4::DrawParameters& right) { From 659a6eecb4b4c82c7f76c808f0ce3833c07fe204 Mon Sep 17 00:00:00 2001 From: Jamie <55252868+chitters2004@users.noreply.github.com> Date: Sat, 31 Oct 2026 19:38:41 +0000 Subject: [PATCH 151/612] fix(agc): keep replay frames alive until the driver has stopped Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp index fcd1fa665a..08bcbb38b1 100644 --- a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp @@ -305,7 +305,7 @@ std::pair progress() { struct Replay { const Options& options; const FrameCaptureReader& reader; - Frames frames; + Frames& frames = *new Frames; std::map> outputs; std::mutex memoryMutex; std::map> groups; From 93123ef32c576bb642d3281521a77cea92b90a17 Mon Sep 17 00:00:00 2001 From: Jamie <55252868+chitters2004@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:02:48 +0000 Subject: [PATCH 152/612] test(agc): skip the frame replay tests without a Vulkan driver Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceAgcDriver/tests/FrameReplay.cpp | 2 ++ .../cmake/agc_driver_frame_replay_tests.cmake | 15 ++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp index 58f420fb60..2915200a3b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp @@ -1,3 +1,4 @@ +#include "execution/VulkanTestDevice.hpp" #include "prx/libSceAgc/DcbFlow/include/Memory.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/FrameCapture.hpp" @@ -331,6 +332,7 @@ int main(int argc, char** argv) { return 2; } try { + if (!OpenVulkanTestDevice()) return VulkanTestSkipped; if (argc == 3 && std::string(argv[2]) == "concurrent") testConcurrentCopies(argv[1]); else if (argc == 3 && std::string(argv[2]) == "reservation_error") testReservationError(argv[1]); else testCaptureAndReplay(argv[1], argc == 3); diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_tests.cmake index 2adff08812..ac0f421ff2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_tests.cmake @@ -1,7 +1,12 @@ if(BUILD_TESTING) if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux") add_test_executable(agc_driver_frame_replay_tests tests/FrameReplay.cpp Execution/src/FrameCapture.cpp) - target_include_directories(agc_driver_frame_replay_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_include_directories(agc_driver_frame_replay_tests PRIVATE + ${LIBS_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/core/shader/recompiler + ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR} + ${ANYPS5_SPIRV_HEADERS_INCLUDE_DIR} + ) target_link_libraries(agc_driver_frame_replay_tests PRIVATE libSceAgc libSceAgcDriver libkernel libc libzstd_static) set_target_properties(agc_driver_frame_replay_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_driver_frame_replay_tests) @@ -9,7 +14,7 @@ if(BUILD_TESTING) add_test(NAME agc_driver_frame_replay COMMAND agc_driver_frame_replay_tests $) set_tests_properties(agc_driver_frame_replay PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame replay capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2" - TIMEOUT 120) + TIMEOUT 120 SKIP_RETURN_CODE 77) if(WIN32) set_tests_properties(agc_driver_frame_replay PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") @@ -17,7 +22,7 @@ if(BUILD_TESTING) add_test(NAME agc_driver_frame_replay_cold_pipelines COMMAND agc_driver_frame_replay_tests $ pipelines) set_tests_properties(agc_driver_frame_replay_cold_pipelines PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=0;APS5_HEADLESS_PIPELINE_CACHE=1;ANYPS5_SHADER_CACHE_DIR=${CMAKE_CURRENT_BINARY_DIR}/frame replay pipeline cache;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame replay cold capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2" - TIMEOUT 30) + TIMEOUT 30 SKIP_RETURN_CODE 77) if(WIN32) set_tests_properties(agc_driver_frame_replay_cold_pipelines PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") @@ -25,7 +30,7 @@ if(BUILD_TESTING) add_test(NAME agc_driver_frame_replay_reservation_error COMMAND agc_driver_frame_replay_tests $ reservation_error) set_tests_properties(agc_driver_frame_replay_reservation_error PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame replay reservation error" - TIMEOUT 30) + TIMEOUT 30 SKIP_RETURN_CODE 77) if(WIN32) set_tests_properties(agc_driver_frame_replay_reservation_error PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") @@ -33,7 +38,7 @@ if(BUILD_TESTING) add_test(NAME agc_driver_frame_replay_concurrent COMMAND agc_driver_frame_replay_tests $ concurrent) set_tests_properties(agc_driver_frame_replay_concurrent PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-concurrent-capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=8" - TIMEOUT 120) + TIMEOUT 120 SKIP_RETURN_CODE 77) if(WIN32) set_tests_properties(agc_driver_frame_replay_concurrent PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") From 7c0784a081b42f91942dfbd07933ed65728b7148 Mon Sep 17 00:00:00 2001 From: Mohamed Malek Aifa <32399935+malekaifa@users.noreply.github.com> Date: Sat, 31 Oct 2026 21:29:15 +0100 Subject: [PATCH 153/612] fix(libSceAgcDriver): claim screenshot paths atomically --- .../Execution/src/Screenshot.cpp | 66 ++++++++++++++++--- .../prx/libSceAgcDriver/tests/Screenshot.cpp | 28 ++++++++ 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp index 9e60cbac8c..133f0ea8b7 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp @@ -3,14 +3,24 @@ #include "SDL_filesystem.h" #include "SDL_error.h" #include +#include +#include #include #include -#include #include #include #include #include +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#endif + namespace { std::atomic requested{false}; @@ -40,10 +50,52 @@ std::string localTimestamp() { return text; } -std::filesystem::path unusedPath(const std::filesystem::path& folder, const std::string& stem) { +int createExclusive(const std::filesystem::path& path) { +#ifdef _WIN32 + return ::_wopen(path.c_str(), _O_WRONLY | _O_CREAT | _O_EXCL | _O_BINARY | _O_NOINHERIT, _S_IREAD | _S_IWRITE); +#else + return ::open(path.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0666); +#endif +} + +bool writeAll(int descriptor, const std::uint8_t* data, std::size_t size) { + while (size != 0) { +#ifdef _WIN32 + const unsigned int chunk = size > INT_MAX ? INT_MAX : static_cast(size); + const int written = ::_write(descriptor, data, chunk); +#else + const auto written = ::write(descriptor, data, size); +#endif + if (written < 0 && errno == EINTR) continue; + if (written <= 0) return false; + data += written; + size -= static_cast(written); + } + return true; +} + +bool closeFile(int descriptor) { +#ifdef _WIN32 + return ::_close(descriptor) == 0; +#else + return ::close(descriptor) == 0; +#endif +} + +std::filesystem::path writeUnusedPng(const std::filesystem::path& folder, const std::string& stem, const std::vector& png) { for (int index = 0; index < 1000; ++index) { const auto path = folder / (index == 0 ? stem + ".png" : stem + "-" + std::to_string(index) + ".png"); - if (!std::filesystem::exists(path)) return path; + const int descriptor = createExclusive(path); + if (descriptor < 0) { + if (errno == EEXIST) continue; + throw std::runtime_error("Screenshot: cannot create output file"); + } + const bool written = writeAll(descriptor, png.data(), png.size()); + const bool closed = closeFile(descriptor); + if (written && closed) return path; + std::error_code error; + std::filesystem::remove(path, error); + throw std::runtime_error("Screenshot: cannot write output file"); } throw std::runtime_error("Screenshot: no unused file name for " + stem + " in " + folder.string()); } @@ -71,11 +123,5 @@ std::filesystem::path AgcDriver::Screenshot::Write(std::string_view titleId, std const auto png = Decoder::Png::Encode(rgb, width, height, 3); const auto folder = directory(); std::filesystem::create_directories(folder); - const auto path = unusedPath(folder, std::string(titleId) + "-" + localTimestamp()); - std::ofstream file; - file.exceptions(std::ios::failbit | std::ios::badbit); - file.open(path, std::ios::binary); - file.write(reinterpret_cast(png.data()), static_cast(png.size())); - file.close(); - return path; + return writeUnusedPng(folder, std::string(titleId) + "-" + localTimestamp(), png); } diff --git a/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp b/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp index 4adf0c824d..93956ced7e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp @@ -1,6 +1,8 @@ #include "prx/libSceAgcDriver/Execution/include/Screenshot.hpp" #include "Decoder/Png.hpp" +#include #include +#include #include #include #include @@ -11,6 +13,7 @@ #include #include #include +#include #include namespace { @@ -54,6 +57,30 @@ void writesRgbPng(const std::filesystem::path& directory) { require(image->pixels == expected, "the screenshot holds the frame's colors as opaque RGB"); } +void concurrentWritesClaimUniquePaths() { + constexpr std::size_t writerCount = 32; + constexpr int attempts = 20; + const std::array bgra{std::byte{0x10}, std::byte{0x20}, std::byte{0x30}, std::byte{0x00}}; + for (int attempt = 0; attempt < attempts; ++attempt) { + std::atomic ready{0}; + std::atomic start{false}; + std::array paths; + std::array writers; + for (std::size_t index = 0; index < writerCount; ++index) { + writers[index] = std::thread([&, index] { + ready.fetch_add(1, std::memory_order_release); + while (!start.load(std::memory_order_acquire)) std::this_thread::yield(); + paths[index] = AgcDriver::Screenshot::Write("RACE00001", 1, 1, bgra); + }); + } + while (ready.load(std::memory_order_acquire) != writerCount) std::this_thread::yield(); + start.store(true, std::memory_order_release); + for (auto& writer : writers) writer.join(); + std::sort(paths.begin(), paths.end()); + require(std::adjacent_find(paths.begin(), paths.end()) == paths.end(), "concurrent screenshots claim unique paths"); + } +} + void rejectsInvalidFrames() { const std::array pixel{}; const auto rejects = [](auto write) { @@ -78,6 +105,7 @@ int main() try { setScreenshotDirectory(directory); requestsMerge(); writesRgbPng(directory); + concurrentWritesClaimUniquePaths(); rejectsInvalidFrames(); std::filesystem::remove_all(root); std::cout << "agc screenshot tests passed\n"; From 5646297aebe7ac50b72d647b4944cdfdbd73f9a3 Mon Sep 17 00:00:00 2001 From: Malek Date: Sat, 31 Oct 2026 21:37:39 +0100 Subject: [PATCH 154/612] fix(hw-oracle): keep extra offset stable across dispatches --- docs/dev/HW_ORACLE.md | 9 +++++---- tools/hw-oracle/hw_oracle.py | 5 ++++- tools/hw-oracle/test_hw_oracle.py | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/docs/dev/HW_ORACLE.md b/docs/dev/HW_ORACLE.md index 01583cdc19..2dc837a567 100644 --- a/docs/dev/HW_ORACLE.md +++ b/docs/dev/HW_ORACLE.md @@ -42,10 +42,11 @@ elsewhere). `HW_ORACLE_HIP` and `HW_ORACLE_COMGR` name other DLLs. `ROCR_VISIBLE - Leave `v0` (lane id), `v1` (input offset), `v2` (output offset) and `s[4:7]` (input and output addresses) unchanged. - 4 KiB of LDS, or the group segment size `--lds` (Python `lds`) gives in bytes, up to 64 KiB. Rows run in workgroups of up to 1024 lanes, padded to whole waves with zero rows. -- Bytes passed as `extra` follow the rows of each dispatch, at `s[4:5] + 16 * rows`: buffer contents, texels, etc. - Build buffer and image descriptors in SGPRs from that address. A kernel that needs more than 4 input dwords per lane - takes the rest from there too, for example `v_lshlrev_b32 v40, 3, v0` / `v_add_nc_u32 v40, 16 * rows, v40` / - `global_load_dwordx2 v[8:9], v40, s[4:5]` for two more dwords per lane, with `rows` the padded count. +- Bytes passed as `extra` start at `s[4:5] + 16 * extra_rows`, where `extra_rows` is the padded row count for one + dispatch and the dispatch capacity for multiple dispatches (1024 in wave32, 512 in wave64). Tail dispatches reserve + unused row slots so the address stays fixed. Build buffer and image descriptors in SGPRs from that address. A kernel + that needs more than 4 input dwords per lane takes the rest from there too, for example `v_lshlrev_b32 v40, 3, v0` / + `v_add_nc_u32 v40, 16 * extra_rows, v40` / `global_load_dwordx2 v[8:9], v40, s[4:5]`. Every run must explicitly set all floating-point controls, including runs of integer instructions. The CLI flags use hyphens; Python keywords use underscores. Record these settings alongside the GPU and measured results. diff --git a/tools/hw-oracle/hw_oracle.py b/tools/hw-oracle/hw_oracle.py index 264f9ee8ed..3ff15b08b0 100644 --- a/tools/hw-oracle/hw_oracle.py +++ b/tools/hw-oracle/hw_oracle.py @@ -155,7 +155,10 @@ def run(body, rows, extra=b"", wave64=False, coarse=False, *, ieee, denorm32, de rows_per_dispatch = ROWS_PER_DISPATCH // 2 if wave64 else ROWS_PER_DISPATCH for start in range(0, len(padded), rows_per_dispatch): chunk = padded[start:start + rows_per_dispatch] - payload = b"".join(struct.pack("<4I", *r) for r in chunk) + extra + payload = b"".join(struct.pack("<4I", *r) for r in chunk) + if extra and len(padded) > rows_per_dispatch: + payload += bytes((rows_per_dispatch - len(chunk)) * 16) + payload += extra if runtime() == "hip": data = hip_runtime().dispatch(Path(code).read_bytes(), len(chunk), payload, 16, coarse) else: diff --git a/tools/hw-oracle/test_hw_oracle.py b/tools/hw-oracle/test_hw_oracle.py index 4c8b1e2385..b3cbc2f1a4 100644 --- a/tools/hw-oracle/test_hw_oracle.py +++ b/tools/hw-oracle/test_hw_oracle.py @@ -199,6 +199,25 @@ class InputRowsTests(unittest.TestCase): results = hw_oracle.run("s_nop 0", [(0, Indexable(), 0x80000000, True)], b"extra", **MODES) self.assertEqual(results, [tuple([0] * 16)]) + def test_extra_stays_at_same_offset_across_dispatches(self): + for wave64, expected_dispatches in ((False, [1024, 32]), (True, [512, 512, 64])): + calls = [] + + def dispatch(command, **kwargs): + threads = int(command[2]) + calls.append((threads, Path(command[3]).read_bytes())) + Path(command[4]).write_bytes(bytes(threads * 64)) + + with self.subTest(wave64=wave64), patch.object(hw_oracle, "assemble", return_value="kernel.co"), \ + patch.object(hw_oracle, "oracle", return_value="oracle"), \ + patch.object(hw_oracle.subprocess, "run", side_effect=dispatch): + hw_oracle.run("s_nop 0", [(0, 0, 0, 0)] * 1025, b"extra", wave64=wave64, **MODES) + rows_per_dispatch = 512 if wave64 else 1024 + self.assertEqual([threads for threads, _ in calls], expected_dispatches) + for threads, data in calls: + self.assertEqual(data[threads * 16:rows_per_dispatch * 16], bytes((rows_per_dispatch - threads) * 16)) + self.assertEqual(data[rows_per_dispatch * 16:], b"extra") + def test_cli_accepts_zero_padded_decimal_rows(self): with tempfile.TemporaryDirectory() as tmp: body, rows = Path(tmp) / "body.s", Path(tmp) / "rows.txt" From 9ef85aeeb26f7c798037140083b358ba630d8b63 Mon Sep 17 00:00:00 2001 From: winstrex <79346005+winstrex@users.noreply.github.com> Date: Sat, 31 Oct 2026 16:30:39 -0300 Subject: [PATCH 155/612] feat(libSceNKWebKit): implement the WTF::StringBuilder and WTF::TextStream exports Ports the 27 StringBuilder and TextStream exports that still threw from public WebKit c55364d53f97: appendCharacters, reserveCapacity, shrinkToFit, reifyString, appendNumber(float) and appendQuotedJSONString, the TextStream operators, release, groups and indent, and the Seconds and URL operators. Both layouts are pinned with static_assert from the release headers of that revision; they are not checked against a console binary. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceNKWebKit/Export.cpp | 108 ----- core/libs/prx/libSceNKWebKit/include/Api.hpp | 1 - .../include/InventoryExports.hpp | 26 - .../prx/libSceNKWebKit/include/WtfExports.hpp | 28 ++ .../prx/libSceNKWebKit/src/StringBuilder.cpp | 388 +++++++++++++++ .../libSceNKWebKit/src/StringBuilderJSON.cpp | 137 ++++++ .../prx/libSceNKWebKit/src/TextStream.cpp | 212 ++++++++ .../libSceNKWebKit/src/WtfNumberFormat.cpp | 146 ++++++ .../libs/prx/libSceNKWebKit/src/WtfString.cpp | 21 + .../prx/libSceNKWebKit/src/WtfStringAbi.hpp | 5 + .../prx/libSceNKWebKit/src/WtfTextAbi.hpp | 59 +++ .../libs/prx/libSceNKWebKit/tests/WtfText.cpp | 452 ++++++++++++++++++ docs/dev/debt/libSceNKWebKit.md | 33 +- 13 files changed, 1453 insertions(+), 163 deletions(-) create mode 100644 core/libs/prx/libSceNKWebKit/src/StringBuilder.cpp create mode 100644 core/libs/prx/libSceNKWebKit/src/StringBuilderJSON.cpp create mode 100644 core/libs/prx/libSceNKWebKit/src/TextStream.cpp create mode 100644 core/libs/prx/libSceNKWebKit/src/WtfNumberFormat.cpp create mode 100644 core/libs/prx/libSceNKWebKit/src/WtfTextAbi.hpp create mode 100644 core/libs/prx/libSceNKWebKit/tests/WtfText.cpp diff --git a/core/libs/prx/libSceNKWebKit/Export.cpp b/core/libs/prx/libSceNKWebKit/Export.cpp index bcadc15ba7..b384711516 100644 --- a/core/libs/prx/libSceNKWebKit/Export.cpp +++ b/core/libs/prx/libSceNKWebKit/Export.cpp @@ -12367,10 +12367,6 @@ int APS5_VABI _ZN7WebCore27TranslateTransformOperationC1ERKNS_6LengthES3_S3_NS_1 NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF13StringBuilder12appendNumberEf() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient35dispatchDidClearWindowObjectInWorldERNS_15DOMWrapperWorldE() { NotImplemented_nid_no_patch(__func__); } @@ -18811,78 +18807,6 @@ int APS5_VABI _ZN3WTF10SymbolImpl6createERNS_10StringImplE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF10TextStream10startGroupEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStream11writeIndentEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStream7releaseEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStream8endGroupEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStream8nextLineEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEPKc() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEPKv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsERKNS_6StringE() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEb() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEc() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEd() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEf() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEi() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEj() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEl() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEm() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEx() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEy() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF10URLHelpers14userVisibleURLERKNS_7CStringE() { NotImplemented_nid_no_patch(__func__); } @@ -19135,26 +19059,6 @@ int APS5_VABI _ZN3WTF13ReadWriteLock9writeLockEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF13StringBuilder11shrinkToFitEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF13StringBuilder15reserveCapacityEj() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKDsj() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKhj() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF13aboutBlankURLEv() { NotImplemented_nid_no_patch(__func__); } @@ -20619,14 +20523,6 @@ int APS5_VABI _ZN3WTF9URLParser9serializeERKNS_6VectorINS_12KeyValuePairINS_6Str NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTFlsERNS_10TextStreamENS_7SecondsE() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTFlsERNS_10TextStreamERKNS_3URLE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN6WebKit14WebProcessMainEiPPc() { NotImplemented_nid_no_patch(__func__); } @@ -36951,10 +36847,6 @@ int APS5_VABI _ZNK3WTF13MonotonicTime4dumpERNS_11PrintStreamE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK3WTF13StringBuilder11reifyStringEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK3WTF14MediaTimeRange12toJSONStringEv() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceNKWebKit/include/Api.hpp b/core/libs/prx/libSceNKWebKit/include/Api.hpp index 017fe05615..77f35e81ae 100644 --- a/core/libs/prx/libSceNKWebKit/include/Api.hpp +++ b/core/libs/prx/libSceNKWebKit/include/Api.hpp @@ -78,7 +78,6 @@ int APS5_VABI _ZN3WTF9URLParser23maybeCanonicalizeSchemeERKNS_6StringE(); int APS5_VABI _ZN3WTF21MemoryPressureHandler15setProcessStateENS_18WebsamProcessStateE(); int APS5_VABI _ZThn16_N9Inspector21InspectorConsoleAgent6enableERN3WTF6StringE(); int APS5_VABI _ZN7WebCore27TranslateTransformOperationC1ERKNS_6LengthES3_S3_NS_18TransformOperation13OperationTypeE(); -int APS5_VABI _ZN3WTF13StringBuilder12appendNumberEf(); int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient35dispatchDidClearWindowObjectInWorldERNS_15DOMWrapperWorldE(); int APS5_VABI WKDownloadCancel(); int APS5_VABI _ZNK7WebCore14SecurityOrigin20isSameSchemeHostPortERKS0_(); diff --git a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp index 602bd5e8e3..0215667380 100644 --- a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp @@ -1581,24 +1581,6 @@ int APS5_VABI _ZN3WTF10StringView16GraphemeClusters8IteratorppEv(); int APS5_VABI _ZN3WTF10SymbolImpl16createNullSymbolEv(); int APS5_VABI _ZN3WTF10SymbolImpl17nextHashForSymbolEv(); int APS5_VABI _ZN3WTF10SymbolImpl6createERNS_10StringImplE(); -int APS5_VABI _ZN3WTF10TextStream10startGroupEv(); -int APS5_VABI _ZN3WTF10TextStream11writeIndentEv(); -int APS5_VABI _ZN3WTF10TextStream7releaseEv(); -int APS5_VABI _ZN3WTF10TextStream8endGroupEv(); -int APS5_VABI _ZN3WTF10TextStream8nextLineEv(); -int APS5_VABI _ZN3WTF10TextStreamlsEPKc(); -int APS5_VABI _ZN3WTF10TextStreamlsEPKv(); -int APS5_VABI _ZN3WTF10TextStreamlsERKNS_6StringE(); -int APS5_VABI _ZN3WTF10TextStreamlsEb(); -int APS5_VABI _ZN3WTF10TextStreamlsEc(); -int APS5_VABI _ZN3WTF10TextStreamlsEd(); -int APS5_VABI _ZN3WTF10TextStreamlsEf(); -int APS5_VABI _ZN3WTF10TextStreamlsEi(); -int APS5_VABI _ZN3WTF10TextStreamlsEj(); -int APS5_VABI _ZN3WTF10TextStreamlsEl(); -int APS5_VABI _ZN3WTF10TextStreamlsEm(); -int APS5_VABI _ZN3WTF10TextStreamlsEx(); -int APS5_VABI _ZN3WTF10TextStreamlsEy(); int APS5_VABI _ZN3WTF10URLHelpers14userVisibleURLERKNS_7CStringE(); int APS5_VABI _ZN3WTF10WorkerPool8postTaskEONS_8FunctionIFvvEEE(); int APS5_VABI _ZN3WTF10WorkerPoolC1ENS_12ASCIILiteralEjNS_7SecondsE(); @@ -1662,11 +1644,6 @@ int APS5_VABI _ZN3WTF13ReadWriteLock10readUnlockEv(); int APS5_VABI _ZN3WTF13ReadWriteLock11writeUnlockEv(); int APS5_VABI _ZN3WTF13ReadWriteLock8readLockEv(); int APS5_VABI _ZN3WTF13ReadWriteLock9writeLockEv(); -int APS5_VABI _ZN3WTF13StringBuilder11shrinkToFitEv(); -int APS5_VABI _ZN3WTF13StringBuilder15reserveCapacityEj(); -int APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKDsj(); -int APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKhj(); -int APS5_VABI _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(); int APS5_VABI _ZN3WTF13aboutBlankURLEv(); int APS5_VABI _ZN3WTF13dumpCharacterERNS_11PrintStreamEc(); int APS5_VABI _ZN3WTF13isMainRunLoopEv(); @@ -2044,8 +2021,6 @@ int APS5_VABI _ZN3WTF9URLParser14allValuesEqualERKNS_3URLES3_(); int APS5_VABI _ZN3WTF9URLParser19parseURLEncodedFormENS_10StringViewE(); int APS5_VABI _ZN3WTF9URLParser24internalValuesConsistentERKNS_3URLE(); int APS5_VABI _ZN3WTF9URLParser9serializeERKNS_6VectorINS_12KeyValuePairINS_6StringES3_EELm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEE(); -int APS5_VABI _ZN3WTFlsERNS_10TextStreamENS_7SecondsE(); -int APS5_VABI _ZN3WTFlsERNS_10TextStreamERKNS_3URLE(); int APS5_VABI _ZN6WebKit14WebProcessMainEiPPc(); int APS5_VABI _ZN6WebKit18NetworkProcessMainEiPPc(); int APS5_VABI _ZN7Nicosia6Buffer6createERKN7WebCore7IntSizeEj(); @@ -6127,7 +6102,6 @@ int APS5_VABI _ZNK3WTF10StringView16GraphemeClusters8IteratoreqERKS2_(); int APS5_VABI _ZNK3WTF11Persistence7Decoder28bufferIsLargeEnoughToContainEm(); int APS5_VABI _ZNK3WTF12ASCIILiteral4dumpERNS_11PrintStreamE(); int APS5_VABI _ZNK3WTF13MonotonicTime4dumpERNS_11PrintStreamE(); -int APS5_VABI _ZNK3WTF13StringBuilder11reifyStringEv(); int APS5_VABI _ZNK3WTF14MediaTimeRange12toJSONStringEv(); int APS5_VABI _ZNK3WTF15AutomaticThread19hasUnderlyingThreadERKNS_14AbstractLockerE(); int APS5_VABI _ZNK3WTF15AutomaticThread4nameEv(); diff --git a/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp b/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp index 000dd5a287..a1f3aad7a6 100644 --- a/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp @@ -2,6 +2,7 @@ #include "SceTypes.hpp" #include "prx/libSceNKWebKit/src/WtfStringAbi.hpp" +#include "prx/libSceNKWebKit/src/WtfTextAbi.hpp" #include "prx/libSceNKWebKit/src/WtfTimeTypes.hpp" #include "prx/libSceNKWebKit/src/WtfMemoryTypes.hpp" #include @@ -60,6 +61,24 @@ NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF10StringImpl23convertToASCIIUppercaseE NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF10StringImpl6createEPKDsj(const char16_t* data, unsigned length); NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF10StringImpl6createEPKhj(const unsigned char* data, unsigned length); NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF10StringImpl9substringEjj(const NKWebKit::WtfAbi::StringImpl* self, unsigned start, unsigned length); +void APS5_VABI _ZN3WTF10TextStream10startGroupEv(NKWebKit::WtfAbi::TextStream* self); +void APS5_VABI _ZN3WTF10TextStream11writeIndentEv(NKWebKit::WtfAbi::TextStream* self); +NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF10TextStream7releaseEv(NKWebKit::WtfAbi::TextStream* self); +void APS5_VABI _ZN3WTF10TextStream8endGroupEv(NKWebKit::WtfAbi::TextStream* self); +void APS5_VABI _ZN3WTF10TextStream8nextLineEv(NKWebKit::WtfAbi::TextStream* self); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEPKc(NKWebKit::WtfAbi::TextStream* self, const char* string); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEPKv(NKWebKit::WtfAbi::TextStream* self, const void* pointer); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsERKNS_6StringE(NKWebKit::WtfAbi::TextStream* self, const NKWebKit::WtfAbi::String& string); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEb(NKWebKit::WtfAbi::TextStream* self, bool value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEc(NKWebKit::WtfAbi::TextStream* self, char value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEd(NKWebKit::WtfAbi::TextStream* self, double value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEf(NKWebKit::WtfAbi::TextStream* self, float value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEi(NKWebKit::WtfAbi::TextStream* self, int value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEj(NKWebKit::WtfAbi::TextStream* self, unsigned value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEl(NKWebKit::WtfAbi::TextStream* self, std::int64_t value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEm(NKWebKit::WtfAbi::TextStream* self, std::uint64_t value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEx(NKWebKit::WtfAbi::TextStream* self, long long value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEy(NKWebKit::WtfAbi::TextStream* self, unsigned long long value); void* APS5_VABI _ZN3WTF10fastCallocEmm(std::size_t count, std::size_t size); void* APS5_VABI _ZN3WTF10fastMallocEm(std::size_t size); char* APS5_VABI _ZN3WTF10fastStrDupEPKc(const char* str); @@ -68,6 +87,12 @@ bool APS5_VABI _ZN3WTF11CStringHash5equalERKNS_7CStringES3_(const NKWebKit::WtfA void* APS5_VABI _ZN3WTF11fastReallocEPvm(void* ptr, std::size_t size); void APS5_VABI _ZN3WTF11jsValueFreeEPv(void* ptr); WTF::MonotonicTime APS5_VABI _ZN3WTF13MonotonicTime3nowEv(); +void APS5_VABI _ZN3WTF13StringBuilder11shrinkToFitEv(NKWebKit::WtfAbi::StringBuilder* self); +void APS5_VABI _ZN3WTF13StringBuilder12appendNumberEf(NKWebKit::WtfAbi::StringBuilder* self, float number); +void APS5_VABI _ZN3WTF13StringBuilder15reserveCapacityEj(NKWebKit::WtfAbi::StringBuilder* self, unsigned newCapacity); +void APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKDsj(NKWebKit::WtfAbi::StringBuilder* self, const char16_t* characters, unsigned length); +void APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKhj(NKWebKit::WtfAbi::StringBuilder* self, const unsigned char* characters, unsigned length); +void APS5_VABI _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(NKWebKit::WtfAbi::StringBuilder* self, const NKWebKit::WtfAbi::String& string); NKWebKit::WtfAbi::ProcessPrivileges APS5_VABI _ZN3WTF13allPrivilegesEv(); void* APS5_VABI _ZN3WTF13jsValueMallocEm(std::size_t size); WTF::TryMallocReturnValue* APS5_VABI _ZN3WTF13tryFastCallocEmm(WTF::TryMallocReturnValue* result, std::size_t count, std::size_t size); @@ -155,6 +180,8 @@ std::size_t APS5_VABI _ZN3WTF8pageSizeEv(); void APS5_VABI _ZN3WTF9dataLogFVEPKcP13__va_list_tag(const char* format, void* argList); bool APS5_VABI _ZN3WTFeqERKNS_7CStringEPKc(const NKWebKit::WtfAbi::CString& a, const char* b); bool APS5_VABI _ZN3WTFeqERKNS_7CStringES2_(const NKWebKit::WtfAbi::CString& a, const NKWebKit::WtfAbi::CString& b); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTFlsERNS_10TextStreamENS_7SecondsE(NKWebKit::WtfAbi::TextStream& stream, WTF::Seconds seconds); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTFlsERNS_10TextStreamERKNS_3URLE(NKWebKit::WtfAbi::TextStream& stream, const NKWebKit::WtfAbi::URL& url); bool APS5_VABI _ZN3WTFltERKNS_7CStringES2_(const NKWebKit::WtfAbi::CString& a, const NKWebKit::WtfAbi::CString& b); bool APS5_VABI _ZN7WebCore21isBackForwardLoadTypeENS_13FrameLoadTypeE(std::uint8_t type); bool APS5_VABI _ZN7WebCore8isReloadENS_13FrameLoadTypeE(std::uint8_t type); @@ -198,6 +225,7 @@ std::size_t APS5_VABI _ZNK3WTF10StringView4findES0_j(const NKWebKit::WtfAbi::Str NKWebKit::WtfAbi::CString APS5_VABI _ZNK3WTF10StringView4utf8ENS_14ConversionModeE(const NKWebKit::WtfAbi::StringView* self, unsigned mode); bool APS5_VABI _ZNK3WTF10StringView8endsWithEDs(const NKWebKit::WtfAbi::StringView* self, char16_t value); WTF::WallTime APS5_VABI _ZNK3WTF13MonotonicTime19approximateWallTimeEv(const WTF::MonotonicTime* self); +void APS5_VABI _ZNK3WTF13StringBuilder11reifyStringEv(const NKWebKit::WtfAbi::StringBuilder* self); WTF::MonotonicTime APS5_VABI _ZNK3WTF24TimeWithDynamicClockType13monotonicTimeEv(const WTF::TimeWithDynamicClockType* self); WTF::TimeWithDynamicClockType APS5_VABI _ZNK3WTF24TimeWithDynamicClockType16nowWithSameClockEv(const WTF::TimeWithDynamicClockType* self); WTF::WallTime APS5_VABI _ZNK3WTF24TimeWithDynamicClockType19approximateWallTimeEv(const WTF::TimeWithDynamicClockType* self); diff --git a/core/libs/prx/libSceNKWebKit/src/StringBuilder.cpp b/core/libs/prx/libSceNKWebKit/src/StringBuilder.cpp new file mode 100644 index 0000000000..d75157be03 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/StringBuilder.cpp @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2009-2019 Apple Inc. All rights reserved. + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Derived from Source/WTF/wtf/text/StringBuilder.cpp and Source/WTF/wtf/text/StringBuilder.h (WebKit c55364d53f97) + +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceNKWebKit/include/WtfExports.hpp" + +#include +#include +#include +#include +#include +#include + +#include "WtfTextAbi.hpp" + +namespace { + +using NKWebKit::WtfAbi::String; +using NKWebKit::WtfAbi::StringBuilder; +using NKWebKit::WtfAbi::StringImpl; + +constexpr unsigned MaxCapacity = 0x7FFFFFFF; + +StringBuilder& Checked(StringBuilder* builder) { + if (!builder) throw std::invalid_argument("null WTF::StringBuilder"); + return *builder; +} + +bool Is8Bit(const StringImpl* impl) { + return !impl || (impl->hashAndFlags & 4); +} + +const unsigned char* Characters8(const StringImpl* impl) { + return impl ? static_cast(impl->data) : nullptr; +} + +const char16_t* Characters16(const StringImpl* impl) { + return impl ? static_cast(impl->data) : nullptr; +} + +bool HasOneRef(const StringImpl* impl) { + return (impl->refCount & ~1U) == 2; +} + +unsigned Capacity(const StringBuilder& builder) { + return builder.buffer ? builder.buffer->length : static_cast(builder.length); +} + +unsigned ExpandedCapacity(unsigned capacity, unsigned requiredLength) { + constexpr unsigned minimumCapacity = 16; + return std::max(requiredLength, std::max(minimumCapacity, std::min(capacity * 2, MaxCapacity))); +} + +void SetBuffer(StringBuilder& builder, StringImpl* buffer) { + NKWebKit::WtfAbi::Release(std::exchange(builder.buffer, buffer)); +} + +void AllocateBuffer(StringBuilder& builder, const unsigned char* characters, unsigned requiredLength) { + if (!builder.is8Bit) throw std::logic_error("8-bit WTF::StringBuilder buffer for 16-bit contents"); + void* output; + auto* buffer = NKWebKit::WtfAbi::TryCreateUninitialized(requiredLength, true, output); + builder.characters8 = static_cast(output); + if (!buffer) return NKWebKit::WtfAbi::DidOverflow(builder); + if (builder.length) std::memcpy(builder.characters8, characters, static_cast(builder.length)); + SetBuffer(builder, buffer); + builder.string = String(); +} + +void AllocateBuffer(StringBuilder& builder, const char16_t* characters, unsigned requiredLength) { + if (builder.is8Bit) throw std::logic_error("16-bit WTF::StringBuilder buffer for 8-bit contents"); + void* output; + auto* buffer = NKWebKit::WtfAbi::TryCreateUninitialized(requiredLength, false, output); + builder.characters16 = static_cast(output); + if (!buffer) return NKWebKit::WtfAbi::DidOverflow(builder); + if (builder.length) std::memcpy(builder.characters16, characters, static_cast(builder.length) * sizeof(char16_t)); + SetBuffer(builder, buffer); + builder.string = String(); +} + +template void Reallocate(StringBuilder& builder, unsigned requiredLength) { + constexpr bool is8Bit = sizeof(CharacterType) == 1; + auto* original = std::exchange(builder.buffer, nullptr); + if (!requiredLength) { + NKWebKit::WtfAbi::Release(original); + builder.buffer = NKWebKit::WtfAbi::EmptyStringImpl(); + builder.characters8 = nullptr; + return; + } + void* output; + auto* buffer = NKWebKit::WtfAbi::TryCreateUninitialized(requiredLength, is8Bit, output); + if (!buffer) { + NKWebKit::WtfAbi::Release(original); + return NKWebKit::WtfAbi::DidOverflow(builder); + } + std::memcpy(output, original->data, std::size_t(std::min(original->length, requiredLength)) * sizeof(CharacterType)); + NKWebKit::WtfAbi::Release(original); + builder.buffer = buffer; + if constexpr (is8Bit) builder.characters8 = static_cast(output); + else builder.characters16 = static_cast(output); +} + +template void ReallocateBuffer(StringBuilder& builder, unsigned requiredLength) { + builder.string = String(); + if constexpr (sizeof(CharacterType) == 1) { + if (HasOneRef(builder.buffer)) Reallocate(builder, requiredLength); + else AllocateBuffer(builder, Characters8(builder.buffer), requiredLength); + } else { + if (Is8Bit(builder.buffer)) NKWebKit::WtfAbi::AllocateBufferUpConvert(builder, Characters8(builder.buffer), requiredLength); + else if (HasOneRef(builder.buffer)) Reallocate(builder, requiredLength); + else AllocateBuffer(builder, Characters16(builder.buffer), requiredLength); + } +} + +template CharacterType* BufferCharacters(StringBuilder& builder) { + if constexpr (sizeof(CharacterType) == 1) return builder.characters8; + else return builder.characters16; +} + +template const CharacterType* StringCharacters(const StringBuilder& builder) { + if constexpr (sizeof(CharacterType) == 1) return Characters8(builder.string.impl); + else return Characters16(builder.string.impl); +} + +template CharacterType* ExtendBufferSlowCase(StringBuilder& builder, unsigned requiredLength) { + if (builder.buffer) ReallocateBuffer(builder, ExpandedCapacity(Capacity(builder), requiredLength)); + else AllocateBuffer(builder, builder.length ? StringCharacters(builder) : nullptr, ExpandedCapacity(Capacity(builder), requiredLength)); + if (builder.overflowed) return nullptr; + auto* result = BufferCharacters(builder) + builder.length; + builder.length = static_cast(requiredLength); + return result; +} + +template CharacterType* ExtendBufferWithoutOverflowCheck(StringBuilder& builder, unsigned requiredLength) { + if (builder.buffer && requiredLength <= builder.buffer->length) { + unsigned currentLength = static_cast(builder.length); + builder.string = String(); + builder.length = static_cast(requiredLength); + return BufferCharacters(builder) + currentLength; + } + return ExtendBufferSlowCase(builder, requiredLength); +} + +std::uint64_t RequiredLength(const StringBuilder& builder, unsigned additionalLength) { + std::uint64_t requiredLength = std::uint64_t(static_cast(builder.length)) + additionalLength; + if (requiredLength > std::numeric_limits::max()) throw std::length_error("WTF::StringBuilder length overflow"); + return requiredLength; +} + +template CharacterType* ExtendBufferForAppending(StringBuilder& builder, unsigned additionalLength) { + auto requiredLength = RequiredLength(builder, additionalLength); + if (requiredLength > MaxCapacity) { + NKWebKit::WtfAbi::DidOverflow(builder); + return nullptr; + } + return ExtendBufferWithoutOverflowCheck(builder, static_cast(requiredLength)); +} + +unsigned char* ExtendBufferForAppending8(StringBuilder& builder, std::uint64_t requiredLength) { + if (requiredLength > MaxCapacity) { + NKWebKit::WtfAbi::DidOverflow(builder); + return nullptr; + } + return ExtendBufferWithoutOverflowCheck(builder, static_cast(requiredLength)); +} + +char16_t* ExtendBufferForAppending16(StringBuilder& builder, std::uint64_t requiredLength) { + if (requiredLength > MaxCapacity) { + NKWebKit::WtfAbi::DidOverflow(builder); + return nullptr; + } + if (builder.is8Bit) { + const unsigned char* characters = builder.buffer ? Characters8(builder.buffer) : Characters8(builder.string.impl); + NKWebKit::WtfAbi::AllocateBufferUpConvert(builder, characters, ExpandedCapacity(Capacity(builder), static_cast(requiredLength))); + if (builder.overflowed) return nullptr; + unsigned oldLength = static_cast(builder.length); + builder.length = static_cast(requiredLength); + return builder.characters16 + oldLength; + } + return ExtendBufferWithoutOverflowCheck(builder, static_cast(requiredLength)); +} + +bool CanShrink(const StringBuilder& builder) { + if (builder.overflowed) return false; + unsigned length = static_cast(builder.length); + return builder.buffer && builder.buffer->length > length + (length >> 2); +} + +void ReifyString(const StringBuilder& builder) { + if (builder.string.impl) return; + if (!builder.length) { + builder.string = String(NKWebKit::WtfAbi::EmptyStringImpl()); + return; + } + if (!builder.buffer) throw std::logic_error("WTF::StringBuilder has contents but no buffer"); + if (static_cast(builder.length) == builder.buffer->length) { + builder.buffer->refCount += 2; + builder.string = String(builder.buffer); + } else { + builder.buffer->refCount += 2; + String buffer(builder.buffer); + builder.string = NKWebKit::WtfAbi::SubstringSharingImpl(buffer, 0, static_cast(builder.length)); + } +} + +void ShrinkToFit(StringBuilder& builder) { + if (!CanShrink(builder)) return; + if (builder.is8Bit) ReallocateBuffer(builder, static_cast(builder.length)); + else ReallocateBuffer(builder, static_cast(builder.length)); + builder.string = String(std::exchange(builder.buffer, nullptr)); +} + +void AppendCharacters(StringBuilder& builder, const unsigned char* characters, unsigned length) { + if (!length || builder.overflowed) return; + if (!characters) throw std::invalid_argument("null WTF::StringBuilder characters"); + if (builder.is8Bit) { + auto* destination = ExtendBufferForAppending(builder, length); + if (!destination) return; + std::memcpy(destination, characters, length); + } else { + auto* destination = ExtendBufferForAppending(builder, length); + if (!destination) return; + std::copy(characters, characters + length, destination); + } +} + +void AppendCharacters(StringBuilder& builder, const char16_t* characters, unsigned length) { + if (!length || builder.overflowed) return; + if (!characters) throw std::invalid_argument("null WTF::StringBuilder characters"); + if (builder.is8Bit && length == 1 && characters[0] <= 0xFF) { + NKWebKit::WtfAbi::AppendCharacter(builder, static_cast(characters[0])); + return; + } + auto* destination = ExtendBufferForAppending16(builder, RequiredLength(builder, length)); + if (!destination) return; + std::memcpy(destination, characters, static_cast(length) * sizeof(char16_t)); +} + +} // namespace + +namespace NKWebKit::WtfAbi { + +void DidOverflow(StringBuilder& builder) { + builder.overflowed = true; + if (builder.shouldCrashOnOverflow) throw std::length_error("WTF::StringBuilder overflowed"); +} + +void AllocateBufferUpConvert(StringBuilder& builder, const unsigned char* characters, unsigned requiredLength) { + unsigned length = static_cast(builder.length); + if (length && !characters) throw std::logic_error("WTF::StringBuilder has contents but no 8-bit characters"); + void* output; + auto* buffer = TryCreateUninitialized(requiredLength, false, output); + builder.characters16 = static_cast(output); + if (!buffer) return DidOverflow(builder); + for (unsigned i = 0; i < length; ++i) builder.characters16[i] = characters[i]; + builder.is8Bit = false; + SetBuffer(builder, buffer); + builder.string = String(); +} + +void AppendCharacter(StringBuilder& builder, unsigned char character) { + if (builder.overflowed) return; + unsigned length = static_cast(builder.length); + if (builder.buffer && length < builder.buffer->length && !builder.string.impl) { + if (builder.is8Bit) builder.characters8[length] = character; + else builder.characters16[length] = character; + ++builder.length; + } else { + AppendCharacters(builder, &character, 1); + } +} + +void AppendString(StringBuilder& builder, const StringImpl* string) { + if (builder.overflowed || !string || !string->length) return; + if (!builder.length && !builder.buffer) { + const_cast(string)->refCount += 2; + builder.string = String(const_cast(string)); + builder.length = static_cast(string->length); + builder.is8Bit = Is8Bit(string); + return; + } + if (Is8Bit(string)) AppendCharacters(builder, Characters8(string), string->length); + else AppendCharacters(builder, Characters16(string), string->length); +} + +void AppendCString(StringBuilder& builder, const char* characters) { + if (characters) AppendCharacters(builder, reinterpret_cast(characters), static_cast(std::strlen(characters))); +} + +void AppendAdapted(StringBuilder& builder, const char* characters, unsigned length) { + std::uint64_t requiredLength = builder.overflowed ? std::uint64_t(MaxCapacity) + 1 : std::uint64_t(static_cast(builder.length)) + length; + if (builder.is8Bit) { + auto* destination = ExtendBufferForAppending8(builder, requiredLength); + if (destination) std::memcpy(destination, characters, length); + } else { + auto* destination = ExtendBufferForAppending16(builder, requiredLength); + if (destination) std::copy(characters, characters + length, destination); + } +} + +String ToString(StringBuilder& builder) { + if (builder.string.impl) return builder.string; + if (builder.overflowed) throw std::length_error("WTF::StringBuilder overflowed"); + ShrinkToFit(builder); + ReifyString(builder); + return builder.string; +} + +void ReserveCapacity(StringBuilder& builder, unsigned newCapacity) { + if (builder.overflowed) return; + if (builder.buffer) { + if (newCapacity > builder.buffer->length) { + if (Is8Bit(builder.buffer)) ReallocateBuffer(builder, newCapacity); + else ReallocateBuffer(builder, newCapacity); + } + return; + } + unsigned length = static_cast(builder.length); + if (newCapacity <= length) return; + if (!length) AllocateBuffer(builder, static_cast(nullptr), newCapacity); + else if (Is8Bit(builder.string.impl)) AllocateBuffer(builder, Characters8(builder.string.impl), newCapacity); + else AllocateBuffer(builder, Characters16(builder.string.impl), newCapacity); +} + +void Clear(StringBuilder& builder) { + builder.overflowed = false; + builder.length = 0; + builder.string = String(); + SetBuffer(builder, nullptr); + builder.characters8 = nullptr; + builder.is8Bit = true; +} + +} // namespace NKWebKit::WtfAbi + +extern "C" { + +void APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKDsj(StringBuilder* self, const char16_t* characters, unsigned length) { + AppendCharacters(Checked(self), characters, length); +} + +void APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKhj(StringBuilder* self, const unsigned char* characters, unsigned length) { + AppendCharacters(Checked(self), characters, length); +} + +void APS5_VABI _ZN3WTF13StringBuilder15reserveCapacityEj(StringBuilder* self, unsigned newCapacity) { + NKWebKit::WtfAbi::ReserveCapacity(Checked(self), newCapacity); +} + +void APS5_VABI _ZN3WTF13StringBuilder11shrinkToFitEv(StringBuilder* self) { + ShrinkToFit(Checked(self)); +} + +void APS5_VABI _ZNK3WTF13StringBuilder11reifyStringEv(const StringBuilder* self) { + ReifyString(Checked(const_cast(self))); +} + +void APS5_VABI _ZN3WTF13StringBuilder12appendNumberEf(StringBuilder* self, float number) { + auto& builder = Checked(self); + auto text = NKWebKit::WtfAbi::NumberToString(number); + NKWebKit::WtfAbi::AppendCString(builder, text.c_str()); +} + +} diff --git a/core/libs/prx/libSceNKWebKit/src/StringBuilderJSON.cpp b/core/libs/prx/libSceNKWebKit/src/StringBuilderJSON.cpp new file mode 100644 index 0000000000..2d59d0bbeb --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/StringBuilderJSON.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2010-2018 Apple Inc. All rights reserved. + * Copyright (C) 2012 Google Inc. All rights reserved. + * Copyright (C) 2017 Yusuke Suzuki . All rights reserved. + * Copyright (C) 2017 Mozilla Foundation. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +// Derived from Source/WTF/wtf/text/StringBuilderJSON.cpp (WebKit c55364d53f97) + +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceNKWebKit/include/WtfExports.hpp" + +#include +#include +#include + +#include "WtfTextAbi.hpp" + +namespace { + +using NKWebKit::WtfAbi::String; +using NKWebKit::WtfAbi::StringBuilder; + +constexpr unsigned MaxLength = 0x7FFFFFFF; + +constexpr unsigned char EscapedFormsForJSON[0x100] = { + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', + 'b', 't', 'n', 'u', 'f', 'r', 'u', 'u', + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', + 0, 0, '\"', 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, '\\', 0, 0, 0, +}; + +char LowerNibbleToLowercaseHex(unsigned value) { + return "0123456789abcdef"[value & 15]; +} + +template +void AppendQuotedJSONStringInternal(OutputCharacterType*& output, const InputCharacterType* input, unsigned length) { + for (auto* end = input + length; input != end; ++input) { + unsigned character = *input; + if (character <= 0xFF) { + auto escaped = EscapedFormsForJSON[character]; + if (!escaped) { + *output++ = static_cast(character); + continue; + } + *output++ = '\\'; + *output++ = escaped; + if (escaped == 'u') { + *output++ = '0'; + *output++ = '0'; + *output++ = LowerNibbleToLowercaseHex(character >> 4); + *output++ = LowerNibbleToLowercaseHex(character); + } + continue; + } + if (character < 0xD800 || character > 0xDFFF) { + *output++ = static_cast(character); + continue; + } + auto next = input + 1; + if (character <= 0xDBFF && next != end && *next >= 0xDC00 && *next <= 0xDFFF) { + *output++ = static_cast(character); + *output++ = static_cast(*next); + ++input; + continue; + } + unsigned upper = character >> 8; + unsigned lower = character & 0xFF; + *output++ = '\\'; + *output++ = 'u'; + *output++ = LowerNibbleToLowercaseHex(upper >> 4); + *output++ = LowerNibbleToLowercaseHex(upper); + *output++ = LowerNibbleToLowercaseHex(lower >> 4); + *output++ = LowerNibbleToLowercaseHex(lower); + } +} + +unsigned RoundUpToPowerOfTwo(unsigned value) { + value--; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value++; + return value; +} + +} // namespace + +extern "C" { + +void APS5_VABI _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(StringBuilder* self, const String& string) { + if (!self) throw std::invalid_argument("null WTF::StringBuilder"); + auto& builder = *self; + if (builder.overflowed) return; + const auto* impl = string.impl; + unsigned stringLength = impl ? impl->length : 0; + bool stringIs8Bit = !impl || (impl->hashAndFlags & 4); + std::uint64_t maximumCapacityRequired = std::uint64_t(static_cast(builder.length)) + 2 + std::uint64_t(stringLength) * 6; + if (maximumCapacityRequired > 0xFFFFFFFFULL) return NKWebKit::WtfAbi::DidOverflow(builder); + unsigned allocationSize = static_cast(maximumCapacityRequired); + allocationSize = std::max(allocationSize, RoundUpToPowerOfTwo(allocationSize)); + if (allocationSize > MaxLength) return NKWebKit::WtfAbi::DidOverflow(builder); + if (builder.is8Bit && !stringIs8Bit) NKWebKit::WtfAbi::AllocateBufferUpConvert(builder, builder.characters8, allocationSize); + else NKWebKit::WtfAbi::ReserveCapacity(builder, allocationSize); + if (builder.overflowed) return; + if (builder.is8Bit) { + auto* output = builder.characters8 + static_cast(builder.length); + *output++ = '"'; + AppendQuotedJSONStringInternal(output, impl ? static_cast(impl->data) : nullptr, stringLength); + *output++ = '"'; + builder.length = static_cast(output - builder.characters8); + } else { + auto* output = builder.characters16 + static_cast(builder.length); + *output++ = '"'; + if (stringIs8Bit) AppendQuotedJSONStringInternal(output, impl ? static_cast(impl->data) : nullptr, stringLength); + else AppendQuotedJSONStringInternal(output, static_cast(impl->data), stringLength); + *output++ = '"'; + builder.length = static_cast(output - builder.characters16); + } +} + +} diff --git a/core/libs/prx/libSceNKWebKit/src/TextStream.cpp b/core/libs/prx/libSceNKWebKit/src/TextStream.cpp new file mode 100644 index 0000000000..9fddcd9b3d --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/TextStream.cpp @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2004-2020 Apple Inc. All rights reserved. + * Copyright (C) 2012 Research In Motion Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Derived from Source/WTF/wtf/text/TextStream.cpp, Source/WTF/wtf/Seconds.cpp and Source/WTF/wtf/URL.cpp (WebKit c55364d53f97) + +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/WindowsFormatting.hpp" +#include "prx/libSceNKWebKit/include/WtfExports.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "WtfTextAbi.hpp" + +namespace { + +using NKWebKit::WtfAbi::String; +using NKWebKit::WtfAbi::TextStream; +using NKWebKit::WtfAbi::URL; + +constexpr unsigned NumberRespectingIntegers = 1 << 1; + +TextStream& Checked(TextStream* stream) { + if (!stream) throw std::invalid_argument("null WTF::TextStream"); + return *stream; +} + +int TruncateToInt(double value) { + if (!(value > -2147483649.0 && value < 2147483648.0)) return std::numeric_limits::min(); + return static_cast(value); +} + +bool HasFractions(double value) { + constexpr double epsilon = 0.0001; + return std::fabs(value - static_cast(TruncateToInt(value))) > epsilon; +} + +template TextStream& AppendNumber(TextStream& stream, T value) { + char buffer[32]; + auto result = std::to_chars(buffer, buffer + sizeof(buffer) - 1, value); + if (result.ec != std::errc{}) throw std::runtime_error("WTF integer formatting failed"); + *result.ptr = 0; + NKWebKit::WtfAbi::AppendCString(stream.text, buffer); + return stream; +} + +TextStream& AppendFixedWidth(TextStream& stream, double value) { + auto text = NKWebKit::WtfAbi::NumberToFixedWidthString(value, 2); + NKWebKit::WtfAbi::AppendAdapted(stream.text, text.data(), static_cast(text.size())); + return stream; +} + +TextStream& AppendNumberRespectingIntegers(TextStream& stream, double value) { + if (HasFractions(value)) return AppendFixedWidth(stream, value); + return AppendNumber(stream, TruncateToInt(value)); +} + +TextStream& AppendCString(TextStream& stream, const char* string) { + NKWebKit::WtfAbi::AppendCString(stream.text, string); + return stream; +} + +TextStream& AppendFloatingPoint(TextStream& stream, double value) { + if (stream.formattingFlags & NumberRespectingIntegers) return AppendNumberRespectingIntegers(stream, value); + return AppendFixedWidth(stream, value); +} + +void WriteIndent(TextStream& stream) { + if (!stream.multiLineMode) return; + for (int i = 0; i < stream.indent; ++i) AppendCString(stream, " "); +} + +} // namespace + +extern "C" { + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEb(TextStream* self, bool value) { + return AppendCString(Checked(self), value ? "1" : "0"); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEc(TextStream* self, char value) { + auto& stream = Checked(self); + NKWebKit::WtfAbi::AppendCharacter(stream.text, static_cast(value)); + return stream; +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEi(TextStream* self, int value) { + return AppendNumber(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEj(TextStream* self, unsigned value) { + return AppendNumber(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEl(TextStream* self, std::int64_t value) { + return AppendNumber(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEm(TextStream* self, std::uint64_t value) { + return AppendNumber(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEx(TextStream* self, long long value) { + return AppendNumber(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEy(TextStream* self, unsigned long long value) { + return AppendNumber(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEf(TextStream* self, float value) { + return AppendFloatingPoint(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEd(TextStream* self, double value) { + return AppendFloatingPoint(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEPKc(TextStream* self, const char* string) { + return AppendCString(Checked(self), string); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEPKv(TextStream* self, const void* pointer) { + auto& stream = Checked(self); + auto text = LibcDetail::FormatPointer("%p", pointer); + return AppendCString(stream, text.c_str()); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsERKNS_6StringE(TextStream* self, const String& string) { + auto& stream = Checked(self); + NKWebKit::WtfAbi::AppendString(stream.text, string.impl); + return stream; +} + +String APS5_VABI _ZN3WTF10TextStream7releaseEv(TextStream* self) { + auto& stream = Checked(self); + String result = NKWebKit::WtfAbi::ToString(stream.text); + NKWebKit::WtfAbi::Clear(stream.text); + return result; +} + +void APS5_VABI _ZN3WTF10TextStream10startGroupEv(TextStream* self) { + auto& stream = Checked(self); + if (stream.multiLineMode) { + AppendCString(stream, "\n"); + WriteIndent(stream); + AppendCString(stream, "("); + ++stream.indent; + } else { + AppendCString(stream, " ("); + } +} + +void APS5_VABI _ZN3WTF10TextStream8endGroupEv(TextStream* self) { + auto& stream = Checked(self); + AppendCString(stream, ")"); + if (stream.multiLineMode) --stream.indent; +} + +void APS5_VABI _ZN3WTF10TextStream8nextLineEv(TextStream* self) { + auto& stream = Checked(self); + if (stream.multiLineMode) { + AppendCString(stream, "\n"); + WriteIndent(stream); + } else { + AppendCString(stream, " "); + } +} + +void APS5_VABI _ZN3WTF10TextStream11writeIndentEv(TextStream* self) { + WriteIndent(Checked(self)); +} + +TextStream& APS5_VABI _ZN3WTFlsERNS_10TextStreamENS_7SecondsE(TextStream& stream, WTF::Seconds seconds) { + AppendFloatingPoint(stream, seconds.m_value); + return AppendCString(stream, "s"); +} + +TextStream& APS5_VABI _ZN3WTFlsERNS_10TextStreamERKNS_3URLE(TextStream& stream, const URL& url) { + NKWebKit::WtfAbi::AppendString(stream.text, url.impl); + return stream; +} + +} diff --git a/core/libs/prx/libSceNKWebKit/src/WtfNumberFormat.cpp b/core/libs/prx/libSceNKWebKit/src/WtfNumberFormat.cpp new file mode 100644 index 0000000000..74ace980e4 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/WtfNumberFormat.cpp @@ -0,0 +1,146 @@ +// Copyright 2010 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Derived from Source/WTF/wtf/dtoa/double-conversion.cc (WebKit c55364d53f97) + +#include +#include +#include +#include +#include + +#include "WtfTextAbi.hpp" + +namespace { + +constexpr int DecimalInShortestLow = -6; +constexpr int DecimalInShortestHigh = 21; +constexpr int MaxFixedDigitsAfterPoint = 100; +constexpr double FirstNonFixed = 1e21; + +std::string SpecialValue(double value) { + if (std::isnan(value)) return "NaN"; + return value < 0 ? "-Infinity" : "Infinity"; +} + +void CreateDecimalRepresentation(std::string& result, const std::string& digits, int decimalPoint, int digitsAfterPoint) { + int length = static_cast(digits.size()); + if (decimalPoint <= 0) { + result += '0'; + if (digitsAfterPoint > 0) { + result += '.'; + result.append(-decimalPoint, '0'); + result += digits; + result.append(digitsAfterPoint - -decimalPoint - length, '0'); + } + } else if (decimalPoint >= length) { + result += digits; + result.append(decimalPoint - length, '0'); + if (digitsAfterPoint > 0) { + result += '.'; + result.append(digitsAfterPoint, '0'); + } + } else { + result.append(digits, 0, decimalPoint); + result += '.'; + result.append(digits, decimalPoint); + result.append(digitsAfterPoint - (length - decimalPoint), '0'); + } +} + +void CreateExponentialRepresentation(std::string& result, const std::string& digits, int exponent) { + result += digits[0]; + if (digits.size() != 1) { + result += '.'; + result.append(digits, 1); + } + result += 'e'; + if (exponent < 0) { + result += '-'; + exponent = -exponent; + } else { + result += '+'; + } + result += std::to_string(exponent); +} + +} // namespace + +namespace NKWebKit::WtfAbi { + +std::string NumberToString(float number) { + if (!std::isfinite(number)) return SpecialValue(number); + if (number == 0) return "0"; + char buffer[64]; + auto converted = std::to_chars(buffer, buffer + sizeof(buffer), std::fabs(number), std::chars_format::scientific); + if (converted.ec != std::errc{}) throw std::runtime_error("WTF float formatting failed"); + std::string digits; + const char* cursor = buffer; + for (; cursor != converted.ptr && *cursor != 'e'; ++cursor) { + if (*cursor != '.') digits += *cursor; + } + int exponent = 0; + if (cursor == converted.ptr || std::from_chars(cursor + (cursor[1] == '+' ? 2 : 1), converted.ptr, exponent).ec != std::errc{}) + throw std::runtime_error("WTF float exponent formatting failed"); + std::string result = number < 0 ? "-" : ""; + int decimalPoint = exponent + 1; + int length = static_cast(digits.size()); + if (DecimalInShortestLow <= exponent && exponent < DecimalInShortestHigh) + CreateDecimalRepresentation(result, digits, decimalPoint, length > decimalPoint ? length - decimalPoint : 0); + else + CreateExponentialRepresentation(result, digits, exponent); + return result; +} + +std::string NumberToFixedWidthString(double number, unsigned decimalPlaces) { + if (!std::isfinite(number)) return SpecialValue(number); + if (decimalPlaces > MaxFixedDigitsAfterPoint) return std::string(); + if (number >= FirstNonFixed || number <= -FirstNonFixed) return std::string(); + char buffer[1200]; + auto converted = std::to_chars(buffer, buffer + sizeof(buffer), std::fabs(number), std::chars_format::fixed, 1100); + if (converted.ec != std::errc{}) throw std::runtime_error("WTF fixed formatting failed"); + std::string exact(buffer, converted.ptr); + auto point = exact.find('.'); + std::string digits = exact.substr(0, point) + exact.substr(point + 1, decimalPlaces); + if (exact[point + 1 + decimalPlaces] >= '5') { + std::size_t index = digits.size(); + while (index && digits[index - 1] == '9') digits[--index] = '0'; + if (index) ++digits[index - 1]; + else digits.insert(digits.begin(), '1'); + } + std::size_t leadingZeros = 0; + while (leadingZeros < digits.size() && digits[leadingZeros] == '0') ++leadingZeros; + int decimalPoint = static_cast(digits.size() - decimalPlaces) - static_cast(leadingZeros); + digits.erase(0, leadingZeros); + while (!digits.empty() && digits.back() == '0') digits.pop_back(); + if (digits.empty()) decimalPoint = -static_cast(decimalPlaces); + std::string result = std::signbit(number) && number != 0.0 ? "-" : ""; + CreateDecimalRepresentation(result, digits, decimalPoint, static_cast(decimalPlaces)); + return result; +} + +} // namespace NKWebKit::WtfAbi diff --git a/core/libs/prx/libSceNKWebKit/src/WtfString.cpp b/core/libs/prx/libSceNKWebKit/src/WtfString.cpp index 72b70c7208..411ed2f789 100644 --- a/core/libs/prx/libSceNKWebKit/src/WtfString.cpp +++ b/core/libs/prx/libSceNKWebKit/src/WtfString.cpp @@ -462,6 +462,27 @@ template String IntegerString(T value) { } // namespace +namespace NKWebKit::WtfAbi { + +StringImpl* EmptyStringImpl() { + return EmptyImpl(); +} + +StringImpl* TryCreateUninitialized(unsigned length, bool is8Bit, void*& characters) { + if (length > std::min(MaxStringLength, (0xFFFFFFFFULL - sizeof(StringImpl)) / (is8Bit ? 1 : 2))) { + characters = nullptr; + return nullptr; + } + return NewImpl(length, is8Bit, characters); +} + +String SubstringSharingImpl(const String& string, unsigned start, unsigned length) { + return SharedSubstring(&string, start, length); +} + +} // namespace NKWebKit::WtfAbi + + extern "C" { String APS5_VABI _ZN3WTF10StringImpl6createEPKDsj(const char16_t* data, unsigned length) { return String(CopyImpl({data, length, false})); diff --git a/core/libs/prx/libSceNKWebKit/src/WtfStringAbi.hpp b/core/libs/prx/libSceNKWebKit/src/WtfStringAbi.hpp index 1f4bdc17e6..23500fad24 100644 --- a/core/libs/prx/libSceNKWebKit/src/WtfStringAbi.hpp +++ b/core/libs/prx/libSceNKWebKit/src/WtfStringAbi.hpp @@ -57,8 +57,13 @@ struct String { String(const String& other) : impl(other.impl) { if (impl) impl->refCount += 2; } String(String&& other) noexcept : impl(std::exchange(other.impl, nullptr)) {} ~String() { Release(impl); } + String& operator=(String other) noexcept { std::swap(impl, other.impl); return *this; } }; +StringImpl* EmptyStringImpl(); +StringImpl* TryCreateUninitialized(unsigned length, bool is8Bit, void*& characters); +String SubstringSharingImpl(const String& string, unsigned start, unsigned length); + struct CString { CStringBuffer* buffer; explicit CString(CStringBuffer* value = nullptr) : buffer(value) {} diff --git a/core/libs/prx/libSceNKWebKit/src/WtfTextAbi.hpp b/core/libs/prx/libSceNKWebKit/src/WtfTextAbi.hpp new file mode 100644 index 0000000000..60d1d94ebc --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/WtfTextAbi.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "WtfStringAbi.hpp" + +#include +#include +#include + +namespace NKWebKit::WtfAbi { + +struct StringBuilder { + mutable String string; + StringImpl* buffer; + union { + unsigned char* characters8; + char16_t* characters16; + }; + bool overflowed; + bool shouldCrashOnOverflow; + std::int32_t length; + bool is8Bit; +}; + +struct TextStream { + StringBuilder text; + unsigned formattingFlags; + int indent; + bool multiLineMode; +}; + +void AppendString(StringBuilder& builder, const StringImpl* string); +void AppendCharacter(StringBuilder& builder, unsigned char character); +void AppendCString(StringBuilder& builder, const char* characters); +void ReserveCapacity(StringBuilder& builder, unsigned newCapacity); +void AppendAdapted(StringBuilder& builder, const char* characters, unsigned length); +String ToString(StringBuilder& builder); +void Clear(StringBuilder& builder); +void DidOverflow(StringBuilder& builder); +void AllocateBufferUpConvert(StringBuilder& builder, const unsigned char* characters, unsigned requiredLength); +std::string NumberToString(float number); +std::string NumberToFixedWidthString(double number, unsigned decimalPlaces); + +static_assert(sizeof(StringBuilder) == 40); +static_assert(alignof(StringBuilder) == 8); +static_assert(offsetof(StringBuilder, string) == 0); +static_assert(offsetof(StringBuilder, buffer) == 8); +static_assert(offsetof(StringBuilder, characters8) == 16); +static_assert(offsetof(StringBuilder, characters16) == 16); +static_assert(offsetof(StringBuilder, overflowed) == 24); +static_assert(offsetof(StringBuilder, shouldCrashOnOverflow) == 25); +static_assert(offsetof(StringBuilder, length) == 28); +static_assert(offsetof(StringBuilder, is8Bit) == 32); +static_assert(sizeof(TextStream) == 56); +static_assert(offsetof(TextStream, text) == 0); +static_assert(offsetof(TextStream, formattingFlags) == 40); +static_assert(offsetof(TextStream, indent) == 44); +static_assert(offsetof(TextStream, multiLineMode) == 48); + +} // namespace NKWebKit::WtfAbi diff --git a/core/libs/prx/libSceNKWebKit/tests/WtfText.cpp b/core/libs/prx/libSceNKWebKit/tests/WtfText.cpp new file mode 100644 index 0000000000..ea3665437b --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/tests/WtfText.cpp @@ -0,0 +1,452 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceNKWebKit/libSceNKWebKit.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../src/WtfTextAbi.hpp" + +namespace { + +using NKWebKit::WtfAbi::String; +using NKWebKit::WtfAbi::StringBuilder; +using NKWebKit::WtfAbi::StringImpl; +using NKWebKit::WtfAbi::TextStream; +using NKWebKit::WtfAbi::URL; + +void Check(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "WTF text test failed at line %u\n", location.line()); + std::abort(); + } +} + +template void Throws(T action, std::source_location location = std::source_location::current()) { + bool threw = false; + try { action(); } catch (const std::exception&) { threw = true; } + Check(threw, location); +} + +const unsigned char* U8(const char* data) { + return reinterpret_cast(data); +} + +String Make8(const char* data) { + return _ZN3WTF10StringImpl6createEPKhj(U8(data), static_cast(std::strlen(data))); +} + +String Make16(const std::u16string& data) { + return _ZN3WTF10StringImpl6createEPKDsj(data.data(), static_cast(data.size())); +} + +std::u16string Units(const StringImpl* impl, unsigned length) { + std::u16string result; + for (unsigned i = 0; i < length; ++i) { + if (impl->hashAndFlags & 4) result += static_cast(impl->data)[i]; + else result += static_cast(impl->data)[i]; + } + return result; +} + +std::u16string Units(const String& string) { + Check(string.impl != nullptr); + return Units(string.impl, string.impl->length); +} + +std::u16string Contents(const StringBuilder& builder) { + if (!builder.length) return {}; + if (builder.string.impl) return Units(builder.string.impl, static_cast(builder.length)); + Check(builder.buffer != nullptr); + return Units(builder.buffer, static_cast(builder.length)); +} + +std::u16string Wide(const char* data) { + std::u16string result; + for (const char* cursor = data; *cursor; ++cursor) result += static_cast(*cursor); + return result; +} + +StringBuilder NewBuilder(bool crashOnOverflow = true) { + StringBuilder builder{}; + builder.shouldCrashOnOverflow = crashOnOverflow; + builder.is8Bit = true; + return builder; +} + +void Free(StringBuilder& builder) { + NKWebKit::WtfAbi::Release(builder.buffer); + builder.buffer = nullptr; +} + +TextStream NewStream(bool multiLine = true, unsigned flags = 0) { + TextStream stream{}; + stream.text = NewBuilder(); + stream.formattingFlags = flags; + stream.multiLineMode = multiLine; + return stream; +} + +std::u16string Release(TextStream& stream) { + auto result = _ZN3WTF10TextStream7releaseEv(&stream); + Check(!stream.text.buffer && !stream.text.string.impl && !stream.text.length && stream.text.is8Bit && !stream.text.characters8); + return Units(result); +} + +void Append8(StringBuilder& builder, const char* data) { + _ZN3WTF13StringBuilder16appendCharactersEPKhj(&builder, U8(data), static_cast(std::strlen(data))); +} + +void TestAppendCharacters() { + auto builder = NewBuilder(); + _ZN3WTF13StringBuilder16appendCharactersEPKhj(&builder, nullptr, 0); + Check(!builder.buffer && !builder.length); + Throws([&] { _ZN3WTF13StringBuilder16appendCharactersEPKhj(&builder, nullptr, 1); }); + Append8(builder, "abc"); + Check(builder.buffer && builder.buffer->length == 16 && builder.length == 3 && builder.is8Bit); + Check(builder.characters8 == builder.buffer->data && Contents(builder) == u"abc"); + Append8(builder, "defghijklmnop"); + Check(builder.buffer->length == 16 && builder.length == 16); + Append8(builder, "q"); + Check(builder.buffer->length == 32 && Contents(builder) == u"abcdefghijklmnopq"); + const char16_t latin = u'\xe9'; + _ZN3WTF13StringBuilder16appendCharactersEPKDsj(&builder, &latin, 1); + Check(builder.is8Bit && builder.length == 18); + const char16_t wide[] = u"\x263a!"; + _ZN3WTF13StringBuilder16appendCharactersEPKDsj(&builder, wide, 2); + Check(!builder.is8Bit && !(builder.buffer->hashAndFlags & 4) && builder.buffer->length == 64 && builder.length == 20); + Check(builder.characters16 == builder.buffer->data && Contents(builder) == u"abcdefghijklmnopq\xe9\x263a!"); + Append8(builder, "\xff"); + Check(Contents(builder) == u"abcdefghijklmnopq\xe9\x263a!\xff"); + Free(builder); + + auto widened = NewBuilder(); + _ZN3WTF13StringBuilder16appendCharactersEPKDsj(&widened, wide, 1); + Check(!widened.is8Bit && widened.buffer->length == 16 && Contents(widened) == u"\x263a"); + Free(widened); +} + +void TestOverflow() { + auto recording = NewBuilder(false); + recording.length = 0x7FFFFFF0; + Append8(recording, "0123456789abcdefg"); + Check(recording.overflowed && !recording.buffer && recording.length == 0x7FFFFFF0); + Append8(recording, "a"); + _ZN3WTF13StringBuilder15reserveCapacityEj(&recording, 4); + Check(recording.overflowed && !recording.buffer); + TextStream stream = NewStream(); + stream.text.shouldCrashOnOverflow = false; + stream.text.overflowed = true; + Throws([&] { _ZN3WTF10TextStream7releaseEv(&stream); }); + + auto crashing = NewBuilder(); + crashing.length = 0x7FFFFFF0; + Throws([&] { Append8(crashing, "0123456789abcdefg"); }); + Check(crashing.overflowed); + + auto wrapping = NewBuilder(false); + wrapping.length = 0x7FFFFFFF; + const char16_t unit = u'x'; + Throws([&] { _ZN3WTF13StringBuilder16appendCharactersEPKDsj(&wrapping, &unit, 0xFFFFFFFF); }); + Check(!wrapping.overflowed); +} + +void TestReserveCapacity() { + auto builder = NewBuilder(); + _ZN3WTF13StringBuilder15reserveCapacityEj(&builder, 0); + Check(!builder.buffer); + _ZN3WTF13StringBuilder15reserveCapacityEj(&builder, 5); + Check(builder.buffer && builder.buffer->length == 5 && builder.length == 0 && builder.characters8 == builder.buffer->data); + Append8(builder, "hello"); + auto* first = builder.buffer; + _ZN3WTF13StringBuilder15reserveCapacityEj(&builder, 3); + Check(builder.buffer == first); + _ZN3WTF13StringBuilder15reserveCapacityEj(&builder, 40); + Check(builder.buffer->length == 40 && builder.buffer->refCount == 2 && Contents(builder) == u"hello"); + Free(builder); + + auto text = Make16(u"\x3b1\x3b2"); + TextStream stream = NewStream(); + _ZN3WTF10TextStreamlsERKNS_6StringE(&stream, text); + Check(stream.text.string.impl == text.impl && text.impl->refCount == 4 && !stream.text.is8Bit && stream.text.length == 2); + _ZN3WTF13StringBuilder15reserveCapacityEj(&stream.text, 10); + Check(!stream.text.string.impl && stream.text.buffer->length == 10 && !(stream.text.buffer->hashAndFlags & 4)); + Check(Contents(stream.text) == u"\x3b1\x3b2" && text.impl->refCount == 2); + Check(Release(stream) == u"\x3b1\x3b2"); + + auto shared = NewBuilder(); + Append8(shared, "abc"); + _ZN3WTF13StringBuilder11shrinkToFitEv(&shared); + _ZN3WTF13StringBuilder15reserveCapacityEj(&shared, 8); + Check(shared.buffer->length == 8 && !shared.string.impl && Contents(shared) == u"abc"); + Free(shared); +} + +void TestReifyAndShrink() { + auto builder = NewBuilder(); + _ZNK3WTF13StringBuilder11reifyStringEv(&builder); + Check(builder.string.impl && builder.string.impl->length == 0 && !builder.buffer); + + auto shortText = NewBuilder(); + Append8(shortText, "abc"); + _ZNK3WTF13StringBuilder11reifyStringEv(&shortText); + Check(shortText.string.impl != shortText.buffer && (shortText.string.impl->hashAndFlags & 3) == 0); + Check(Units(shortText.string) == u"abc" && shortText.buffer->refCount == 2); + auto* reified = shortText.string.impl; + _ZNK3WTF13StringBuilder11reifyStringEv(&shortText); + Check(shortText.string.impl == reified); + Append8(shortText, "d"); + Check(!shortText.string.impl && Contents(shortText) == u"abcd"); + Free(shortText); + + auto longText = NewBuilder(); + Append8(longText, "0123456789abcd"); + _ZNK3WTF13StringBuilder11reifyStringEv(&longText); + Check((longText.string.impl->hashAndFlags & 3) == 2 && longText.string.impl->data == longText.buffer->data); + Check(longText.buffer->refCount == 4 && Units(longText.string) == u"0123456789abcd"); + Free(longText); + + auto full = NewBuilder(); + _ZN3WTF13StringBuilder15reserveCapacityEj(&full, 4); + Append8(full, "wxyz"); + _ZNK3WTF13StringBuilder11reifyStringEv(&full); + Check(full.string.impl == full.buffer && full.buffer->refCount == 4); + Free(full); + + auto shrink = NewBuilder(); + Append8(shrink, "abc"); + _ZN3WTF13StringBuilder11shrinkToFitEv(&shrink); + Check(!shrink.buffer && shrink.string.impl && shrink.string.impl->length == 3 && shrink.string.impl->refCount == 2); + Check(shrink.characters8 == shrink.string.impl->data && Units(shrink.string) == u"abc"); + _ZN3WTF13StringBuilder11shrinkToFitEv(&shrink); + Check(shrink.string.impl->length == 3); + + auto nearlyFull = NewBuilder(); + Append8(nearlyFull, "0123456789abc"); + _ZN3WTF13StringBuilder11shrinkToFitEv(&nearlyFull); + Check(nearlyFull.buffer && nearlyFull.buffer->length == 16 && !nearlyFull.string.impl); + Free(nearlyFull); + + auto wide = NewBuilder(); + const char16_t text[] = u"\x263a\x263b"; + _ZN3WTF13StringBuilder16appendCharactersEPKDsj(&wide, text, 2); + _ZN3WTF13StringBuilder11shrinkToFitEv(&wide); + Check(!wide.buffer && wide.string.impl->length == 2 && !(wide.string.impl->hashAndFlags & 4) && Units(wide.string) == u"\x263a\x263b"); +} + +void TestQuotedJSON() { + auto builder = NewBuilder(); + auto text = Make8("a\"b\\\n\x01\x7f\xe9/"); + _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(&builder, text); + Check(builder.is8Bit && builder.buffer->length == 64); + Check(Contents(builder) == Wide("\"a\\\"b\\\\\\n\\u0001\x7f\xe9/\"")); + Free(builder); + + auto controls = NewBuilder(); + auto escapes = Make8("\b\t\f\r\x1f"); + _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(&controls, escapes); + Check(Contents(controls) == Wide("\"\\b\\t\\f\\r\\u001f\"")); + Free(controls); + + auto wide = NewBuilder(); + Append8(wide, "x"); + auto surrogates = Make16(u"\x263a\xd83d\xde00\xd800-\xdc00\xdbff"); + _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(&wide, surrogates); + Check(!wide.is8Bit && wide.buffer->length == 64); + Check(Contents(wide) == u"x\"\x263a\xd83d\xde00\\ud800-\\udc00\\udbff\""); + Free(wide); + + auto empty = NewBuilder(); + String null; + _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(&empty, null); + Check(Contents(empty) == u"\"\"" && empty.buffer->length == 2); + Free(empty); + + TextStream stream = NewStream(); + auto ascii = Make8("abc"); + _ZN3WTF10TextStreamlsERKNS_6StringE(&stream, ascii); + auto quoted = Make8("q"); + _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(&stream.text, quoted); + Check(Contents(stream.text) == u"abc\"q\"" && stream.text.buffer->length == 16); + Check(Release(stream) == u"abc\"q\""); + _ZN3WTF10TextStreamlsERKNS_6StringE(&stream, ascii); + Throws([&] { _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(&stream.text, surrogates); }); + + auto recording = NewBuilder(false); + recording.length = 0x7FFFFFF0; + _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(&recording, quoted); + Check(recording.overflowed && !recording.buffer); +} + +std::u16string Float(float value) { + auto builder = NewBuilder(); + _ZN3WTF13StringBuilder12appendNumberEf(&builder, value); + auto result = Contents(builder); + Free(builder); + return result; +} + +void TestAppendNumber() { + Check(Float(0.1f) == u"0.1"); + Check(Float(100.0f) == u"100"); + Check(Float(-2.5f) == u"-2.5"); + Check(Float(123456789.0f) == u"123456790"); + Check(Float(1e20f) == u"100000000000000000000"); + Check(Float(1e21f) == u"1e+21"); + Check(Float(0.000001f) == u"0.000001"); + Check(Float(1.5e-7f) == u"1.5e-7"); + Check(Float(3.4028235e38f) == u"3.4028235e+38"); + Check(Float(1.17549435e-38f) == u"1.1754944e-38"); + Check(Float(1.4e-45f) == u"1e-45"); + Check(Float(0.0f) == u"0" && Float(-0.0f) == u"0"); + Check(Float(std::numeric_limits::quiet_NaN()) == u"NaN"); + Check(Float(std::numeric_limits::infinity()) == u"Infinity"); + Check(Float(-std::numeric_limits::infinity()) == u"-Infinity"); +} + +void TestTextStreamValues() { + auto stream = NewStream(); + auto* self = &stream; + Check(&_ZN3WTF10TextStreamlsEb(self, true) == self); + _ZN3WTF10TextStreamlsEb(self, false); + _ZN3WTF10TextStreamlsEc(self, ','); + _ZN3WTF10TextStreamlsEi(self, std::numeric_limits::min()); + _ZN3WTF10TextStreamlsEc(self, ','); + _ZN3WTF10TextStreamlsEj(self, std::numeric_limits::max()); + _ZN3WTF10TextStreamlsEc(self, ','); + _ZN3WTF10TextStreamlsEl(self, std::numeric_limits::min()); + _ZN3WTF10TextStreamlsEc(self, ','); + _ZN3WTF10TextStreamlsEm(self, std::numeric_limits::max()); + _ZN3WTF10TextStreamlsEc(self, ','); + _ZN3WTF10TextStreamlsEx(self, -42); + _ZN3WTF10TextStreamlsEc(self, ','); + _ZN3WTF10TextStreamlsEy(self, 42); + Check(Release(stream) == u"10,-2147483648,4294967295,-9223372036854775808,18446744073709551615,-42,42"); + + _ZN3WTF10TextStreamlsEf(self, 1.5f); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, 3.14159); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, 0.125); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, 0.996); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, 9.999); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, -0.001); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, -0.0); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, 1e20); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, std::numeric_limits::quiet_NaN()); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, -std::numeric_limits::infinity()); + _ZN3WTF10TextStreamlsEPKc(self, "|"); + _ZN3WTF10TextStreamlsEd(self, 1e21); + _ZN3WTF10TextStreamlsEPKc(self, "|"); + Check(Release(stream) == u"1.50 3.14 0.13 1.00 10.00 -0.00 0.00 100000000000000000000.00 NaN -Infinity||"); + + auto empty = NewStream(); + _ZN3WTF10TextStreamlsEd(&empty, 1e21); + Check(empty.text.buffer && empty.text.buffer->length == 16 && !empty.text.length); + Check(Release(empty).empty()); + + auto integers = NewStream(true, 2); + _ZN3WTF10TextStreamlsEd(&integers, 3.0); + _ZN3WTF10TextStreamlsEc(&integers, ' '); + _ZN3WTF10TextStreamlsEd(&integers, 3.5); + _ZN3WTF10TextStreamlsEc(&integers, ' '); + _ZN3WTF10TextStreamlsEf(&integers, -7.00001f); + _ZN3WTF10TextStreamlsEc(&integers, ' '); + _ZN3WTF10TextStreamlsEd(&integers, 1e10); + _ZN3WTF10TextStreamlsEc(&integers, ' '); + _ZN3WTF10TextStreamlsEd(&integers, std::numeric_limits::quiet_NaN()); + Check(Release(integers) == u"3 3.50 -7 10000000000.00 -2147483648"); + + _ZN3WTF10TextStreamlsEPKv(self, reinterpret_cast(std::uintptr_t{0x1234abcd})); + _ZN3WTF10TextStreamlsEc(self, ' '); + _ZN3WTF10TextStreamlsEPKv(self, nullptr); + _ZN3WTF10TextStreamlsEPKc(self, nullptr); + Check(Release(stream) == u"1234abcd 0"); + + Check(Release(stream).empty()); + Throws([] { _ZN3WTF10TextStreamlsEi(nullptr, 1); }); +} + +void TestTextStreamStrings() { + auto stream = NewStream(); + String null; + _ZN3WTF10TextStreamlsERKNS_6StringE(&stream, null); + Check(!stream.text.string.impl && !stream.text.length); + auto first = Make8("first"); + _ZN3WTF10TextStreamlsERKNS_6StringE(&stream, first); + Check(stream.text.string.impl == first.impl && !stream.text.buffer); + auto result = _ZN3WTF10TextStream7releaseEv(&stream); + Check(result.impl == first.impl && first.impl->refCount == 4); + + auto wide = Make16(u"\x3a9"); + _ZN3WTF10TextStreamlsEc(&stream, '<'); + _ZN3WTF10TextStreamlsERKNS_6StringE(&stream, wide); + _ZN3WTF10TextStreamlsEc(&stream, '>'); + Check(!stream.text.is8Bit && Release(stream) == u"<\x3a9>"); + + URL url{}; + auto address = Make8("https://example.com/"); + url.impl = address.impl; + _ZN3WTFlsERNS_10TextStreamERKNS_3URLE(stream, url); + _ZN3WTFlsERNS_10TextStreamENS_7SecondsE(stream, WTF::Seconds{2.5}); + _ZN3WTFlsERNS_10TextStreamENS_7SecondsE(stream, WTF::Seconds{-0.25}); + Check(Release(stream) == u"https://example.com/2.50s-0.25s"); +} + +void TestTextStreamGroups() { + auto stream = NewStream(); + _ZN3WTF10TextStreamlsEPKc(&stream, "root"); + _ZN3WTF10TextStream10startGroupEv(&stream); + Check(stream.indent == 1); + _ZN3WTF10TextStreamlsEPKc(&stream, "a"); + _ZN3WTF10TextStream10startGroupEv(&stream); + _ZN3WTF10TextStreamlsEPKc(&stream, "b"); + _ZN3WTF10TextStream8nextLineEv(&stream); + _ZN3WTF10TextStreamlsEPKc(&stream, "c"); + _ZN3WTF10TextStream8endGroupEv(&stream); + _ZN3WTF10TextStream8endGroupEv(&stream); + Check(stream.indent == 0); + _ZN3WTF10TextStream11writeIndentEv(&stream); + stream.indent = 2; + _ZN3WTF10TextStream11writeIndentEv(&stream); + Check(Release(stream) == u"root\n(a\n (b\n c)) "); + + auto single = NewStream(false); + _ZN3WTF10TextStreamlsEPKc(&single, "root"); + _ZN3WTF10TextStream10startGroupEv(&single); + _ZN3WTF10TextStreamlsEPKc(&single, "a"); + _ZN3WTF10TextStream8nextLineEv(&single); + _ZN3WTF10TextStreamlsEPKc(&single, "b"); + _ZN3WTF10TextStream8endGroupEv(&single); + Check(single.indent == 0); + single.indent = 3; + _ZN3WTF10TextStream11writeIndentEv(&single); + Check(single.indent == 3 && Release(single) == u"root (a b)"); +} + +} // namespace + +int main() { + TestAppendCharacters(); + TestOverflow(); + TestReserveCapacity(); + TestReifyAndShrink(); + TestQuotedJSON(); + TestAppendNumber(); + TestTextStreamValues(); + TestTextStreamStrings(); + TestTextStreamGroups(); + return 0; +} diff --git a/docs/dev/debt/libSceNKWebKit.md b/docs/dev/debt/libSceNKWebKit.md index 787e89801b..57fa611688 100644 --- a/docs/dev/debt/libSceNKWebKit.md +++ b/docs/dev/debt/libSceNKWebKit.md @@ -4,6 +4,7 @@ - [WebKit API objects](../../../core/libs/prx/libSceNKWebKit/src/Object.hpp) (libSceNKWebKit) - `WK*` objects are host objects numbered with `API::Object::Type` as [APIObject.h](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebKit/Shared/API/APIObject.h) builds for PlayStation, with ApplicationManifest, InspectorExtension, WebExtension and WebAuthn types off and PopupMenuItem and AccessibilityObject on per [OptionsPlayStation.cmake](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/cmake/OptionsPlayStation.cmake); the console module version, its type numbers and its object layouts are unverified, so these objects cannot be passed to a console WebKit module. A null or wrong-type reference throws where WebKit dereferences it. - [WTF and WebCore value layouts](../../../core/libs/prx/libSceNKWebKit/src/WtfStringAbi.hpp) (libSceNKWebKit) - `WTF::String`, `StringImpl`, `StringView`, `CString`, `URL` and the WebCore geometry, transform and `Color` layouts follow the release 64-bit headers at the [pinned source](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WTF/wtf/text/StringImpl.h) and are pinned with `static_assert`; they are tested on Windows, not against console binaries. `StringImpl::destroy`, atom and symbol strings and external buffers still throw, so a string whose last reference guest code drops terminates. +- [WTF text builder layouts](../../../core/libs/prx/libSceNKWebKit/src/WtfTextAbi.hpp) (libSceNKWebKit) - `WTF::StringBuilder` (40 bytes) and `WTF::TextStream` (56 bytes) follow the release 64-bit headers of public WebKit [StringBuilder.h](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/text/StringBuilder.h#L365-L376), [CheckedArithmetic.h](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/CheckedArithmetic.h#L91-L114) and [TextStream.h](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/text/TextStream.h#L168-L171) without `ASSERT_ENABLED`, and are pinned with `static_assert`. That WebKit revision is inferred from the export names, and the layouts, the buffer growth and the `shrinkToFit` threshold are not checked against a console binary. - [Media preferences](../../../core/libs/prx/libSceNKWebKit/src/Preferences.cpp) (libSceNKWebKit) - unset `MediaSourceEnabled`, `EncryptedMediaAPIEnabled` and `SourceBufferChangeTypeEnabled` getters throw, and feature-key setters for `EncryptedMediaAPIEnabled`, `SourceBufferChangeTypeEnabled` and `WebMParserEnabled` throw, because the PlayStation media player configuration behind `ENABLE_MEDIA_SOURCE` and `ENABLE_ENCRYPTED_MEDIA` is not in the pinned source. Other defaults evaluate [UnifiedWebPreferences.yaml](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml) for PlayStation; downstream console overrides are unverified. ### Functional @@ -15,7 +16,7 @@ ### Known export names; ABI prototypes unverified -- The 131 remaining WebCore, WebKit C API, JavaScriptCore, Inspector and WTF exports in [Export.cpp](../../../core/libs/prx/libSceNKWebKit/Export.cpp) are present in the PS5 3.20 `libSceNKWebKit` export inventory from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). Each is named after the symbol the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) gives for its NID, and the NID of that name matches. Prototypes and behavior are unverified; each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`. These declarations make no ABI or behavior claim. +- The 130 remaining WebCore, WebKit C API, JavaScriptCore, Inspector and WTF exports in [Export.cpp](../../../core/libs/prx/libSceNKWebKit/Export.cpp) are present in the PS5 3.20 `libSceNKWebKit` export inventory from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). Each is named after the symbol the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) gives for its NID, and the NID of that name matches. Prototypes and behavior are unverified; each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`. These declarations make no ABI or behavior claim. ### Older callable exports @@ -25,7 +26,7 @@ - The [revision-pinned identity inventory](https://gist.github.com/kyon-eth/d44995f181e1818977bd38f0e6fc0f34/8afcb14b0707a09b6274ff7f7bcf33ef46cfe2e7) records the module, library, symbol type and NIDs for the declarations in [Export.cpp](../../../core/libs/prx/libSceNKWebKit/Export.cpp); it is a metadata summary rather than independent verification. For the 255 absent from the public catalogue, [per-image ELF records](https://gist.github.com/kyon-eth/147be8dd556e9cced2577ccbed548972/1a659ecfe819e724675991a574a1aebb5fbb48e5) include the raw qualified names, module and library identities, symbol type, values and sizes; the record file SHA-256 is `44bdc4d6073192a8f41b7be359b75364b3d41dbbd11ce649924b8cebdd4ffe51`. Image labels are not authenticated. -- [Additional callable exports](../../../core/libs/prx/libSceNKWebKit/Export.cpp) - 7574 of the 8703 inventory declarations listed below throw through `NotImplemented_nid_no_patch`; signatures and behavior are unverified. The later 13.60-labelled image contains all 8703; 8448 match the public 3.20 catalogue, while 255 are absent and their presence in retail 3.20 is unverified. Per-image records show the latter in a separate 3.20-labelled image, but neither image label is authenticated. Identified names match NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). Data exports need separate analysis. +- [Additional callable exports](../../../core/libs/prx/libSceNKWebKit/Export.cpp) - 7548 of the 8703 inventory declarations listed below throw through `NotImplemented_nid_no_patch`; signatures and behavior are unverified. The later 13.60-labelled image contains all 8703; 8448 match the public 3.20 catalogue, while 255 are absent and their presence in retail 3.20 is unverified. Per-image records show the latter in a separate 3.20-labelled image, but neither image label is authenticated. Identified names match NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). Data exports need separate analysis. | Name | NID | | --- | --- | @@ -1606,24 +1607,6 @@ | `_ZN3WTF10SymbolImpl16createNullSymbolEv` | `mi8ipN3nDyg` | | `_ZN3WTF10SymbolImpl17nextHashForSymbolEv` | `AaU89EqjdQ4` | | `_ZN3WTF10SymbolImpl6createERNS_10StringImplE` | `24SDQYS0N4M` | -| `_ZN3WTF10TextStream10startGroupEv` | `7KjKZ1IVMSc` | -| `_ZN3WTF10TextStream11writeIndentEv` | `miR5GweF1bI` | -| `_ZN3WTF10TextStream7releaseEv` | `qUP3pQuGplg` | -| `_ZN3WTF10TextStream8endGroupEv` | `OoOcl7cXBC8` | -| `_ZN3WTF10TextStream8nextLineEv` | `uG59t516E9Y` | -| `_ZN3WTF10TextStreamlsEPKc` | `Yn4fNeuLEHQ` | -| `_ZN3WTF10TextStreamlsEPKv` | `q-u3UcSDz54` | -| `_ZN3WTF10TextStreamlsERKNS_6StringE` | `emGrjWcFUpg` | -| `_ZN3WTF10TextStreamlsEb` | `ZNFyGf1tkF0` | -| `_ZN3WTF10TextStreamlsEc` | `hr8P3owweqU` | -| `_ZN3WTF10TextStreamlsEd` | `gEYDrUZ4RH0` | -| `_ZN3WTF10TextStreamlsEf` | `zgdoEsexgpk` | -| `_ZN3WTF10TextStreamlsEi` | `tKaE15lxeA8` | -| `_ZN3WTF10TextStreamlsEj` | `vdsj9De6dXc` | -| `_ZN3WTF10TextStreamlsEl` | `WCrQ7baRnjE` | -| `_ZN3WTF10TextStreamlsEm` | `YfUi1bomEh0` | -| `_ZN3WTF10TextStreamlsEx` | `OVWIH7kpWNA` | -| `_ZN3WTF10TextStreamlsEy` | `ZEgja1SGUP8` | | `_ZN3WTF10URLHelpers14userVisibleURLERKNS_7CStringE` | `2lI1xw8MWoE` | | `_ZN3WTF10WorkerPool8postTaskEONS_8FunctionIFvvEEE` | `rKBbPSETkpU` | | `_ZN3WTF10WorkerPoolC1ENS_12ASCIILiteralEjNS_7SecondsE` | `Awb0lcEV300` | @@ -1687,11 +1670,6 @@ | `_ZN3WTF13ReadWriteLock11writeUnlockEv` | `trYWw7dUt9Y` | | `_ZN3WTF13ReadWriteLock8readLockEv` | `tbhRqP+K-1c` | | `_ZN3WTF13ReadWriteLock9writeLockEv` | `cKaXv3V+n+k` | -| `_ZN3WTF13StringBuilder11shrinkToFitEv` | `2n+gsAD8UIc` | -| `_ZN3WTF13StringBuilder15reserveCapacityEj` | `ZE+kvBSZtjw` | -| `_ZN3WTF13StringBuilder16appendCharactersEPKDsj` | `ZWHpG+3pbDg` | -| `_ZN3WTF13StringBuilder16appendCharactersEPKhj` | `E2TkkVyRubQ` | -| `_ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE` | `xJRNEp2d-Wg` | | `_ZN3WTF13aboutBlankURLEv` | `3nmBMeZtd+Y` | | `_ZN3WTF13dumpCharacterERNS_11PrintStreamEc` | `rpsD9ynfjc0` | | `_ZN3WTF13isMainRunLoopEv` | `5K-9Y7aFV60` | @@ -2058,8 +2036,6 @@ | `_ZN3WTF9URLParser19parseURLEncodedFormENS_10StringViewE` | `Ev+2GetBNwE` | | `_ZN3WTF9URLParser24internalValuesConsistentERKNS_3URLE` | `jKPJKar5yGY` | | `_ZN3WTF9URLParser9serializeERKNS_6VectorINS_12KeyValuePairINS_6StringES3_EELm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEE` | `sx-mGnQPyeg` | -| `_ZN3WTFlsERNS_10TextStreamENS_7SecondsE` | `UgIITEJeYRo` | -| `_ZN3WTFlsERNS_10TextStreamERKNS_3URLE` | `8M6cv85VPG4` | | `_ZN6WebKit14WebProcessMainEiPPc` | `xQI1SMBDGvk` | | `_ZN6WebKit18NetworkProcessMainEiPPc` | `SJWwfPd52Ug` | | `_ZN7Nicosia6Buffer6createERKN7WebCore7IntSizeEj` | `MfJ3R1TdXCY` | @@ -6141,7 +6117,6 @@ | `_ZNK3WTF11Persistence7Decoder28bufferIsLargeEnoughToContainEm` | `OuoVW-SJiZQ` | | `_ZNK3WTF12ASCIILiteral4dumpERNS_11PrintStreamE` | `IdIO1h6EW3I` | | `_ZNK3WTF13MonotonicTime4dumpERNS_11PrintStreamE` | `K-UfI+QGiEU` | -| `_ZNK3WTF13StringBuilder11reifyStringEv` | `DmOJ1MfHFrw` | | `_ZNK3WTF14MediaTimeRange12toJSONStringEv` | `mz868SDp4PI` | | `_ZNK3WTF15AutomaticThread19hasUnderlyingThreadERKNS_14AbstractLockerE` | `U7n0UvpCFFw` | | `_ZNK3WTF15AutomaticThread4nameEv` | `r8Md8WuqsoA` | @@ -7610,3 +7585,5 @@ - [parseDateFromNullTerminatedCharacters(const char*, bool&)](../../../core/libs/prx/libSceNKWebKit/DateParsing.cpp) (libSceNKWebKit) - is not measured on a device, and throws where pinned [DateMath.cpp](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/DateMath.cpp#L684-L988) overflows a signed integer, which is undefined there (the `YYYY/MM/DD` day count, the `GMT+hh:mm` minute total and comment nesting), and for a null argument, where WebKit faults. - [WTF decimal parsing](../../../core/libs/prx/libSceNKWebKit/CharactersToNumber.cpp) - the device's WebKit revision is unverified; `charactersToDouble`, `charactersToFloat` and `StringToDoubleConverter` follow pinned WebKit [toDoubleType](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/text/WTFString.cpp#L1040-L1098) and [StringToIeee](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/dtoa/double-conversion.cc#L459-L664). Under a rounding mode other than round-to-nearest, WebKit's [exact-power fast path](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/dtoa/strtod.cc#L219-L254) and the double-to-float cast in [Strtof](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/dtoa/strtod.cc#L485-L588) round in the current mode and the host conversion may round differently. - [WTF decimal parsing limits](../../../core/libs/prx/libSceNKWebKit/CharactersToNumber.cpp) - inputs of about 1 GiB or more whose decimal exponent leaves the range of WebKit's [`int` exponent](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/dtoa/double-conversion.cc#L488-L490) throw, where WebKit overflows. A UTF-16 `charactersTo*` length above `UINT32_MAX` after leading spaces throws where WebKit's [conversion Vector](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/Vector.h#L291-L293) crashes. +- [WTF StringBuilder](../../../core/libs/prx/libSceNKWebKit/src/StringBuilder.cpp) (libSceNKWebKit) - throws where WebKit crashes: an overflow of a builder that crashes on overflow, a length sum above `UINT32_MAX`, `toString` after an overflow, and null characters with a nonzero length. It also throws where a debug assertion guards memory WebKit would corrupt: `reserveCapacity` on an empty 16-bit builder without a buffer, and [`appendQuotedJSONString`](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/text/StringBuilderJSON.cpp#L127-L128) of a 16-bit string into an 8-bit builder that holds an adopted string and no buffer. A capacity above the tree's `StringImpl` limit records an overflow, as a failed allocation does. Growing or shrinking a buffer with one reference copies it instead of calling `fastRealloc`, so its address always changes. +- [WTF TextStream pointers](../../../core/libs/prx/libSceNKWebKit/src/TextStream.cpp) (libSceNKWebKit) - `operator<<(const void*)` formats the pointer as the tree's libc formats `%p` ([`snprintf`](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/text/TextStream.cpp#L114-L119) in WebKit); the console libc's output is unverified. From 83413ef46807f8ae6fa37b4f93d54ac6691db910 Mon Sep 17 00:00:00 2001 From: winstrex <79346005+winstrex@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:02:45 -0300 Subject: [PATCH 156/612] fix(libSceNKWebKit): record a failed WTF::StringBuilder allocation as an overflow Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceNKWebKit/CMakeLists.txt | 12 + .../libs/prx/libSceNKWebKit/src/WtfString.cpp | 32 ++- .../libs/prx/libSceNKWebKit/tests/WtfText.cpp | 4 +- .../tests/allocation/WtfTextAllocation.cpp | 215 ++++++++++++++++++ docs/dev/debt/libSceNKWebKit.md | 2 +- 5 files changed, 253 insertions(+), 12 deletions(-) create mode 100644 core/libs/prx/libSceNKWebKit/tests/allocation/WtfTextAllocation.cpp diff --git a/core/libs/prx/libSceNKWebKit/CMakeLists.txt b/core/libs/prx/libSceNKWebKit/CMakeLists.txt index 9bad4ef4d0..c057ab2688 100644 --- a/core/libs/prx/libSceNKWebKit/CMakeLists.txt +++ b/core/libs/prx/libSceNKWebKit/CMakeLists.txt @@ -60,4 +60,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endforeach() + + add_test_executable(nkwebkit_wtftext_allocation_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/allocation/WtfTextAllocation.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/StringBuilder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/WtfString.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/WtfNumberFormat.cpp) + target_include_directories(nkwebkit_wtftext_allocation_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(nkwebkit_wtftext_allocation_tests PRIVATE libc) + configure_windows_unwind(nkwebkit_wtftext_allocation_tests) + add_test(NAME nkwebkit_wtftext_allocation COMMAND nkwebkit_wtftext_allocation_tests) + set_tests_properties(nkwebkit_wtftext_allocation PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(nkwebkit_wtftext_allocation PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceNKWebKit/src/WtfString.cpp b/core/libs/prx/libSceNKWebKit/src/WtfString.cpp index 411ed2f789..1896e9c2b4 100644 --- a/core/libs/prx/libSceNKWebKit/src/WtfString.cpp +++ b/core/libs/prx/libSceNKWebKit/src/WtfString.cpp @@ -165,6 +165,7 @@ double APS5_VABI _ZN3WTF8Internal25parseDoubleFromLongStringEPKDsmRm( namespace NKWebKit { void* Allocate(std::size_t size, std::size_t alignment = 16); +void* TryAllocate(std::size_t size, std::size_t alignment = 16); void Deallocate(void* ptr); } // namespace NKWebKit @@ -242,17 +243,26 @@ StringImpl* Retain(StringImpl* impl) { return impl; } +bool ExceedsMaxLength(unsigned length, bool is8Bit) { + return length > std::min(MaxStringLength, (0xFFFFFFFFULL - sizeof(StringImpl)) / (is8Bit ? 1 : 2)); +} + +std::size_t ImplAllocationSize(unsigned length, bool is8Bit) { + return std::max(sizeof(StringImpl), 20 + (is8Bit ? 1 : 2) * std::size_t(length)); +} + +StringImpl* ConstructImpl(void* storage, unsigned length, bool is8Bit, void*& characters) { + characters = static_cast(storage) + 20; + return new (storage) StringImpl{2, length, characters, is8Bit ? 4U : 0U}; +} + StringImpl* NewImpl(unsigned length, bool is8Bit, void*& characters) { if (!length) { characters = nullptr; return EmptyImpl(); } - const std::size_t width = is8Bit ? 1 : 2; - if (length > std::min(MaxStringLength, (0xFFFFFFFFULL - sizeof(StringImpl)) / width)) - throw std::length_error("WTF string exceeds maximum length"); - auto* storage = static_cast(NKWebKit::Allocate(std::max(sizeof(StringImpl), 20 + width * length))); - characters = storage + 20; - return new (storage) StringImpl{2, length, characters, is8Bit ? 4U : 0U}; + if (ExceedsMaxLength(length, is8Bit)) throw std::length_error("WTF string exceeds maximum length"); + return ConstructImpl(NKWebKit::Allocate(ImplAllocationSize(length, is8Bit)), length, is8Bit, characters); } StringImpl* CopyImpl(StringView view, bool narrow = false) { @@ -469,11 +479,16 @@ StringImpl* EmptyStringImpl() { } StringImpl* TryCreateUninitialized(unsigned length, bool is8Bit, void*& characters) { - if (length > std::min(MaxStringLength, (0xFFFFFFFFULL - sizeof(StringImpl)) / (is8Bit ? 1 : 2))) { + if (!length) { + characters = nullptr; + return EmptyImpl(); + } + void* storage = ExceedsMaxLength(length, is8Bit) ? nullptr : NKWebKit::TryAllocate(ImplAllocationSize(length, is8Bit)); + if (!storage) { characters = nullptr; return nullptr; } - return NewImpl(length, is8Bit, characters); + return ConstructImpl(storage, length, is8Bit, characters); } String SubstringSharingImpl(const String& string, unsigned start, unsigned length) { @@ -482,7 +497,6 @@ String SubstringSharingImpl(const String& string, unsigned start, unsigned lengt } // namespace NKWebKit::WtfAbi - extern "C" { String APS5_VABI _ZN3WTF10StringImpl6createEPKDsj(const char16_t* data, unsigned length) { return String(CopyImpl({data, length, false})); diff --git a/core/libs/prx/libSceNKWebKit/tests/WtfText.cpp b/core/libs/prx/libSceNKWebKit/tests/WtfText.cpp index ea3665437b..7906d56654 100644 --- a/core/libs/prx/libSceNKWebKit/tests/WtfText.cpp +++ b/core/libs/prx/libSceNKWebKit/tests/WtfText.cpp @@ -73,7 +73,7 @@ std::u16string Wide(const char* data) { } StringBuilder NewBuilder(bool crashOnOverflow = true) { - StringBuilder builder{}; + StringBuilder builder{String()}; builder.shouldCrashOnOverflow = crashOnOverflow; builder.is8Bit = true; return builder; @@ -85,7 +85,7 @@ void Free(StringBuilder& builder) { } TextStream NewStream(bool multiLine = true, unsigned flags = 0) { - TextStream stream{}; + TextStream stream{NewBuilder()}; stream.text = NewBuilder(); stream.formattingFlags = flags; stream.multiLineMode = multiLine; diff --git a/core/libs/prx/libSceNKWebKit/tests/allocation/WtfTextAllocation.cpp b/core/libs/prx/libSceNKWebKit/tests/allocation/WtfTextAllocation.cpp new file mode 100644 index 0000000000..ecb8600b5f --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/tests/allocation/WtfTextAllocation.cpp @@ -0,0 +1,215 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceNKWebKit/include/WtfExports.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../../src/WtfTextAbi.hpp" + +namespace { + +constexpr std::size_t GuardSize = 16; +constexpr std::size_t LargestExpectedAllocation = std::size_t(1) << 24; +constexpr unsigned char GuardByte = 0xA5; + +std::unordered_map liveAllocations; +int allocationsBeforeFailure = -1; + +void Check(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "WTF text allocation test failed at line %u\n", location.line()); + std::abort(); + } +} + +void* Place(std::size_t size) { + Check(size <= LargestExpectedAllocation); + if (allocationsBeforeFailure == 0) return nullptr; + if (allocationsBeforeFailure > 0) --allocationsBeforeFailure; + auto* allocation = static_cast(std::malloc(size + GuardSize)); + Check(allocation != nullptr); + std::memset(allocation + size, GuardByte, GuardSize); + liveAllocations.emplace(allocation, size); + return allocation; +} + +} // namespace + +namespace NKWebKit { + +void* Allocate(std::size_t size, std::size_t) { + void* allocation = Place(size); + if (!allocation) { + std::fprintf(stderr, "WTF text allocation test: fatal allocation failed\n"); + std::abort(); + } + return allocation; +} + +void* TryAllocate(std::size_t size, std::size_t) { + return Place(size); +} + +void Deallocate(void* ptr) { + if (!ptr) return; + auto allocation = liveAllocations.find(ptr); + Check(allocation != liveAllocations.end()); + const auto* guard = static_cast(ptr) + allocation->second; + for (std::size_t i = 0; i < GuardSize; ++i) Check(guard[i] == GuardByte); + liveAllocations.erase(allocation); + std::free(ptr); +} + +} // namespace NKWebKit + +namespace { + +using NKWebKit::WtfAbi::String; +using NKWebKit::WtfAbi::StringBuilder; + +template void Throws(T action, std::source_location location = std::source_location::current()) { + bool threw = false; + try { action(); } catch (const std::length_error&) { threw = true; } + Check(threw, location); +} + +StringBuilder NewBuilder(bool crashOnOverflow) { + StringBuilder builder{String()}; + builder.shouldCrashOnOverflow = crashOnOverflow; + builder.is8Bit = true; + return builder; +} + +void Free(StringBuilder& builder) { + NKWebKit::WtfAbi::Release(builder.buffer); + builder.buffer = nullptr; + builder.string = String(); +} + +void Append8(StringBuilder& builder, const char* data) { + _ZN3WTF13StringBuilder16appendCharactersEPKhj(&builder, reinterpret_cast(data), static_cast(std::strlen(data))); +} + +void Append16(StringBuilder& builder, const char16_t* data) { + _ZN3WTF13StringBuilder16appendCharactersEPKDsj(&builder, data, static_cast(std::char_traits::length(data))); +} + +void Reserve(StringBuilder& builder, unsigned capacity) { + _ZN3WTF13StringBuilder15reserveCapacityEj(&builder, capacity); +} + +void FailFrom(int successfulAllocations) { + allocationsBeforeFailure = successfulAllocations; +} + +void Restore() { + allocationsBeforeFailure = -1; +} + +template void Case(TSetup setup, TFailing failing, TRecorded recorded, String* kept = nullptr) { + const auto baseline = liveAllocations.size(); + auto recording = NewBuilder(false); + setup(recording); + FailFrom(0); + failing(recording); + Restore(); + Check(recording.overflowed); + recorded(recording); + Free(recording); + if (kept) *kept = String(); + Check(liveAllocations.size() == baseline); + + auto crashing = NewBuilder(true); + setup(crashing); + FailFrom(0); + Throws([&] { failing(crashing); }); + Restore(); + Check(crashing.overflowed); + Free(crashing); + if (kept) *kept = String(); + Check(liveAllocations.size() == baseline); +} + +void TestInitialAllocation() { + Case([](StringBuilder&) {}, [](StringBuilder& builder) { Append8(builder, "abc"); }, + [](StringBuilder& builder) { Check(!builder.buffer && !builder.length && builder.is8Bit && !builder.characters8); }); +} + +void TestGrowth() { + Case([](StringBuilder& builder) { Append8(builder, "abcdefghijklmnop"); }, [](StringBuilder& builder) { Append8(builder, "q"); }, + [](StringBuilder& builder) { Check(!builder.buffer && builder.length == 16); }); +} + +void TestSharedGrowth() { + String shared; + Case([&](StringBuilder& builder) { + Append8(builder, "abcdefghijklmnop"); + shared = NKWebKit::WtfAbi::ToString(builder); + Check(shared.impl == builder.buffer); + }, + [](StringBuilder& builder) { Append8(builder, "q"); }, + [&](StringBuilder& builder) { + Check(builder.buffer == shared.impl && builder.length == 16 && !builder.string.impl && !builder.characters8); + Check(shared.impl->length == 16 && std::memcmp(shared.impl->data, "abcdefghijklmnop", 16) == 0); + }, &shared); +} + +void TestWideSharedGrowth() { + String shared; + Case([&](StringBuilder& builder) { + Append16(builder, u"\x263a\x263b\x263c\x263d\x263e\x263f\x2640\x2641\x2642\x2643\x2644\x2645\x2646\x2647\x2648\x2649"); + shared = NKWebKit::WtfAbi::ToString(builder); + Check(!builder.is8Bit && shared.impl == builder.buffer && builder.length == 16); + }, + [](StringBuilder& builder) { Append16(builder, u"\x264a"); }, + [&](StringBuilder& builder) { + Check(builder.buffer == shared.impl && builder.length == 16 && !builder.string.impl && !builder.characters16); + Check(static_cast(shared.impl->data)[15] == u'\x2649'); + }, &shared); +} + +void TestLengthLimit() { + const auto baseline = liveAllocations.size(); + auto recording = NewBuilder(false); + Append16(recording, u"\x263a"); + Reserve(recording, 0x7FFFFFFF); + Check(recording.overflowed); + Free(recording); + auto crashing = NewBuilder(true); + Append16(crashing, u"\x263a"); + Throws([&] { Reserve(crashing, 0x7FFFFFFF); }); + Check(crashing.overflowed); + Free(crashing); + Check(liveAllocations.size() == baseline); +} + +void TestReserveCapacity() { + Case([](StringBuilder&) {}, [](StringBuilder& builder) { Reserve(builder, 100); }, + [](StringBuilder& builder) { Check(!builder.buffer && !builder.length && !builder.characters8); }); + Case([](StringBuilder& builder) { Append8(builder, "abc"); }, [](StringBuilder& builder) { Reserve(builder, 100); }, + [](StringBuilder& builder) { Check(!builder.buffer && builder.length == 3); }); +} + +void TestUpConversion() { + Case([](StringBuilder& builder) { Append8(builder, "abc"); }, [](StringBuilder& builder) { Append16(builder, u"\x263a"); }, + [](StringBuilder& builder) { Check(builder.is8Bit && builder.buffer && builder.buffer->length == 16 && builder.length == 3 && !builder.characters16); }); +} + +} // namespace + +int main() { + TestInitialAllocation(); + TestGrowth(); + TestSharedGrowth(); + TestWideSharedGrowth(); + TestReserveCapacity(); + TestLengthLimit(); + TestUpConversion(); + Check(liveAllocations.empty()); + return 0; +} diff --git a/docs/dev/debt/libSceNKWebKit.md b/docs/dev/debt/libSceNKWebKit.md index 57fa611688..8e346ac8d0 100644 --- a/docs/dev/debt/libSceNKWebKit.md +++ b/docs/dev/debt/libSceNKWebKit.md @@ -4,7 +4,7 @@ - [WebKit API objects](../../../core/libs/prx/libSceNKWebKit/src/Object.hpp) (libSceNKWebKit) - `WK*` objects are host objects numbered with `API::Object::Type` as [APIObject.h](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebKit/Shared/API/APIObject.h) builds for PlayStation, with ApplicationManifest, InspectorExtension, WebExtension and WebAuthn types off and PopupMenuItem and AccessibilityObject on per [OptionsPlayStation.cmake](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/cmake/OptionsPlayStation.cmake); the console module version, its type numbers and its object layouts are unverified, so these objects cannot be passed to a console WebKit module. A null or wrong-type reference throws where WebKit dereferences it. - [WTF and WebCore value layouts](../../../core/libs/prx/libSceNKWebKit/src/WtfStringAbi.hpp) (libSceNKWebKit) - `WTF::String`, `StringImpl`, `StringView`, `CString`, `URL` and the WebCore geometry, transform and `Color` layouts follow the release 64-bit headers at the [pinned source](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WTF/wtf/text/StringImpl.h) and are pinned with `static_assert`; they are tested on Windows, not against console binaries. `StringImpl::destroy`, atom and symbol strings and external buffers still throw, so a string whose last reference guest code drops terminates. -- [WTF text builder layouts](../../../core/libs/prx/libSceNKWebKit/src/WtfTextAbi.hpp) (libSceNKWebKit) - `WTF::StringBuilder` (40 bytes) and `WTF::TextStream` (56 bytes) follow the release 64-bit headers of public WebKit [StringBuilder.h](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/text/StringBuilder.h#L365-L376), [CheckedArithmetic.h](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/CheckedArithmetic.h#L91-L114) and [TextStream.h](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/text/TextStream.h#L168-L171) without `ASSERT_ENABLED`, and are pinned with `static_assert`. That WebKit revision is inferred from the export names, and the layouts, the buffer growth and the `shrinkToFit` threshold are not checked against a console binary. +- [WTF text builder layouts](../../../core/libs/prx/libSceNKWebKit/src/WtfTextAbi.hpp) (libSceNKWebKit) - layouts, buffer growth and the `shrinkToFit` threshold are not checked against a console binary. `WTF::StringBuilder` (40 bytes) and `WTF::TextStream` (56 bytes) follow release 64-bit [StringBuilder.h](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/text/StringBuilder.h#L365-L376), [CheckedArithmetic.h](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/CheckedArithmetic.h#L91-L114) and [TextStream.h](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/text/TextStream.h#L168-L171) without `ASSERT_ENABLED`, pinned with `static_assert`. They hold for every WebKit revision with all 27 exports, [bddada44652f](https://github.com/WebKit/WebKit/commit/bddada44652f) (January 2020) to [e12a212a531a](https://github.com/WebKit/WebKit/commit/e12a212a531a) (May 2021); from `1d10fce7651a` the `TextStream` flags are one byte, the bit read unchanged. - [Media preferences](../../../core/libs/prx/libSceNKWebKit/src/Preferences.cpp) (libSceNKWebKit) - unset `MediaSourceEnabled`, `EncryptedMediaAPIEnabled` and `SourceBufferChangeTypeEnabled` getters throw, and feature-key setters for `EncryptedMediaAPIEnabled`, `SourceBufferChangeTypeEnabled` and `WebMParserEnabled` throw, because the PlayStation media player configuration behind `ENABLE_MEDIA_SOURCE` and `ENABLE_ENCRYPTED_MEDIA` is not in the pinned source. Other defaults evaluate [UnifiedWebPreferences.yaml](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml) for PlayStation; downstream console overrides are unverified. ### Functional From 6d393f2b371c2383b08d9fb6d480ddb5abd56f61 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:40:47 +0200 Subject: [PATCH 157/612] feat(libSceLibreSsl): implement X509_NAME_get_entry --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 ++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 44 +++++++++++ core/libs/tests/GuestLibreSslX509Name.cpp | 79 +++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 1 + 5 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/X509Name.cpp create mode 100644 core/libs/tests/GuestLibreSslX509Name.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index e4635639c7..0fe8cbe0fc 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509Name.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -159,6 +159,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_pkey_type PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_x509_name_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslX509Name.cpp) + target_include_directories(guest_libressl_x509_name_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_x509_name_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_x509_name_tests) + add_test(NAME guest_libressl_x509_name COMMAND guest_libressl_x509_name_tests) + set_tests_properties(guest_libressl_x509_name PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_x509_name PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index fc78f64744..c380938055 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1387,11 +1387,6 @@ int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void) { return 0; } -int APS5_VABI X509_NAME_get_entry_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp new file mode 100644 index 0000000000..85fb8215a6 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct LibreSslX509NameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct LibreSslX509Name { + LibreSslStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + +static_assert(sizeof(LibreSslStack) == 32); +static_assert(offsetof(LibreSslStack, data) == 8); +static_assert(sizeof(LibreSslX509NameEntry) == 24); +static_assert(offsetof(LibreSslX509NameEntry, value) == 8); +static_assert(sizeof(LibreSslX509Name) == 40); +static_assert(offsetof(LibreSslX509Name, entries) == 0); + +extern "C" { + +LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc) { + if (!name || loc < 0 || !name->entries || name->entries->num <= loc) return nullptr; + if (!name->entries->data) throw std::invalid_argument(std::string(__func__) + ": null entry array"); + return reinterpret_cast(name->entries->data[loc]); +} + +} diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp new file mode 100644 index 0000000000..ac574ce133 --- /dev/null +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -0,0 +1,79 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct TestStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct TestNameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct TestName { + TestStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + +static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); +static_assert(sizeof(TestNameEntry) == 24 && offsetof(TestNameEntry, value) == 8); +static_assert(sizeof(TestName) == 40); + +extern "C" { +TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL X509_NAME check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetEntry() { + TestNameEntry first {}; + TestNameEntry second {}; + char* slots[] {reinterpret_cast(&first), reinterpret_cast(&second), nullptr}; + TestStack entries {2, slots, 0, 3, nullptr}; + TestName name {}; + Require(X509_NAME_get_entry_nid_postfix(nullptr, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + name.entries = &entries; + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == &first); + Require(X509_NAME_get_entry_nid_postfix(&name, 1) == &second); + Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, INT_MIN) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, INT_MAX) == nullptr); + entries.num = 0; + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + entries.num = 3; + Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + entries.data = nullptr; + RequireThrows([&] { X509_NAME_get_entry_nid_postfix(&name, 0); }); + Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); +} + +int main() { + TestGetEntry(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 271bc7c326..18ae3f47ab 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -26,6 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console +- [X509_NAME_get_entry](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names the caller builds reach it. A positive entry count with a null entry array, which LibreSSL dereferences, throws ### Functional From 779f8bec175940c2f44cfd9c6734f01065670068 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:42:33 +0200 Subject: [PATCH 158/612] feat(libSceLibreSsl): implement X509_NAME_ENTRY_get_data --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 5 +++++ core/libs/tests/GuestLibreSslX509Name.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index c380938055..743492fe4c 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1382,11 +1382,6 @@ int APS5_VABI X509_INFO_free_nid_postfix(void) { return 0; } -int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp index 85fb8215a6..57efc6993b 100644 --- a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -41,4 +41,9 @@ LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX return reinterpret_cast(name->entries->data[loc]); } +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry* entry) { + if (!entry) return nullptr; + return entry->value; +} + } diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp index ac574ce133..1dea64955e 100644 --- a/core/libs/tests/GuestLibreSslX509Name.cpp +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -35,6 +35,7 @@ static_assert(sizeof(TestName) == 40); extern "C" { TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const TestNameEntry*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -73,7 +74,20 @@ static void TestGetEntry() { Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); } +static void TestEntryGetData() { + int value = 0; + TestNameEntry entry {}; + Require(X509_NAME_ENTRY_get_data_nid_postfix(nullptr) == nullptr); + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == nullptr); + entry.value = &value; + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + entry.object = &entry; + entry.set = 7; + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); +} + int main() { TestGetEntry(); + TestEntryGetData(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 18ae3f47ab..7ba919b23c 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 292 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 288 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [X509_NAME_get_entry](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names the caller builds reach it. A positive entry count with a null entry array, which LibreSSL dereferences, throws +- [X509_NAME_get_entry, X509_NAME_ENTRY_get_data](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names and entries the caller builds reach them. A positive entry count with a null entry array, which LibreSSL dereferences, throws ### Functional From 1fe82b5b655556a102322df8f08bf85e14657ea7 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 06:00:22 +0100 Subject: [PATCH 159/612] refactor(libSceLibreSsl): declare the X509_NAME exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 19 ++++++++- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 26 +----------- core/libs/tests/GuestLibreSslX509Name.cpp | 40 ++++++++++--------- 3 files changed, 40 insertions(+), 45 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 3e3d9b7601..b25b7546c8 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -250,6 +250,21 @@ struct LibreSslX509StoreContext { void* exData; }; +struct LibreSslX509NameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct LibreSslX509Name { + LibreSslStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -563,8 +578,8 @@ int APS5_VABI X509_EXTENSION_get_data_nid_postfix(void); int APS5_VABI X509_EXTENSION_get_object_nid_postfix(void); int APS5_VABI X509_INFO_free_nid_postfix(void); LibreSslX509LookupMethod* APS5_VABI X509_LOOKUP_file_nid_postfix(void); -int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void); -int APS5_VABI X509_NAME_get_entry_nid_postfix(void); +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry* entry); +LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc); int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void); int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(void); int APS5_VABI X509_NAME_oneline_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp index 57efc6993b..c220b821a0 100644 --- a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -1,33 +1,9 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslStack { - int num; - char** data; - int sorted; - int numAlloc; - void* comp; -}; - -struct LibreSslX509NameEntry { - void* object; - void* value; - int set; - int size; -}; - -struct LibreSslX509Name { - LibreSslStack* entries; - int modified; - void* bytes; - unsigned char* canonEnc; - int canonEnclen; -}; - -static_assert(sizeof(LibreSslStack) == 32); -static_assert(offsetof(LibreSslStack, data) == 8); static_assert(sizeof(LibreSslX509NameEntry) == 24); static_assert(offsetof(LibreSslX509NameEntry, value) == 8); static_assert(sizeof(LibreSslX509Name) == 40); diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp index 1dea64955e..365b16002e 100644 --- a/core/libs/tests/GuestLibreSslX509Name.cpp +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -33,9 +34,12 @@ static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); static_assert(sizeof(TestNameEntry) == 24 && offsetof(TestNameEntry, value) == 8); static_assert(sizeof(TestName) == 40); -extern "C" { -TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); -void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const TestNameEntry*); +static const LibreSslX509Name* Name(const TestName* name) { + return reinterpret_cast(name); +} + +static const LibreSslX509NameEntry* Entry(const TestNameEntry* entry) { + return reinterpret_cast(entry); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -56,34 +60,34 @@ static void TestGetEntry() { TestStack entries {2, slots, 0, 3, nullptr}; TestName name {}; Require(X509_NAME_get_entry_nid_postfix(nullptr, 0) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), -1) == nullptr); name.entries = &entries; - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == &first); - Require(X509_NAME_get_entry_nid_postfix(&name, 1) == &second); - Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, INT_MIN) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, INT_MAX) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == Entry(&first)); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 1) == Entry(&second)); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), INT_MIN) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), INT_MAX) == nullptr); entries.num = 0; - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == nullptr); entries.num = 3; - Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 2) == nullptr); entries.data = nullptr; - RequireThrows([&] { X509_NAME_get_entry_nid_postfix(&name, 0); }); - Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); + RequireThrows([&] { X509_NAME_get_entry_nid_postfix(Name(&name), 0); }); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 3) == nullptr); } static void TestEntryGetData() { int value = 0; TestNameEntry entry {}; Require(X509_NAME_ENTRY_get_data_nid_postfix(nullptr) == nullptr); - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == nullptr); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == nullptr); entry.value = &value; - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == &value); entry.object = &entry; entry.set = 7; - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == &value); } int main() { From 527f3e5df59f78ac596532c6fde05b4e99192321 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:53:15 +0200 Subject: [PATCH 160/612] feat(libSceLibreSsl): implement SSL_get_SSL_CTX --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 +++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 78 +++++++++++++++++++++ core/libs/tests/GuestLibreSslSsl.cpp | 65 +++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 1 + 5 files changed, 156 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/Ssl.cpp create mode 100644 core/libs/tests/GuestLibreSslSsl.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index e4635639c7..48f64afc70 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ssl.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -159,6 +159,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_pkey_type PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_ssl_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSsl.cpp) + target_include_directories(guest_libressl_ssl_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_tests) + add_test(NAME guest_libressl_ssl COMMAND guest_libressl_ssl_tests) + set_tests_properties(guest_libressl_ssl PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index fc78f64744..ea3f081a5c 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1151,11 +1151,6 @@ int APS5_VABI SSL_get1_session_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_SSL_CTX_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_get_certificate_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp new file mode 100644 index 0000000000..c817055b09 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct LibreSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + int server; + void* s3; + void* d1; + void* param; + void* cipherList; + void* cert; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* session; + int verifyMode; + int error; + int errorCode; + void* ctx; + std::int64_t verifyResult; + int references; + int clientVersion; + unsigned int maxSendFragment; + char* tlsextHostname; + int tlsextStatusType; + void* initialCtx; + void* encReadCtx; + void* readHash; + LibreSslSslInternal* internal; +}; + +static_assert(sizeof(LibreSslSslInternal) == 432); +static_assert(offsetof(LibreSslSslInternal, rwstate) == 156); +static_assert(offsetof(LibreSslSslInternal, shutdown) == 168); +static_assert(sizeof(LibreSsl) == 232); +static_assert(offsetof(LibreSsl, rbio) == 16); +static_assert(offsetof(LibreSsl, wbio) == 24); +static_assert(offsetof(LibreSsl, session) == 128); +static_assert(offsetof(LibreSsl, ctx) == 152); +static_assert(offsetof(LibreSsl, verifyResult) == 160); +static_assert(offsetof(LibreSsl, internal) == 224); + +namespace { + +const LibreSsl& Connection(const LibreSsl* ssl, const char* function) { + if (!ssl) throw std::invalid_argument(std::string(function) + ": null connection"); + return *ssl; +} + +const LibreSslSslInternal& Internal(const LibreSsl* ssl, const char* function) { + const LibreSsl& connection = Connection(ssl, function); + if (!connection.internal) throw std::invalid_argument(std::string(function) + ": null internal state"); + return *connection.internal; +} + +} + +extern "C" { + +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).ctx; +} + +} diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp new file mode 100644 index 0000000000..db130edf56 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -0,0 +1,65 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct TestSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct TestSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + unsigned char beforeSession[88]; + void* session; + unsigned char beforeCtx[16]; + void* ctx; + std::int64_t verifyResult; + unsigned char beforeInternal[56]; + TestSslInternal* internal; +}; + +static_assert(sizeof(TestSslInternal) == 432 && offsetof(TestSslInternal, rwstate) == 156 && offsetof(TestSslInternal, shutdown) == 168); +static_assert(sizeof(TestSsl) == 232 && offsetof(TestSsl, session) == 128 && offsetof(TestSsl, ctx) == 152); +static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, internal) == 224); + +extern "C" { +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetSslCtx() { + int context = 0; + int session = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_SSL_CTX_nid_postfix(nullptr); }); + Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == nullptr); + ssl.ctx = &context; + ssl.session = &session; + Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); +} + +int main() { + TestGetSslCtx(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 271bc7c326..c310cdcaa2 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -26,6 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console +- [SSL_get_SSL_CTX](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach it. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 1de5b9180cf7e97d8241051de53bf705f242e73d Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:17 +0200 Subject: [PATCH 161/612] feat(libSceLibreSsl): implement SSL_get_rbio --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 13 +++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index ea3f081a5c..635cf6e0be 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1191,11 +1191,6 @@ int APS5_VABI SSL_get_privatekey_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_rbio_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_get_servername_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index c817055b09..3069c3cc5d 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -75,4 +75,8 @@ void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).ctx; } +void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).rbio; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index db130edf56..95fa53ff94 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -35,6 +35,7 @@ static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, intern extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); +void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -59,7 +60,19 @@ static void TestGetSslCtx() { Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); } +static void TestGetRbio() { + int read = 0; + int write = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_rbio_nid_postfix(nullptr); }); + Require(SSL_get_rbio_nid_postfix(&ssl) == nullptr); + ssl.rbio = &read; + ssl.wbio = &write; + Require(SSL_get_rbio_nid_postfix(&ssl) == &read); +} + int main() { TestGetSslCtx(); + TestGetRbio(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index c310cdcaa2..b24ea776af 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 292 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 288 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_get_SSL_CTX](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach it. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 942cd4900b0fcd54ce3e69d67609a254f6efe2ff Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:27 +0200 Subject: [PATCH 162/612] feat(libSceLibreSsl): implement SSL_get_wbio --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 15 +++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 635cf6e0be..431196e9ba 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1211,11 +1211,6 @@ int APS5_VABI SSL_get_verify_result_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_wbio_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 3069c3cc5d..2c544b2aa2 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -79,4 +79,8 @@ void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).rbio; } +void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).wbio; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 95fa53ff94..6547b2aac1 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -36,6 +36,7 @@ static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, intern extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); +void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -71,8 +72,22 @@ static void TestGetRbio() { Require(SSL_get_rbio_nid_postfix(&ssl) == &read); } +static void TestGetWbio() { + int read = 0; + int write = 0; + int buffer = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_wbio_nid_postfix(nullptr); }); + Require(SSL_get_wbio_nid_postfix(&ssl) == nullptr); + ssl.rbio = &read; + ssl.wbio = &write; + ssl.bbio = &buffer; + Require(SSL_get_wbio_nid_postfix(&ssl) == &write); +} + int main() { TestGetSslCtx(); TestGetRbio(); + TestGetWbio(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index b24ea776af..3e89c6937b 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 288 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 287 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_get_SSL_CTX, SSL_get_rbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 89bc7c5f70eb94d07ab735ca5779a34e30998ce3 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:37 +0200 Subject: [PATCH 163/612] feat(libSceLibreSsl): implement SSL_version --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 431196e9ba..f4757263e1 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1288,11 +1288,6 @@ int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void) { return 0; } -int APS5_VABI SSL_version_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_want_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 2c544b2aa2..a980c12199 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -83,4 +83,8 @@ void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).wbio; } +int APS5_VABI SSL_version_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).version; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 6547b2aac1..10945d54ba 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -37,6 +37,7 @@ extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); +int APS5_VABI SSL_version_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -85,9 +86,20 @@ static void TestGetWbio() { Require(SSL_get_wbio_nid_postfix(&ssl) == &write); } +static void TestVersion() { + TestSsl ssl {}; + RequireThrows([] { SSL_version_nid_postfix(nullptr); }); + Require(SSL_version_nid_postfix(&ssl) == 0); + ssl.version = 0x0303; + Require(SSL_version_nid_postfix(&ssl) == 0x0303); + ssl.version = 0xFEFF; + Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); + TestVersion(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 3e89c6937b..0f9606d56a 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 287 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 286 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From cc5364e5f3692ffe45a6a6e0f75571159cdc7711 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:47 +0200 Subject: [PATCH 164/612] feat(libSceLibreSsl): implement SSL_get_verify_result --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index f4757263e1..d36c93bb9c 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1206,11 +1206,6 @@ int APS5_VABI SSL_get_shutdown_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_verify_result_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index a980c12199..89c984e2d6 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -87,4 +87,8 @@ int APS5_VABI SSL_version_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).version; } +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSsl* ssl) { + return Connection(ssl, __func__).verifyResult; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 10945d54ba..2d1a43528c 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -38,6 +38,7 @@ void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -96,10 +97,23 @@ static void TestVersion() { Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); } +static void TestVerifyResult() { + TestSsl ssl {}; + RequireThrows([] { SSL_get_verify_result_nid_postfix(nullptr); }); + Require(SSL_get_verify_result_nid_postfix(&ssl) == 0); + ssl.verifyResult = 20; + Require(SSL_get_verify_result_nid_postfix(&ssl) == 20); + ssl.verifyResult = INT64_C(0x100000002); + Require(SSL_get_verify_result_nid_postfix(&ssl) == INT64_C(0x100000002)); + ssl.verifyResult = -1; + Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); TestVersion(); + TestVerifyResult(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 0f9606d56a..6aabd16fa1 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 286 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 285 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From c6930c6b2d1820474bbe446977f707f6286a04d0 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:57 +0200 Subject: [PATCH 165/612] feat(libSceLibreSsl): implement SSL_get_shutdown --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 16 ++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index d36c93bb9c..83f9b65bb3 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1201,11 +1201,6 @@ int APS5_VABI SSL_get_session_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_shutdown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 89c984e2d6..cb226a9590 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -91,4 +91,8 @@ std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSsl* ssl) { return Connection(ssl, __func__).verifyResult; } +int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSsl* ssl) { + return Internal(ssl, __func__).shutdown; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 2d1a43528c..2bca9bdbdb 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -39,6 +39,7 @@ void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); +int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -109,11 +110,26 @@ static void TestVerifyResult() { Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); } +static void TestGetShutdown() { + TestSslInternal internal {}; + TestSsl ssl {}; + RequireThrows([] { SSL_get_shutdown_nid_postfix(nullptr); }); + RequireThrows([&] { SSL_get_shutdown_nid_postfix(&ssl); }); + ssl.internal = &internal; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 0); + internal.rwstate = 3; + internal.shutdown = 1; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 1); + internal.shutdown = 3; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); TestVersion(); TestVerifyResult(); + TestGetShutdown(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 6aabd16fa1..f572c3e65e 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 285 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 284 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From f864427b1ce2e8086ab11df7aa32cbb44f7f6afa Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:55:07 +0200 Subject: [PATCH 166/612] feat(libSceLibreSsl): implement SSL_want --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 16 ++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 83f9b65bb3..05dd6672b3 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1278,11 +1278,6 @@ int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void) { return 0; } -int APS5_VABI SSL_want_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_write_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index cb226a9590..69780d7342 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -95,4 +95,8 @@ int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSsl* ssl) { return Internal(ssl, __func__).shutdown; } +int APS5_VABI SSL_want_nid_postfix(const LibreSsl* ssl) { + return Internal(ssl, __func__).rwstate; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 2bca9bdbdb..04abd2ad93 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -40,6 +40,7 @@ void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); +int APS5_VABI SSL_want_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -124,6 +125,20 @@ static void TestGetShutdown() { Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); } +static void TestWant() { + TestSslInternal internal {}; + TestSsl ssl {}; + RequireThrows([] { SSL_want_nid_postfix(nullptr); }); + RequireThrows([&] { SSL_want_nid_postfix(&ssl); }); + ssl.internal = &internal; + Require(SSL_want_nid_postfix(&ssl) == 0); + internal.rwstate = 1; + Require(SSL_want_nid_postfix(&ssl) == 1); + internal.shutdown = 2; + internal.rwstate = 3; + Require(SSL_want_nid_postfix(&ssl) == 3); +} + int main() { TestGetSslCtx(); TestGetRbio(); @@ -131,5 +146,6 @@ int main() { TestVersion(); TestVerifyResult(); TestGetShutdown(); + TestWant(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index f572c3e65e..b55bf911d3 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 284 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 283 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown, SSL_want](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 52c8fbdfa88ff88dba5272bf7e08020e2e61a3f7 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 06:01:21 +0100 Subject: [PATCH 167/612] refactor(libSceLibreSsl): declare the SSL accessor exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 53 +++++++++++++++++--- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 40 +-------------- core/libs/tests/GuestLibreSslSsl.cpp | 53 +++++++++----------- 3 files changed, 71 insertions(+), 75 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 3e3d9b7601..f91c451341 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -250,6 +250,45 @@ struct LibreSslX509StoreContext { void* exData; }; +struct LibreSslSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct LibreSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + int server; + void* s3; + void* d1; + void* param; + void* cipherList; + void* cert; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* session; + int verifyMode; + int error; + int errorCode; + void* ctx; + std::int64_t verifyResult; + int references; + int clientVersion; + unsigned int maxSendFragment; + char* tlsextHostname; + int tlsextStatusType; + void* initialCtx; + void* encReadCtx; + void* readHash; + LibreSslSslInternal* internal; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -501,7 +540,7 @@ int APS5_VABI SSL_free_nid_postfix(void); int APS5_VABI SSL_get0_alpn_selected_nid_postfix(void); int APS5_VABI SSL_get0_param_nid_postfix(void); int APS5_VABI SSL_get1_session_nid_postfix(void); -int APS5_VABI SSL_get_SSL_CTX_nid_postfix(void); +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_get_certificate_nid_postfix(void); int APS5_VABI SSL_get_current_cipher_nid_postfix(void); int APS5_VABI SSL_get_error_nid_postfix(void); @@ -512,12 +551,12 @@ int APS5_VABI SSL_get_fd_nid_postfix(void); int APS5_VABI SSL_get_peer_cert_chain_nid_postfix(void); int APS5_VABI SSL_get_peer_certificate_nid_postfix(void); int APS5_VABI SSL_get_privatekey_nid_postfix(void); -int APS5_VABI SSL_get_rbio_nid_postfix(void); +void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_get_servername_nid_postfix(void); int APS5_VABI SSL_get_session_nid_postfix(void); -int APS5_VABI SSL_get_shutdown_nid_postfix(void); -int APS5_VABI SSL_get_verify_result_nid_postfix(void); -int APS5_VABI SSL_get_wbio_nid_postfix(void); +int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSsl* ssl); +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSsl* ssl); +void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_library_init_nid_postfix(void); void APS5_VABI SSL_load_error_strings_nid_postfix(void); int APS5_VABI SSL_new_nid_postfix(void); @@ -535,8 +574,8 @@ int APS5_VABI SSL_set_verify_nid_postfix(void); int APS5_VABI SSL_shutdown_nid_postfix(void); int APS5_VABI SSL_use_PrivateKey_ASN1_nid_postfix(void); int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void); -int APS5_VABI SSL_version_nid_postfix(void); -int APS5_VABI SSL_want_nid_postfix(void); +int APS5_VABI SSL_version_nid_postfix(const LibreSsl* ssl); +int APS5_VABI SSL_want_nid_postfix(const LibreSsl* ssl); int APS5_VABI SSL_write_nid_postfix(void); std::uint64_t APS5_VABI SSLeay_nid_postfix(void); int APS5_VABI SSLv23_client_method_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 69780d7342..74c3e5aa99 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -1,48 +1,10 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslSslInternal { - unsigned char beforeRwstate[156]; - int rwstate; - unsigned char beforeShutdown[8]; - int shutdown; - unsigned char rest[260]; -}; - -struct LibreSsl { - int version; - const void* method; - void* rbio; - void* wbio; - void* bbio; - int server; - void* s3; - void* d1; - void* param; - void* cipherList; - void* cert; - unsigned int sidCtxLength; - unsigned char sidCtx[32]; - void* session; - int verifyMode; - int error; - int errorCode; - void* ctx; - std::int64_t verifyResult; - int references; - int clientVersion; - unsigned int maxSendFragment; - char* tlsextHostname; - int tlsextStatusType; - void* initialCtx; - void* encReadCtx; - void* readHash; - LibreSslSslInternal* internal; -}; - static_assert(sizeof(LibreSslSslInternal) == 432); static_assert(offsetof(LibreSslSslInternal, rwstate) == 156); static_assert(offsetof(LibreSslSslInternal, shutdown) == 168); diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 04abd2ad93..e2549fab2f 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -33,14 +34,8 @@ static_assert(sizeof(TestSslInternal) == 432 && offsetof(TestSslInternal, rwstat static_assert(sizeof(TestSsl) == 232 && offsetof(TestSsl, session) == 128 && offsetof(TestSsl, ctx) == 152); static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, internal) == 224); -extern "C" { -void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); -void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); -void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); -int APS5_VABI SSL_version_nid_postfix(const TestSsl*); -std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); -int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); -int APS5_VABI SSL_want_nid_postfix(const TestSsl*); +static const LibreSsl* Ssl(const TestSsl* ssl) { + return reinterpret_cast(ssl); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -59,10 +54,10 @@ static void TestGetSslCtx() { int session = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_SSL_CTX_nid_postfix(nullptr); }); - Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == nullptr); + Require(SSL_get_SSL_CTX_nid_postfix(Ssl(&ssl)) == nullptr); ssl.ctx = &context; ssl.session = &session; - Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); + Require(SSL_get_SSL_CTX_nid_postfix(Ssl(&ssl)) == &context); } static void TestGetRbio() { @@ -70,10 +65,10 @@ static void TestGetRbio() { int write = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_rbio_nid_postfix(nullptr); }); - Require(SSL_get_rbio_nid_postfix(&ssl) == nullptr); + Require(SSL_get_rbio_nid_postfix(Ssl(&ssl)) == nullptr); ssl.rbio = &read; ssl.wbio = &write; - Require(SSL_get_rbio_nid_postfix(&ssl) == &read); + Require(SSL_get_rbio_nid_postfix(Ssl(&ssl)) == &read); } static void TestGetWbio() { @@ -82,61 +77,61 @@ static void TestGetWbio() { int buffer = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_wbio_nid_postfix(nullptr); }); - Require(SSL_get_wbio_nid_postfix(&ssl) == nullptr); + Require(SSL_get_wbio_nid_postfix(Ssl(&ssl)) == nullptr); ssl.rbio = &read; ssl.wbio = &write; ssl.bbio = &buffer; - Require(SSL_get_wbio_nid_postfix(&ssl) == &write); + Require(SSL_get_wbio_nid_postfix(Ssl(&ssl)) == &write); } static void TestVersion() { TestSsl ssl {}; RequireThrows([] { SSL_version_nid_postfix(nullptr); }); - Require(SSL_version_nid_postfix(&ssl) == 0); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0); ssl.version = 0x0303; - Require(SSL_version_nid_postfix(&ssl) == 0x0303); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0x0303); ssl.version = 0xFEFF; - Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0xFEFF); } static void TestVerifyResult() { TestSsl ssl {}; RequireThrows([] { SSL_get_verify_result_nid_postfix(nullptr); }); - Require(SSL_get_verify_result_nid_postfix(&ssl) == 0); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == 0); ssl.verifyResult = 20; - Require(SSL_get_verify_result_nid_postfix(&ssl) == 20); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == 20); ssl.verifyResult = INT64_C(0x100000002); - Require(SSL_get_verify_result_nid_postfix(&ssl) == INT64_C(0x100000002)); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == INT64_C(0x100000002)); ssl.verifyResult = -1; - Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == -1); } static void TestGetShutdown() { TestSslInternal internal {}; TestSsl ssl {}; RequireThrows([] { SSL_get_shutdown_nid_postfix(nullptr); }); - RequireThrows([&] { SSL_get_shutdown_nid_postfix(&ssl); }); + RequireThrows([&] { SSL_get_shutdown_nid_postfix(Ssl(&ssl)); }); ssl.internal = &internal; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 0); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 0); internal.rwstate = 3; internal.shutdown = 1; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 1); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 1); internal.shutdown = 3; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 3); } static void TestWant() { TestSslInternal internal {}; TestSsl ssl {}; RequireThrows([] { SSL_want_nid_postfix(nullptr); }); - RequireThrows([&] { SSL_want_nid_postfix(&ssl); }); + RequireThrows([&] { SSL_want_nid_postfix(Ssl(&ssl)); }); ssl.internal = &internal; - Require(SSL_want_nid_postfix(&ssl) == 0); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 0); internal.rwstate = 1; - Require(SSL_want_nid_postfix(&ssl) == 1); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 1); internal.shutdown = 2; internal.rwstate = 3; - Require(SSL_want_nid_postfix(&ssl) == 3); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 3); } int main() { From 53c692afa979ec0fce5a41ff3a26bdf16d0aca52 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:00:27 +0200 Subject: [PATCH 168/612] feat(libSceLibreSsl): implement SSL_CTX_get_verify_mode --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 ++++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 53 ++++++++++++++++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 60 +++++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 1 + 5 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/SslCtx.cpp create mode 100644 core/libs/tests/GuestLibreSslSslCtx.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index e4635639c7..046616f9c5 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SslCtx.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -159,6 +159,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_pkey_type PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_ssl_ctx_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSslCtx.cpp) + target_include_directories(guest_libressl_ssl_ctx_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_ctx_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_ctx_tests) + add_test(NAME guest_libressl_ssl_ctx COMMAND guest_libressl_ssl_ctx_tests) + set_tests_properties(guest_libressl_ssl_ctx PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl_ctx PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index fc78f64744..ffeb462d92 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -966,11 +966,6 @@ int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_load_verify_locations_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp new file mode 100644 index 0000000000..f4566a99a9 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct LibreSslCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + int references; + void* extraCerts; + int verifyMode; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* param; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + LibreSslCtxInternal* internal; +}; + +static_assert(sizeof(LibreSslCtxInternal) == 464); +static_assert(offsetof(LibreSslCtxInternal, quietShutdown) == 292); +static_assert(sizeof(LibreSslCtx) == 120); +static_assert(offsetof(LibreSslCtx, certStore) == 16); +static_assert(offsetof(LibreSslCtx, verifyMode) == 48); +static_assert(offsetof(LibreSslCtx, defaultPasswdCallback) == 96); +static_assert(offsetof(LibreSslCtx, defaultPasswdCallbackUserdata) == 104); +static_assert(offsetof(LibreSslCtx, internal) == 112); + +namespace { + +LibreSslCtx& Context(LibreSslCtx* ctx, const char* function) { + if (!ctx) throw std::invalid_argument(std::string(function) + ": null context"); + return *ctx; +} + +} + +extern "C" { + +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx) { + return Context(ctx, __func__).verifyMode; +} + +} diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp new file mode 100644 index 0000000000..acbcf82c94 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -0,0 +1,60 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +struct TestCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct TestCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + unsigned char beforeVerifyMode[16]; + int verifyMode; + unsigned char beforeCallback[44]; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + TestCtxInternal* internal; +}; + +static_assert(sizeof(TestCtxInternal) == 464 && offsetof(TestCtxInternal, quietShutdown) == 292); +static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && offsetof(TestCtx, defaultPasswdCallback) == 96); + +extern "C" { +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL_CTX check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetVerifyMode() { + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_get_verify_mode_nid_postfix(nullptr); }); + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0); + ctx.verifyMode = 0x03; + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0x03); + ctx.verifyMode = -1; + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); +} + +int main() { + TestGetVerifyMode(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 271bc7c326..76a9cd87ef 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -26,6 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console +- [SSL_CTX_get_verify_mode](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach it, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From de8ed4aeac367d769a4aaa0238a723d0989d6691 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:22 +0200 Subject: [PATCH 169/612] feat(libSceLibreSsl): implement SSL_CTX_get_cert_store --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index ffeb462d92..50141ae045 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -956,11 +956,6 @@ int APS5_VABI SSL_CTX_free_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_get_cert_store_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index f4566a99a9..446eff72de 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -50,4 +50,8 @@ int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx) { return Context(ctx, __func__).verifyMode; } +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx) { + return Context(ctx, __func__).certStore; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index acbcf82c94..df6c8d1c3d 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -31,6 +31,7 @@ static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && o extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -54,7 +55,20 @@ static void TestGetVerifyMode() { Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); } +static void TestGetCertStore() { + int store = 0; + int ciphers = 0; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_get_cert_store_nid_postfix(nullptr); }); + Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == nullptr); + ctx.cipherList = &ciphers; + ctx.certStore = &store; + ctx.sessionTimeout = -1; + Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); +} + int main() { TestGetVerifyMode(); + TestGetCertStore(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 76a9cd87ef..c7e7e25d9a 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 292 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 288 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_CTX_get_verify_mode](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach it, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 46e06a19c8d26ec23e14930f404690454256ca4f Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:31 +0200 Subject: [PATCH 170/612] feat(libSceLibreSsl): implement SSL_CTX_set_quiet_shutdown --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 6 ++++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 18 ++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 50141ae045..1ddb37e32e 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1046,11 +1046,6 @@ int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 446eff72de..2a86c75d25 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -54,4 +54,10 @@ void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx) { return Context(ctx, __func__).certStore; } +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode) { + LibreSslCtx& context = Context(ctx, __func__); + if (!context.internal) throw std::invalid_argument(std::string(__func__) + ": null internal state"); + context.internal->quietShutdown = mode; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index df6c8d1c3d..38f3bb5fad 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -32,6 +32,7 @@ static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && o extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -67,8 +68,25 @@ static void TestGetCertStore() { Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); } +static void TestSetQuietShutdown() { + TestCtxInternal internal {}; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_set_quiet_shutdown_nid_postfix(nullptr, 1); }); + RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); }); + ctx.internal = &internal; + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); + Require(internal.quietShutdown == 1); + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, INT_MIN); + Require(internal.quietShutdown == INT_MIN); + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 0); + Require(internal.quietShutdown == 0); + for (unsigned char byte : internal.beforeQuietShutdown) Require(byte == 0); + for (unsigned char byte : internal.rest) Require(byte == 0); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); + TestSetQuietShutdown(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index c7e7e25d9a..21462325ec 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 288 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 287 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From d167df501871bd12c921f18552f8a9039f80a327 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:41 +0200 Subject: [PATCH 171/612] feat(libSceLibreSsl): implement SSL_CTX_set_default_passwd_cb --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 1ddb37e32e..ca0e492ea6 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1016,11 +1016,6 @@ int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 2a86c75d25..573dbf8978 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -60,4 +60,8 @@ void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode context.internal->quietShutdown = mode; } +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* callback) { + Context(ctx, __func__).defaultPasswdCallback = callback; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index 38f3bb5fad..613aed964a 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -33,6 +33,7 @@ extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -84,9 +85,20 @@ static void TestSetQuietShutdown() { for (unsigned char byte : internal.rest) Require(byte == 0); } +static void TestSetPasswordCallback() { + int callback = 0; + TestCtx ctx {}; + RequireThrows([&] { SSL_CTX_set_default_passwd_cb_nid_postfix(nullptr, &callback); }); + SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, &callback); + Require(ctx.defaultPasswdCallback == &callback && ctx.defaultPasswdCallbackUserdata == nullptr); + SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, nullptr); + Require(ctx.defaultPasswdCallback == nullptr); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); TestSetQuietShutdown(); + TestSetPasswordCallback(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 21462325ec..6a33540873 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 287 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 286 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 922a7d100c68e08cd0141573d2d6308a9c5a13d5 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:51 +0200 Subject: [PATCH 172/612] feat(libSceLibreSsl): implement SSL_CTX_set_default_passwd_cb_userdata --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index ca0e492ea6..c1d7220d87 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1016,11 +1016,6 @@ int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 573dbf8978..b70a0d9f12 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -64,4 +64,8 @@ void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* Context(ctx, __func__).defaultPasswdCallback = callback; } +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* ctx, void* userdata) { + Context(ctx, __func__).defaultPasswdCallbackUserdata = userdata; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index 613aed964a..fe76a282de 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -34,6 +34,7 @@ int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(TestCtx*, void*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -95,10 +96,21 @@ static void TestSetPasswordCallback() { Require(ctx.defaultPasswdCallback == nullptr); } +static void TestSetPasswordUserdata() { + int userdata = 0; + TestCtx ctx {}; + RequireThrows([&] { SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(nullptr, &userdata); }); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, &userdata); + Require(ctx.defaultPasswdCallbackUserdata == &userdata && ctx.defaultPasswdCallback == nullptr && ctx.internal == nullptr); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, nullptr); + Require(ctx.defaultPasswdCallbackUserdata == nullptr); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); TestSetQuietShutdown(); TestSetPasswordCallback(); + TestSetPasswordUserdata(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 6a33540873..8d09701219 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 286 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 285 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -26,7 +26,7 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb, SSL_CTX_set_default_passwd_cb_userdata](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From a4817006c90c494c003d740b0ad6989f92ab0425 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 05:59:27 +0100 Subject: [PATCH 173/612] refactor(libSceLibreSsl): declare the SSL_CTX accessor exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 32 +++++++++++++++--- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 23 +------------ core/libs/tests/GuestLibreSslSslCtx.cpp | 35 +++++++++----------- 3 files changed, 44 insertions(+), 46 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 3e3d9b7601..9988e45134 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -250,6 +250,28 @@ struct LibreSslX509StoreContext { void* exData; }; +struct LibreSslCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct LibreSslCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + int references; + void* extraCerts; + int verifyMode; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* param; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + LibreSslCtxInternal* internal; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -460,10 +482,10 @@ int APS5_VABI SSL_CTX_callback_ctrl_nid_postfix(void); int APS5_VABI SSL_CTX_check_private_key_nid_postfix(void); int APS5_VABI SSL_CTX_ctrl_nid_postfix(void); int APS5_VABI SSL_CTX_free_nid_postfix(void); -int APS5_VABI SSL_CTX_get_cert_store_nid_postfix(void); +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx); int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void); int APS5_VABI SSL_CTX_get_ex_new_index_nid_postfix(std::int64_t argl, void* argp, LibreSslExNew newFunction, LibreSslExDup dupFunction, LibreSslExFree freeFunction); -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void); +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx); int APS5_VABI SSL_CTX_load_verify_locations_nid_postfix(void); int APS5_VABI SSL_CTX_load_verify_mem_nid_postfix(void); int APS5_VABI SSL_CTX_new_nid_postfix(void); @@ -475,13 +497,13 @@ int APS5_VABI SSL_CTX_set_cert_store_nid_postfix(void); int APS5_VABI SSL_CTX_set_cert_verify_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_cipher_list_nid_postfix(void); int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void); +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* callback); +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* ctx, void* userdata); int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void); int APS5_VABI SSL_CTX_set_info_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_msg_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(void); +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode); int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void); int APS5_VABI SSL_CTX_set_verify_nid_postfix(void); int APS5_VABI SSL_CTX_use_PrivateKey_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index b70a0d9f12..99eb4fa5a1 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -1,31 +1,10 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslCtxInternal { - unsigned char beforeQuietShutdown[292]; - int quietShutdown; - unsigned char rest[168]; -}; - -struct LibreSslCtx { - const void* method; - void* cipherList; - void* certStore; - std::int64_t sessionTimeout; - int references; - void* extraCerts; - int verifyMode; - unsigned int sidCtxLength; - unsigned char sidCtx[32]; - void* param; - void* defaultPasswdCallback; - void* defaultPasswdCallbackUserdata; - LibreSslCtxInternal* internal; -}; - static_assert(sizeof(LibreSslCtxInternal) == 464); static_assert(offsetof(LibreSslCtxInternal, quietShutdown) == 292); static_assert(sizeof(LibreSslCtx) == 120); diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index fe76a282de..3e77a25e02 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -29,12 +30,8 @@ struct TestCtx { static_assert(sizeof(TestCtxInternal) == 464 && offsetof(TestCtxInternal, quietShutdown) == 292); static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && offsetof(TestCtx, defaultPasswdCallback) == 96); -extern "C" { -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); -void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); -void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); -void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); -void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(TestCtx*, void*); +static LibreSslCtx* Context(TestCtx* ctx) { + return reinterpret_cast(ctx); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -51,11 +48,11 @@ template static void RequireThrows(TAction action, std::source static void TestGetVerifyMode() { TestCtx ctx {}; RequireThrows([] { SSL_CTX_get_verify_mode_nid_postfix(nullptr); }); - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == 0); ctx.verifyMode = 0x03; - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0x03); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == 0x03); ctx.verifyMode = -1; - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == -1); } static void TestGetCertStore() { @@ -63,24 +60,24 @@ static void TestGetCertStore() { int ciphers = 0; TestCtx ctx {}; RequireThrows([] { SSL_CTX_get_cert_store_nid_postfix(nullptr); }); - Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == nullptr); + Require(SSL_CTX_get_cert_store_nid_postfix(Context(&ctx)) == nullptr); ctx.cipherList = &ciphers; ctx.certStore = &store; ctx.sessionTimeout = -1; - Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); + Require(SSL_CTX_get_cert_store_nid_postfix(Context(&ctx)) == &store); } static void TestSetQuietShutdown() { TestCtxInternal internal {}; TestCtx ctx {}; RequireThrows([] { SSL_CTX_set_quiet_shutdown_nid_postfix(nullptr, 1); }); - RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); }); + RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 1); }); ctx.internal = &internal; - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 1); Require(internal.quietShutdown == 1); - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, INT_MIN); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), INT_MIN); Require(internal.quietShutdown == INT_MIN); - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 0); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 0); Require(internal.quietShutdown == 0); for (unsigned char byte : internal.beforeQuietShutdown) Require(byte == 0); for (unsigned char byte : internal.rest) Require(byte == 0); @@ -90,9 +87,9 @@ static void TestSetPasswordCallback() { int callback = 0; TestCtx ctx {}; RequireThrows([&] { SSL_CTX_set_default_passwd_cb_nid_postfix(nullptr, &callback); }); - SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, &callback); + SSL_CTX_set_default_passwd_cb_nid_postfix(Context(&ctx), &callback); Require(ctx.defaultPasswdCallback == &callback && ctx.defaultPasswdCallbackUserdata == nullptr); - SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, nullptr); + SSL_CTX_set_default_passwd_cb_nid_postfix(Context(&ctx), nullptr); Require(ctx.defaultPasswdCallback == nullptr); } @@ -100,9 +97,9 @@ static void TestSetPasswordUserdata() { int userdata = 0; TestCtx ctx {}; RequireThrows([&] { SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(nullptr, &userdata); }); - SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, &userdata); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(Context(&ctx), &userdata); Require(ctx.defaultPasswdCallbackUserdata == &userdata && ctx.defaultPasswdCallback == nullptr && ctx.internal == nullptr); - SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, nullptr); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(Context(&ctx), nullptr); Require(ctx.defaultPasswdCallbackUserdata == nullptr); } From 067ab6f48168cd26ca20a034e771314ad5b91295 Mon Sep 17 00:00:00 2001 From: rooklz <175737347+rooklz@users.noreply.github.com> Date: Sat, 31 Oct 2026 15:41:59 +0000 Subject: [PATCH 174/612] feat(net): return EOPNOTSUPP for emulation debug settings Match sceNetEmulationGet/Set on retail-unavailable debug settings (shadPS4 pattern). --- core/libs/prx/libSceNet/Export.cpp | 10 ++++++---- core/libs/tests/GuestNetThread.cpp | 4 ++++ docs/dev/debt/libSceNet.md | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index fed529568f..cdb064f202 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -3484,13 +3484,15 @@ int APS5_VABI sceNetDumpRead(std::int32_t dumpId, void* buffer, std::int32_t buf } int APS5_VABI sceNetEmulationDebugSettingsSet(const NetEmulationParameter* parameter, std::uint32_t flags) { - NotImplemented_nid_no_patch(__func__); - return 0; + (void)parameter; + (void)flags; + return fail(NET_EOPNOTSUPP); } int APS5_VABI sceNetEmulationDebugSettingsGet(NetEmulationParameter* parameter, std::uint32_t flags) { - NotImplemented_nid_no_patch(__func__); - return 0; + (void)parameter; + (void)flags; + return fail(NET_EOPNOTSUPP); } int APS5_VABI sceNetEmulationSet(const NetEmulationParameter*, std::uint32_t) { diff --git a/core/libs/tests/GuestNetThread.cpp b/core/libs/tests/GuestNetThread.cpp index abdc2c22d8..66f662a6a7 100644 --- a/core/libs/tests/GuestNetThread.cpp +++ b/core/libs/tests/GuestNetThread.cpp @@ -68,6 +68,10 @@ int main() { Require(sceNetEmulationGet(nullptr, 0) == (NetError | Eopnotsupp) && *sceNetErrnoLoc() == Eopnotsupp, "sceNetEmulationGet did not return EOPNOTSUPP"); *sceNetErrnoLoc() = 0; Require(sceNetEmulationSet(nullptr, 0) == (NetError | Eopnotsupp) && *sceNetErrnoLoc() == Eopnotsupp, "sceNetEmulationSet did not return EOPNOTSUPP"); + *sceNetErrnoLoc() = 0; + Require(sceNetEmulationDebugSettingsGet(nullptr, 0) == (NetError | Eopnotsupp) && *sceNetErrnoLoc() == Eopnotsupp, "sceNetEmulationDebugSettingsGet did not return EOPNOTSUPP"); + *sceNetErrnoLoc() = 0; + Require(sceNetEmulationDebugSettingsSet(nullptr, 0) == (NetError | Eopnotsupp) && *sceNetErrnoLoc() == Eopnotsupp, "sceNetEmulationDebugSettingsSet did not return EOPNOTSUPP"); std::puts("net thread tests passed"); return 0; } diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index 86749242b8..ce13a0b309 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -32,6 +32,7 @@ - [Resolver-connect, interface and diagnostic queries, WLAN configuration and DHCP queries](../../../core/libs/prx/libSceNet/Export.cpp) retain unsupported implementations. Their restored argument lists still reference incomplete connection, interface, route, WLAN and statistics types. Kernel ioctl payloads need reconstruction from their handlers or consumers; buffer lengths alone do not establish field types. - [sceNetThreadCreate, sceNetThreadExit, sceNetThreadJoin, sceNetEmulationGet and sceNetEmulationSet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the signatures, the `{ entry, arg, reserved, flags }` parameter block and the `EOPNOTSUPP` of the two emulation calls ("not available on retail units") follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/net/net.cpp) (PS4), not a PS5 measurement. The block is read on the new thread, so it must stay valid until the thread starts, `flags` is ignored (shadPS4 ends the thread with `pthread_exit` when bit 0 is set, which ends it the same way), and a failing `scePthreadCreate` or `scePthreadJoin` result is returned as it is, where shadPS4 also sets the net errno from it +- [sceNetEmulationDebugSettingsGet and sceNetEmulationDebugSettingsSet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - both return `EOPNOTSUPP` by analogy with `sceNetEmulationGet`/`Set`, whose unsupported-operation policy ("not available on retail units") the pinned [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/net/net.cpp) revision defines. That revision does not define this debug-settings pair, so their signatures and the `EOPNOTSUPP` result are not sourced from it and were not measured on a PS5 - [sceHttp2GetMemoryPoolStats, sceSslGetMemoryPoolStats, sceNetGetMemoryPoolStats](../../../core/libs/prx/libSceNet/Export.cpp) (libSceHttp2, libSceSsl, libSceNet) - the signatures `(int id, stats*)`, the layouts (pool size, peak and current use, and for Http2 and Ssl a 32-bit reserved field) and the error codes were measured on a PS5 (firmware 13.60). The pool size is the one given at init or pool creation. Http2 and Net allocate nothing from these pools. Ssl counts its certificate backend allocations and returned data against its pool; C++ bookkeeping is outside that budget. All three initially report zero use, where the console already reports 0x15C0 bytes for Ssl, 0x1640 for Http2 and 4 for a Net pool. Ssl's allocation sizes and peak usage do not reproduce the console allocator - [sceNetClearDnsCache](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the library has no DNS cache to clear because lookups use the host resolver. The host resolver cache is left unchanged. - [sceNetEtherStrton](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - strings shorter than 17 bytes return EINVAL without changing the output, rather than reading past the terminating NUL as in the [firmware 13.60 probe](https://gist.github.com/orezraey/789290577123af3c45b340d6db6f395c/71e0a6215c7ee86f49fdb31de989dd2b50720b7a). Non-ASCII digit bytes throw because their decoding is unmeasured; other firmware versions remain unverified. Fixed character pairs, ignored separators, alphabetic values beyond f, non-alphanumeric ASCII values of 0x7f, and null-pointer EINVAL follow those measurements, not the strict [shadPS4 parser](https://github.com/shadps4-emu/shadPS4/blob/d615b3887ba29025a59242e1a26f5b68bb176158/src/core/libraries/net/net.cpp#L1274-L1297) From 342117a322fdf8bbfa672050cde378e25965149d Mon Sep 17 00:00:00 2001 From: Bipin Date: Sun, 1 Nov 2026 09:51:06 +0530 Subject: [PATCH 175/612] feat(libkernel_sys): implement adjtime export --- core/libs/CMakeLists.txt | 10 ++++ .../prx/libkernel_sys/AdditionalExports.cpp | 26 +++++++++- .../include/AdditionalExports.hpp | 2 +- core/libs/tests/GuestKernelSysAdjtime.cpp | 47 +++++++++++++++++++ docs/dev/debt/libkernel_sys.md | 6 +-- 5 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 core/libs/tests/GuestKernelSysAdjtime.cpp diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index c9b739fb83..51a8738db8 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -502,6 +502,16 @@ if(BUILD_TESTING) configure_windows_unwind(guest_kernel_sys_barrier_tests) add_test(NAME guest_kernel_sys_barrier COMMAND guest_kernel_sys_barrier_tests) set_tests_properties(guest_kernel_sys_barrier PROPERTIES TIMEOUT 20) + add_test_executable(guest_kernel_sys_adjtime_tests tests/GuestKernelSysAdjtime.cpp) + target_include_directories(guest_kernel_sys_adjtime_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_adjtime_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_adjtime_tests) + add_test(NAME guest_kernel_sys_adjtime COMMAND guest_kernel_sys_adjtime_tests) + set_tests_properties(guest_kernel_sys_adjtime PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_adjtime PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_kernel_sys_pthread_tests tests/GuestKernelSysPthread.cpp) target_include_directories(guest_kernel_sys_pthread_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_kernel_sys_pthread_tests PRIVATE libkernel_sys libkernel libc) diff --git a/core/libs/prx/libkernel_sys/AdditionalExports.cpp b/core/libs/prx/libkernel_sys/AdditionalExports.cpp index 38cfeae140..59156b17ed 100644 --- a/core/libs/prx/libkernel_sys/AdditionalExports.cpp +++ b/core/libs/prx/libkernel_sys/AdditionalExports.cpp @@ -1,5 +1,9 @@ #include "prx/libkernel_sys/include/AdditionalExports.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/Api.hpp" +#include +#include +#include extern "C" { @@ -203,8 +207,26 @@ int APS5_VABI _wait4_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI adjtime_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI adjtime_nid_postfix(const KernelTimeval* delta, KernelTimeval* oldDelta) { + constexpr int GuestEfault = 14; + KernelTimeval adjustment{}; + if (delta != nullptr && !GuestCopyIn_nid_no_patch(&adjustment, delta, sizeof(adjustment))) { + *__error_nid_postfix() = GuestEfault; + return -1; + } + if (delta != nullptr) { + char requested[48]; + std::snprintf(requested, sizeof(requested), "%lld.%06lld", static_cast(adjustment.tv_sec), static_cast(adjustment.tv_usec)); + throw std::runtime_error(std::string("adjtime: adjusting the clock by ") + requested + " is not supported, the host clock is not changed"); + } + if (oldDelta != nullptr) { + const KernelTimeval pending{}; + if (!GuestCopyOut_nid_no_patch(oldDelta, &pending, sizeof(pending))) { + *__error_nid_postfix() = GuestEfault; + return -1; + } + } + return 0; } int APS5_VABI libkernel_sysUnknown_0CV40L3tznI_nid_no_patch() { diff --git a/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp b/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp index 75c2c5e523..adfebec535 100644 --- a/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp +++ b/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp @@ -54,7 +54,7 @@ int APS5_VABI _dup2_nid_postfix(); int APS5_VABI _execve_nid_postfix(); int APS5_VABI _execvpe_nid_postfix(); int APS5_VABI _wait4_nid_postfix(); -int APS5_VABI adjtime_nid_postfix(); +int APS5_VABI adjtime_nid_postfix(const KernelTimeval* delta, KernelTimeval* oldDelta); int APS5_VABI libkernel_sysUnknown_0CV40L3tznI_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_1WXQlxMpnNA_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_1v23w7Rnkic_nid_no_patch(); diff --git a/core/libs/tests/GuestKernelSysAdjtime.cpp b/core/libs/tests/GuestKernelSysAdjtime.cpp new file mode 100644 index 0000000000..36c968b8f1 --- /dev/null +++ b/core/libs/tests/GuestKernelSysAdjtime.cpp @@ -0,0 +1,47 @@ +#include "prx/libkernel_sys/include/AdditionalExports.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/Api.hpp" + +#include +#include +#include +#include + +namespace { + +constexpr int GuestEfault = 14; +constexpr char SecondsAdjustment[] = "adjtime: adjusting the clock by 2.000000 is not supported, the host clock is not changed"; +constexpr char SubSecondAdjustment[] = "adjtime: adjusting the clock by 0.-00001 is not supported, the host clock is not changed"; + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "kernel sys adjtime: %s\n", message); + std::abort(); + } +} + +void RequireAdjustment(const KernelTimeval& delta, const char* expected) { + try { + adjtime_nid_postfix(&delta, nullptr); + } catch (const std::runtime_error& value) { + Require(std::strcmp(value.what(), expected) == 0, value.what()); + return; + } + Require(false, expected); +} + +} // namespace + +int main() { + Require(adjtime_nid_postfix(nullptr, nullptr) == 0, "adjtime with both pointers null did not succeed"); + KernelTimeval remaining{-3, -2}; + Require(adjtime_nid_postfix(nullptr, &remaining) == 0, "adjtime with a null delta did not succeed"); + Require(remaining.tv_sec == 0 && remaining.tv_usec == 0, "adjtime did not report a zero pending adjustment"); + Require(adjtime_nid_postfix(reinterpret_cast(1), nullptr) == -1 && *__error_nid_postfix() == GuestEfault, "adjtime with an unreadable delta did not fail with EFAULT"); + const KernelTimeval seconds{2, 0}; + RequireAdjustment(seconds, SecondsAdjustment); + const KernelTimeval subSecond{0, -1}; + RequireAdjustment(subSecond, SubSecondAdjustment); + std::puts("guest_kernel_sys_adjtime passed"); + return 0; +} diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 80bc89018e..19db4bcf78 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libkernel_sys additional exports](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) - 325 callable exports have import-resolution declarations only. Parameter lists, return types and behavior remain unverified. Parameterless `int` declarations are placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. Unresolved names use literal NID aliases; no runtime functionality or data objects are fabricated. Existing providers and exports supplied by open pull requests are excluded. Kernel variant imports already resolve through `libkernel`, then `libc`; existing providers remain unchanged. Six native data exports (`errno`, `__stack_chk_guard`, `_sigintr`, `__progname`, `environ`, and NID `AxgW94umKqU`) require separate storage, layout, TLS and ownership validation and are not added as callable aliases or fabricated globals. +- [libkernel_sys additional exports](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) - 324 callable exports have import-resolution declarations only. Parameter lists, return types and behavior remain unverified. Parameterless `int` declarations are placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. Unresolved names use literal NID aliases; no runtime functionality or data objects are fabricated. Existing providers and exports supplied by open pull requests are excluded. Kernel variant imports already resolve through `libkernel`, then `libc`; existing providers remain unchanged. Six native data exports (`errno`, `__stack_chk_guard`, `_sigintr`, `__progname`, `environ`, and NID `AxgW94umKqU`) require separate storage, layout, TLS and ownership validation and are not added as callable aliases or fabricated globals. | Name | NID | | --- | --- | @@ -61,7 +61,6 @@ | `_execve` | `-3nj+K1elI0` | | `_execvpe` | `ScfDTOqLOMw` | | `_wait4` | `RFlsu7nfopM` | -| `adjtime` | `2t3V7tfWY5s` | | `mmap2` | `tGtH2GTWbyE` | | `pthread_setschedother_np` | `ymCrwbFD4fk` | | `sceKernelAddDynlibEvent` | `dGFwUPT+BK8` | @@ -384,6 +383,7 @@ - [sceKernelGetProcessType](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - not measured on a console; as in shadPS4, the title's own pid, -1 and 0 are type 0 (big application) and any other pid is `ESRCH` - [cpuset, cpuset_getid, cpuset_setid](../../../core/libs/prx/libkernel_sys/Cpuset.cpp) (libkernel_sys) - console root IDs and inheritance are unverified; non-process scopes (TID, CPUSET, JAIL) throw as unmodelled as per-thread cpusets and hierarchy visibility are not tracked. CPU set assignment and queries follow FreeBSD 11.4 [kern_cpuset.c](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_cpuset.c): cpuset allocates sequential IDs from 2; cpuset_setid supports CPU_WHICH_PID for known IDs (or root 0, default 1); dynamic sets are single-process referenced and unlinked when vacated, returning ESRCH on lookup; cpuset_getid returns root 0 for CPU_LEVEL_ROOT and the assigned ID for CPU_LEVEL_CPUSET. Output writes use GuestCopyOut_nid_no_patch (EFAULT on bad pointers), with level/which/id errors returned before copyout; levels outside 1 to 3 return EINVAL where FreeBSD returns 0 with the base set ID; other which return EINVAL, unknown pids return ESRCH - [sceKernelSettimeofday](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - forwards to `settimeofday` and returns its result, or `0x80020000` plus `errno` for a failure, as shadPS4's [`sceKernelSettimeofday`](https://github.com/shadps4-emu/shadPS4/blob/7ea677e56d75bf3d90ace2b0a5144a665d1f38b6/src/core/libraries/kernel/time.cpp#L565-L571) maps its call through `ErrnoToSceKernelError`; that branch passes the call's return value, and shadPS4's own `settimeofday` never fails, so the errno form the other sce wrappers here use is taken. Nothing is measured on the console, and a valid time inherits `settimeofday`'s refusal to set the host clock +- [adjtime](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) checks like FreeBSD 11.4's [`sys_adjtime`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_ntptime.c#L955-L978) calling `kern_adjtime`: `EFAULT` for an unreadable `delta`, read before anything is stored. A valid adjustment throws, naming it, since the host clock is never adjusted and no pending-adjustment state is kept. A null `delta` reports the pending adjustment, which is zero, as FreeBSD's `time_adjtime` is before any `ntp_adjtime` access. FreeBSD applies an adjustment only with `PRIV_ADJTIME`, which a jailed process fails with `EPERM`; whether a title holds it on the console is unverified ### Unknown function info @@ -402,7 +402,7 @@ The NID set is from the inspected `libkernel_sys` ELF function-export table (defined `STT_FUNC` entries), not the older 3.20 inventory. The module-parameter words at `0x10` and `0x14` are `0x13590001` and `0x13600007`; the earlier 13.60 label refers to SDK-formatted metadata, not independently authenticated retail firmware or an introduction date. The [reproducible static audit](https://github.com/boykopovar/AnyPS5/pull/12367#issuecomment-6470603298) finds 1551 unique function NIDs with set digest `5f1668cc9c69e2950722a83151f31cf7aa94fb1552ab883e5067f43c94d63b16` and confirms all 328 proposed entries. The digest hashes sorted unique ASCII NIDs, each followed by LF. Named declarations are candidate spellings whose repository-computed NID exactly matches an observed function NID; the hashed table establishes neither plaintext spelling nor C ABI. -The 35 formerly literal aliases are named by the [repository-pinned aerolib table](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). There are now 165 named declarations and 160 unresolved NID aliases; every one of the 325 entries remains a throwing placeholder. `sceKernelHasTrinityMode` left the inventory for its `libkernel` implementation. +The 35 formerly literal aliases are named by the [repository-pinned aerolib table](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). There are now 164 named declarations and 160 unresolved NID aliases; every one of the 324 entries remains a throwing placeholder. `sceKernelHasTrinityMode` left the inventory for its `libkernel` implementation. The following eight candidate spellings are absent from that aerolib revision. Their name/NID pairs occur in the [public ps5rs identity database at commit `2304be5131221bae0dac24b9767cabffda33f747`](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv) and independently recompute to the function NIDs in the inspected table. This database supports candidate spelling only; it does not establish firmware membership, introduction version or ABI. From f2637b524670131d046c705a8909eefd2fb77f34 Mon Sep 17 00:00:00 2001 From: StoneDC <19905433+StoneDC@users.noreply.github.com> Date: Sun, 1 Nov 2026 13:32:47 +0800 Subject: [PATCH 176/612] test(libSceIcu): guard version string conversion extents --- core/libs/prx/libSceIcu/CMakeLists.txt | 13 +++ core/libs/tests/GuestIcuVersionExtents.cpp | 116 +++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 core/libs/tests/GuestIcuVersionExtents.cpp diff --git a/core/libs/prx/libSceIcu/CMakeLists.txt b/core/libs/prx/libSceIcu/CMakeLists.txt index 0bbdfb93f1..0f09fde969 100644 --- a/core/libs/prx/libSceIcu/CMakeLists.txt +++ b/core/libs/prx/libSceIcu/CMakeLists.txt @@ -9,6 +9,19 @@ target_sources(libSceIcu PRIVATE Space.cpp) target_sources(libSceIcu PRIVATE Utf8.cpp) target_sources(libSceIcu PRIVATE Resource.cpp) +if(BUILD_TESTING) + add_test_executable(guest_icu_version_extents_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuVersionExtents.cpp) + target_include_directories(guest_icu_version_extents_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_version_extents_tests PRIVATE libSceIcu libc) + configure_windows_unwind(guest_icu_version_extents_tests) + add_test(NAME guest_icu_version_extents COMMAND guest_icu_version_extents_tests) + set_tests_properties(guest_icu_version_extents PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_icu_version_extents PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + if(BUILD_TESTING) add_test_executable(guest_icu_utf8_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuUtf8.cpp) target_include_directories(guest_icu_utf8_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/tests/GuestIcuVersionExtents.cpp b/core/libs/tests/GuestIcuVersionExtents.cpp new file mode 100644 index 0000000000..bcafa93aba --- /dev/null +++ b/core/libs/tests/GuestIcuVersionExtents.cpp @@ -0,0 +1,116 @@ +#include "prx/libSceIcu/libSceIcu.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +static void Require(bool value, std::source_location location = std::source_location::current()) { + if (!value) { + std::fprintf(stderr, "boundary check failed at line %u\n", location.line()); + std::abort(); + } +} + +class Guarded { + unsigned char* memory; + std::size_t page; + std::size_t writable; +public: + explicit Guarded(std::size_t capacity = 1) { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); +#endif + writable = ((capacity + 128 + page - 1) / page) * page; +#ifdef _WIN32 + memory = static_cast(VirtualAlloc(nullptr, writable + page * 2, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(memory != nullptr); +#else + memory = static_cast(mmap(nullptr, writable + page * 2, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(memory != MAP_FAILED); +#endif + Reset(); + } + ~Guarded() { +#ifdef _WIN32 + Require(VirtualFree(memory, 0, MEM_RELEASE) != 0); +#else + Require(munmap(memory, writable + page * 2) == 0); +#endif + } + Guarded(const Guarded&) = delete; + Guarded& operator=(const Guarded&) = delete; + void Protect(bool readOnly) { +#ifdef _WIN32 + DWORD previous; + Require(VirtualProtect(memory + page, writable, readOnly ? PAGE_READONLY : PAGE_READWRITE, &previous) != 0); +#else + Require(mprotect(memory + page, writable, readOnly ? PROT_READ : PROT_READ | PROT_WRITE) == 0); +#endif + } + void Reset() { + Protect(false); + std::memset(memory + page, 0xa5, writable); + } + unsigned char* End(std::size_t count) { + Require(count + 64 <= writable); + return memory + page + writable - (std::max)(count, std::size_t{1}); + } + void Prefix(const void* address) const { + const auto* bytes = static_cast(address); + for (int index = 1; index <= 32; ++index) Require(bytes[-index] == 0xa5); + } +}; + +struct Version { const char* input; std::array bytes; const char* text; }; +int main() { + const Version versions[]{ + {"",{0,0,0,0},"0.0"},{"0",{0,0,0,0},"0.0"},{"1",{1,0,0,0},"1.0"}, + {"1.2.3.4",{1,2,3,4},"1.2.3.4"},{"255.0.255.1",{255,0,255,1},"255.0.255.1"}, + {"105.101.100.110",{105,101,100,110},"15.11.10.110"}, + {" +1.2.3.4",{1,2,3,4},"1.2.3.4"},{"1.2.3.4.567890123456789",{1,2,3,4},"1.2.3.4"}}; + std::size_t cases = 0; + for (const auto& version : versions) { + const auto length = std::strlen(version.input); + Guarded source(length+1), wide((length+1)*2), destination(4), text(20); + auto* input = reinterpret_cast(source.End(length+1)); + std::memcpy(input,version.input,length+1); + auto* units = reinterpret_cast(wide.End((length+1)*2)); + for (std::size_t i = 0; i <= length; ++i) std::construct_at(units+i,static_cast(version.input[i])); + source.Protect(true); wide.Protect(true); + for (bool utf16 : {false,true}) { + destination.Reset(); text.Reset(); + auto* bytes = destination.End(4); + if (utf16) u_versionFromUString_nid_postfix(bytes,units); + else u_versionFromString_nid_postfix(bytes,input); + Require(std::equal(version.bytes.begin(),version.bytes.end(),bytes)); + auto* output = reinterpret_cast(text.End(20)); + u_versionToString_nid_postfix(bytes,output); + Require(std::string(output) == version.text); + Require(std::memcmp(input,version.input,length+1) == 0); + source.Prefix(input); wide.Prefix(units); destination.Prefix(bytes); text.Prefix(output); + ++cases; + } + } + std::printf("%zu guarded ICU version conversions passed\n", cases); +} From cf77fa4bcc75cb3dc794d20bd243482d26e56586 Mon Sep 17 00:00:00 2001 From: Bipin Date: Sun, 1 Nov 2026 11:05:32 +0530 Subject: [PATCH 177/612] feat(libkernel_sys): implement _execve export --- core/libs/CMakeLists.txt | 10 ++ .../prx/libkernel_sys/AdditionalExports.cpp | 104 ++++++++++++- .../include/AdditionalExports.hpp | 2 +- core/libs/tests/GuestKernelSysExecve.cpp | 146 ++++++++++++++++++ docs/dev/debt/libkernel_sys.md | 6 +- 5 files changed, 262 insertions(+), 6 deletions(-) create mode 100644 core/libs/tests/GuestKernelSysExecve.cpp diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index c9b739fb83..ee21ad27d6 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -502,6 +502,16 @@ if(BUILD_TESTING) configure_windows_unwind(guest_kernel_sys_barrier_tests) add_test(NAME guest_kernel_sys_barrier COMMAND guest_kernel_sys_barrier_tests) set_tests_properties(guest_kernel_sys_barrier PROPERTIES TIMEOUT 20) + add_test_executable(guest_kernel_sys_execve_tests tests/GuestKernelSysExecve.cpp) + target_include_directories(guest_kernel_sys_execve_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_execve_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_execve_tests) + add_test(NAME guest_kernel_sys_execve COMMAND guest_kernel_sys_execve_tests) + set_tests_properties(guest_kernel_sys_execve PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_execve PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_kernel_sys_pthread_tests tests/GuestKernelSysPthread.cpp) target_include_directories(guest_kernel_sys_pthread_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_kernel_sys_pthread_tests PRIVATE libkernel_sys libkernel libc) diff --git a/core/libs/prx/libkernel_sys/AdditionalExports.cpp b/core/libs/prx/libkernel_sys/AdditionalExports.cpp index 38cfeae140..3ee3b1e24b 100644 --- a/core/libs/prx/libkernel_sys/AdditionalExports.cpp +++ b/core/libs/prx/libkernel_sys/AdditionalExports.cpp @@ -1,5 +1,13 @@ #include "prx/libkernel_sys/include/AdditionalExports.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/FilesystemError.hpp" + +#include +#include +#include +#include +#include extern "C" { @@ -191,8 +199,100 @@ int APS5_VABI _dup2_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _execve_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _execve_nid_postfix(const char* path, char* const* argv, char* const* envp) { + constexpr int GuestEfault = 14; + constexpr int GuestEacces = 13; + constexpr int GuestEnoexec = 8; + constexpr int GuestE2big = 7; + constexpr int GuestEnoent = 2; + constexpr int GuestEnametoolong = 63; + constexpr std::size_t GuestPathMax = 1024; + constexpr std::size_t GuestArgMax = 262144; + const auto fail = [](int error) { + *__error_nid_postfix() = error; + return -1; + }; + const auto gather = [](const char* guest, std::size_t maxLength, std::string& copy) -> int { + for (std::size_t read = 0; read < maxLength;) { + const auto address = reinterpret_cast(guest) + read; + const std::size_t boundary = 4096 - address % 4096; + const std::size_t bytes = boundary < maxLength - read ? boundary : maxLength - read; + char buffer[4096]; + if (!GuestCopyIn_nid_no_patch(buffer, guest + read, bytes)) return GuestEfault; + const auto end = static_cast(std::memchr(buffer, 0, bytes)); + if (end != nullptr) { + copy.append(buffer, static_cast(end - buffer)); + return 0; + } + copy.append(buffer, bytes); + read += bytes; + } + return GuestEnametoolong; + }; + if (argv == nullptr) return fail(GuestEfault); + std::string fname; + if (path != nullptr) { + const int error = gather(path, GuestPathMax, fname); + if (error != 0) return fail(error); + } + std::string text; + std::size_t stringspace = GuestArgMax; + for (char* const* slot = argv;; ++slot) { + char* argument = nullptr; + if (!GuestCopyIn_nid_no_patch(&argument, slot, sizeof(argument))) return fail(GuestEfault); + if (argument == nullptr) break; + text.clear(); + const int error = gather(argument, stringspace, text); + if (error != 0) return fail(error == GuestEnametoolong ? GuestE2big : error); + stringspace -= text.size() + 1; + } + if (envp != nullptr) { + for (char* const* slot = envp;; ++slot) { + char* variable = nullptr; + if (!GuestCopyIn_nid_no_patch(&variable, slot, sizeof(variable))) return fail(GuestEfault); + if (variable == nullptr) break; + text.clear(); + const int error = gather(variable, stringspace, text); + if (error != 0) return fail(error == GuestEnametoolong ? GuestE2big : error); + stringspace -= text.size() + 1; + } + } + if (path == nullptr || fname.empty()) return fail(GuestEnoent); + std::filesystem::path native; + if (const int error = ResolvePathError_nid_no_patch(fname.c_str(), native)) return fail(error); + std::error_code statusError; + const auto status = std::filesystem::status(native, statusError); + if (status.type() == std::filesystem::file_type::not_found) return fail(NotFoundError_nid_no_patch(native)); + if (statusError) return fail(FilesystemError(statusError)); +#ifdef _WIN32 + const bool executable = std::filesystem::is_regular_file(status); +#else + constexpr std::filesystem::perms execute = std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec; + const bool executable = (status.permissions() & execute) != std::filesystem::perms::none && std::filesystem::is_regular_file(status); +#endif + if (!executable) return fail(GuestEacces); + std::error_code sizeError; + const auto size = std::filesystem::file_size(native, sizeError); + if (sizeError) return fail(FilesystemError(sizeError)); + if (size == 0) return fail(GuestEnoexec); + if (!ExecutePermission_nid_no_patch(native)) return fail(GuestEacces); + std::ifstream image(native, std::ios::binary); + if (!image) return fail(GuestEacces); + unsigned char header[4]{}; + image.read(reinterpret_cast(header), sizeof(header)); + if (image.gcount() < static_cast(sizeof(header))) return fail(GuestEnoexec); + if (header[0] == '#' && header[1] == '!') { + throw std::runtime_error(std::string("execve: running the script ") + fname + " is not supported, script interpreters are not modelled"); + } + constexpr unsigned char ElfMagic[] = {0x7f, 0x45, 0x4c, 0x46}; + constexpr unsigned char Ps4SelfMagic[] = {0x4f, 0x15, 0x3d, 0x1d}; + constexpr unsigned char Ps5SelfMagic[] = {0x54, 0x14, 0xf5, 0xee}; + if (std::memcmp(header, ElfMagic, sizeof(ElfMagic)) == 0 || + std::memcmp(header, Ps4SelfMagic, sizeof(Ps4SelfMagic)) == 0 || + std::memcmp(header, Ps5SelfMagic, sizeof(Ps5SelfMagic)) == 0) { + throw std::runtime_error(std::string("execve: replacing the process image with ") + fname + " is not supported, program activation is not modelled"); + } + return fail(GuestEnoexec); } int APS5_VABI _execvpe_nid_postfix() { diff --git a/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp b/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp index 75c2c5e523..fb2c41bb12 100644 --- a/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp +++ b/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp @@ -51,7 +51,7 @@ int APS5_VABI __sys_workaround8849_nid_postfix(); int APS5_VABI __ubsan_handle_cfi_check_fail_abort_nid_postfix(); int APS5_VABI __ubsan_handle_cfi_check_fail_nid_postfix(); int APS5_VABI _dup2_nid_postfix(); -int APS5_VABI _execve_nid_postfix(); +int APS5_VABI _execve_nid_postfix(const char* path, char* const* argv, char* const* envp); int APS5_VABI _execvpe_nid_postfix(); int APS5_VABI _wait4_nid_postfix(); int APS5_VABI adjtime_nid_postfix(); diff --git a/core/libs/tests/GuestKernelSysExecve.cpp b/core/libs/tests/GuestKernelSysExecve.cpp new file mode 100644 index 0000000000..543a96620f --- /dev/null +++ b/core/libs/tests/GuestKernelSysExecve.cpp @@ -0,0 +1,146 @@ +#include "prx/libkernel_sys/include/AdditionalExports.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/Api.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int GuestE2big = 7; +constexpr int GuestEacces = 13; +constexpr int GuestEnoent = 2; +constexpr int GuestEnoexec = 8; +constexpr int GuestEfault = 14; +constexpr int GuestEnotdir = 20; +constexpr int GuestEnametoolong = 63; + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "kernel sys execve: %s\n", message); + std::abort(); + } +} + +void RequireFailure(int result, int error, const char* message) { + Require(result == -1 && *__error_nid_postfix() == error, message); +} + +template void RequireThrows(TCall call, const std::string& expected, const char* message) { + try { + call(); + } catch (const std::runtime_error& value) { + Require(value.what() == expected, message); + return; + } + Require(false, message); +} + +std::string RepeatedComponents(std::size_t length) { + std::string path = "a"; + while (path.size() + 2 <= length) path += "/a"; + return path; +} + +void WriteFile(const char* name, const char* content, std::streamsize bytes, std::filesystem::perms mode) { + std::ofstream file(name, std::ios::binary); + file.write(content, bytes); + file.close(); +#ifndef _WIN32 + std::filesystem::permissions(name, mode); +#else + (void)mode; +#endif +} + +} // namespace + +int main() { + char first[] = "first"; + char second[] = "second"; + char* const argv[] = {first, second, nullptr}; + char* const emptyArgv[] = {nullptr}; + char* const environment[] = {second, nullptr}; + char* const junkArgument[] = {reinterpret_cast(1), nullptr}; + RequireFailure(_execve_nid_postfix("", nullptr, nullptr), GuestEfault, "execve with a null argv did not fail with EFAULT"); + static char unterminated[1101]; + std::memset(unterminated, 'x', sizeof(unterminated)); + unterminated[sizeof(unterminated) - 1] = '\0'; + RequireFailure(_execve_nid_postfix(unterminated, nullptr, nullptr), GuestEfault, "execve did not check a null argv before the pathname"); + RequireFailure(_execve_nid_postfix("kernel_execve_missing.bin", reinterpret_cast(1), nullptr), GuestEfault, "execve with an unreadable argv array did not fail with EFAULT"); + RequireFailure(_execve_nid_postfix("kernel_execve_missing.bin", junkArgument, nullptr), GuestEfault, "execve with an unreadable argument did not fail with EFAULT"); + RequireFailure(_execve_nid_postfix("kernel_execve_missing.bin", emptyArgv, reinterpret_cast(1)), GuestEfault, "execve with an unreadable envp array did not fail with EFAULT"); + const std::string severalComponents = RepeatedComponents(1023); + Require(severalComponents.size() == 1023, "the 1023 byte pathname test built the wrong length"); + RequireFailure(_execve_nid_postfix(severalComponents.c_str(), emptyArgv, nullptr), GuestEnoent, "execve with a 1023 byte pathname did not reach the lookup"); + const std::string tooLongPath = "a" + std::string(1024, 'x'); + Require(tooLongPath.size() == 1025, "the 1024 byte unterminated pathname test built the wrong length"); + RequireFailure(_execve_nid_postfix(tooLongPath.c_str(), emptyArgv, nullptr), GuestEnametoolong, "execve with an unterminated 1024 byte pathname did not fail with ENAMETOOLONG"); + RequireFailure(_execve_nid_postfix("", emptyArgv, nullptr), GuestEnoent, "execve with an empty pathname did not fail with ENOENT"); + RequireFailure(_execve_nid_postfix(nullptr, emptyArgv, nullptr), GuestEnoent, "execve with a null pathname did not fail with ENOENT"); + RequireFailure(_execve_nid_postfix("kernel_execve_missing.bin", argv, environment), GuestEnoent, "execve of a missing file did not fail with ENOENT"); + constexpr char prefixBody[] = "prefix"; + WriteFile("kernel_execve_prefix.bin", prefixBody, sizeof(prefixBody) - 1, std::filesystem::perms::owner_all); + RequireFailure(_execve_nid_postfix("kernel_execve_prefix.bin/inside", argv, nullptr), GuestEnotdir, "execve through a file prefix did not fail with ENOTDIR"); + std::filesystem::create_directory("kernel_execve_dir"); + RequireFailure(_execve_nid_postfix("kernel_execve_dir", argv, nullptr), GuestEacces, "execve of a directory did not fail with EACCES"); + WriteFile("kernel_execve_zero.bin", "", 0, std::filesystem::perms::owner_all | std::filesystem::perms::group_all); + RequireFailure(_execve_nid_postfix("kernel_execve_zero.bin", argv, nullptr), GuestEnoexec, "execve of an empty file did not fail with ENOEXEC"); + WriteFile("kernel_execve_short.bin", "EL", 2, std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec); + RequireFailure(_execve_nid_postfix("kernel_execve_short.bin", argv, nullptr), GuestEnoexec, "execve of a two byte file did not fail with ENOEXEC"); + WriteFile("kernel_execve_plain.bin", "just data", 9, std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec); + RequireFailure(_execve_nid_postfix("kernel_execve_plain.bin", argv, nullptr), GuestEnoexec, "execve of a plain file did not fail with ENOEXEC"); +#ifndef _WIN32 + WriteFile("kernel_execve_unexec.bin", "just data", 9, std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::group_read | std::filesystem::perms::others_read); + RequireFailure(_execve_nid_postfix("kernel_execve_unexec.bin", argv, nullptr), GuestEacces, "execve without execute bits did not fail with EACCES"); +#endif + constexpr char elfBody[] = "\x7f" "ELFreference"; + WriteFile("kernel_execve_elf.bin", elfBody, sizeof(elfBody) - 1, std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec); + RequireThrows([&emptyArgv] { _execve_nid_postfix("kernel_execve_elf.bin", emptyArgv, nullptr); }, + "execve: replacing the process image with kernel_execve_elf.bin is not supported, program activation is not modelled", + "execve of an ELF image did not refuse replacement"); + constexpr unsigned char selfBody[] = {0x4f, 0x15, 0x3d, 0x1d, 'r', 'e', 'f', 'e', 'r', 'e', 'n', 'c', 'e'}; + WriteFile("kernel_execve_self.bin", reinterpret_cast(selfBody), sizeof(selfBody), std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec); + RequireThrows([&emptyArgv] { _execve_nid_postfix("kernel_execve_self.bin", emptyArgv, nullptr); }, + "execve: replacing the process image with kernel_execve_self.bin is not supported, program activation is not modelled", + "execve of a SELF image did not refuse replacement"); + constexpr unsigned char payloadBody[] = {0x54, 0x14, 0xf5, 0xee, 'r', 'e', 'f'}; + WriteFile("kernel_execve_payload.bin", reinterpret_cast(payloadBody), sizeof(payloadBody), std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec); + RequireThrows([&emptyArgv] { _execve_nid_postfix("kernel_execve_payload.bin", emptyArgv, nullptr); }, + "execve: replacing the process image with kernel_execve_payload.bin is not supported, program activation is not modelled", + "execve of a PS5 SELF payload did not refuse replacement"); + constexpr char scriptBody[] = "#!/bin/sh\necho double\n"; + WriteFile("kernel_execve_script.bin", scriptBody, sizeof(scriptBody) - 1, std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec); + RequireThrows([&emptyArgv] { _execve_nid_postfix("kernel_execve_script.bin", emptyArgv, nullptr); }, + "execve: running the script kernel_execve_script.bin is not supported, script interpreters are not modelled", + "execve of a script did not refuse the interpreter"); + const std::string oversized(2048, 'a'); + std::vector storage; + storage.reserve(128); + std::vector oversizedArgv; + oversizedArgv.reserve(129); + for (int index = 0; index < 128; ++index) { + storage.push_back(oversized); + oversizedArgv.push_back(storage.back().data()); + } + oversizedArgv.push_back(nullptr); + RequireFailure(_execve_nid_postfix("kernel_execve_missing.bin", oversizedArgv.data(), nullptr), GuestE2big, "execve with arguments beyond ARG_MAX did not fail with E2BIG"); + std::filesystem::remove("kernel_execve_prefix.bin"); + std::filesystem::remove("kernel_execve_zero.bin"); + std::filesystem::remove("kernel_execve_short.bin"); + std::filesystem::remove("kernel_execve_plain.bin"); + std::filesystem::remove("kernel_execve_unexec.bin"); + std::filesystem::remove("kernel_execve_elf.bin"); + std::filesystem::remove("kernel_execve_self.bin"); + std::filesystem::remove("kernel_execve_payload.bin"); + std::filesystem::remove("kernel_execve_script.bin"); + std::filesystem::remove("kernel_execve_dir"); + std::puts("guest_kernel_sys_execve passed"); + return 0; +} diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 80bc89018e..7819461d91 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libkernel_sys additional exports](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) - 325 callable exports have import-resolution declarations only. Parameter lists, return types and behavior remain unverified. Parameterless `int` declarations are placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. Unresolved names use literal NID aliases; no runtime functionality or data objects are fabricated. Existing providers and exports supplied by open pull requests are excluded. Kernel variant imports already resolve through `libkernel`, then `libc`; existing providers remain unchanged. Six native data exports (`errno`, `__stack_chk_guard`, `_sigintr`, `__progname`, `environ`, and NID `AxgW94umKqU`) require separate storage, layout, TLS and ownership validation and are not added as callable aliases or fabricated globals. +- [libkernel_sys additional exports](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) - 324 callable exports have import-resolution declarations only. Parameter lists, return types and behavior remain unverified. Parameterless `int` declarations are placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. Unresolved names use literal NID aliases; no runtime functionality or data objects are fabricated. Existing providers and exports supplied by open pull requests are excluded. Kernel variant imports already resolve through `libkernel`, then `libc`; existing providers remain unchanged. Six native data exports (`errno`, `__stack_chk_guard`, `_sigintr`, `__progname`, `environ`, and NID `AxgW94umKqU`) require separate storage, layout, TLS and ownership validation and are not added as callable aliases or fabricated globals. | Name | NID | | --- | --- | @@ -58,7 +58,6 @@ | `__ubsan_handle_cfi_check_fail` | `K4M0IhFb88Q` | | `__ubsan_handle_cfi_check_fail_abort` | `sPU6b2LaPqg` | | `_dup2` | `W8f1adVl+48` | -| `_execve` | `-3nj+K1elI0` | | `_execvpe` | `ScfDTOqLOMw` | | `_wait4` | `RFlsu7nfopM` | | `adjtime` | `2t3V7tfWY5s` | @@ -351,6 +350,7 @@ - [openat, _openat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) pass `AT_FDCWD` (-100) and absolute paths to `open`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. They return `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. As with `open`, `O_NONBLOCK` is ignored. PPSA99003 opens directory entries through `openat`. Console behaviour is unverified - [fchmodat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `chmod`; any other relative path is joined to the descriptor's host directory the same way as for `openat`, so guest path aliases and case-insensitive name matching do not apply to it and a `..` component is not kept inside the guest root. A flag other than `0` or `AT_SYMLINK_NOFOLLOW` (0x200) returns `EINVAL`; the mode is masked to `07777`. With `AT_SYMLINK_NOFOLLOW` a symbolic link returns success silently on Windows and Linux, since the host cannot change a link's own mode, while a regular file is changed either way. The mode maps to the host file mode, which on Windows is only the CRT's read-only bit. PPSA99621 calls it through `std::filesystem::permissions`. Console behaviour is unverified - [lchmod](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws for a symbolic link on Windows and Linux, whose hosts cannot change a link's own mode; on macOS the link's mode is changed. Anything else goes to `fchmodat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW)`, as FreeBSD 11.4's [`sys_lchmod`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L2490-L2496) does, so a file or directory is changed as by `chmod`, with `fchmodat`'s path lookup and Windows mode mapping. On Windows any reparse point counts as a link. Console behaviour is unverified +- [_execve](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) follows FreeBSD 11.4's [`exec_copyin_args`/`exec_check_permissions`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_exec.c#L1172-L1680): a null `argv` is `EFAULT` before the pathname, guest-read page-wise within `PATH_MAX` (`ENAMETOOLONG`); every `argv` and non-null `envp` entry shares an `ARG_MAX` 262144-byte stringspace counting NULs (`E2BIG` past it; unreadable, `EFAULT`); an empty or null pathname `ENOENT`. The resolver gives `ENOTDIR` from a file prefix; no execute bits or a non-regular file is `EACCES`, an empty file `ENOEXEC`, refused execute permission or a failed reopen `EACCES`. An image with the `ELF` or a PS4/PS5 `SELF` magic throws naming the path; `#!` throws; otherwise `ENOEXEC`. `MNT_NOEXEC`, `ETXTBSY`, `#!` redirection, descriptor-0 exec, Windows mode bits, plain-`ELF` acceptance and resolver search permission are unmodelled. Console behaviour is unverified - [pathconf, fpathconf, _fpathconf](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) answer every file and directory as the console's UFS mount `/system_data` does in a payload process on a PS5 (firmware 13.60). Other mounts differ: `/data` and `/user` give `_PC_LINK_MAX` 1, `_PC_SYMLINK_MAX` 0, `_PC_NO_TRUNC` `EINVAL` and 255 for `_PC_MAX_CANON`, `_PC_MAX_INPUT` and `_PC_VDISABLE`; `/`, `/system`, `/hostapp` and `/update` answer only four names; tmpfs gives `_PC_FILESIZEBITS` 0 and `_PC_SYNC_IO` 1. What a title sees under `/app0`, `/savedata0`, `/temp0` and `/download0` is not known. `fpathconf` and `_fpathconf` answer `_PC_ASYNC_IO` 200112 and, for a pipe, only `_PC_PIPE_BUF` 512, as FreeBSD 11's `kern_fpathconf` does; a guest socket fails `fstat` with `EBADF` - [mkdirat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `mkdir`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. On Windows, `mode` is ignored. Console behaviour is unverified - [readlink](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) on Windows reads a symbolic link's target from its reparse data and returns it with `/` separators, only when the target is relative, as `symlink` creates them. A symbolic link with an absolute target returns its print name, or its substitute name when the print name is empty, without a `\??\` or `\\?\` prefix. A directory junction returns the same kind of name when it is a drive path (`X:\...`), using the print name when that is a drive path and the substitute name otherwise. A mounted volume (`\??\Volume{GUID}\`), any other name surrogate and every reparse point without the name surrogate bit (WOF compression, deduplication, cloud files) return `EINVAL`, as FreeBSD does for a file that is not a link and as CPython's `os.readlink`, which reads only symbolic links and junctions, does @@ -402,7 +402,7 @@ The NID set is from the inspected `libkernel_sys` ELF function-export table (defined `STT_FUNC` entries), not the older 3.20 inventory. The module-parameter words at `0x10` and `0x14` are `0x13590001` and `0x13600007`; the earlier 13.60 label refers to SDK-formatted metadata, not independently authenticated retail firmware or an introduction date. The [reproducible static audit](https://github.com/boykopovar/AnyPS5/pull/12367#issuecomment-6470603298) finds 1551 unique function NIDs with set digest `5f1668cc9c69e2950722a83151f31cf7aa94fb1552ab883e5067f43c94d63b16` and confirms all 328 proposed entries. The digest hashes sorted unique ASCII NIDs, each followed by LF. Named declarations are candidate spellings whose repository-computed NID exactly matches an observed function NID; the hashed table establishes neither plaintext spelling nor C ABI. -The 35 formerly literal aliases are named by the [repository-pinned aerolib table](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). There are now 165 named declarations and 160 unresolved NID aliases; every one of the 325 entries remains a throwing placeholder. `sceKernelHasTrinityMode` left the inventory for its `libkernel` implementation. +The 35 formerly literal aliases are named by the [repository-pinned aerolib table](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). There are now 164 named declarations and 160 unresolved NID aliases; every one of the 324 entries remains a throwing placeholder. `sceKernelHasTrinityMode` left the inventory for its `libkernel` implementation and `_execve` left it for its implementation in `AdditionalExports.cpp`. The following eight candidate spellings are absent from that aerolib revision. Their name/NID pairs occur in the [public ps5rs identity database at commit `2304be5131221bae0dac24b9767cabffda33f747`](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv) and independently recompute to the function NIDs in the inspected table. This database supports candidate spelling only; it does not establish firmware membership, introduction version or ABI. From 1dc2835234ab91524f419489f8d2c784f35261c2 Mon Sep 17 00:00:00 2001 From: rooklz Date: Sun, 1 Nov 2026 07:49:41 +0100 Subject: [PATCH 178/612] fix(libSceLibreSsl3): throw the documented d2i fault arguments --- .../libSceLibreSsl3/src/IntegerDecoder.cpp | 5 ++-- core/libs/prx/libSceLibreSsl3/tests/Asn1.cpp | 30 +++++++++++++++++++ docs/dev/debt/libSceLibreSsl3.md | 2 +- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl3/src/IntegerDecoder.cpp b/core/libs/prx/libSceLibreSsl3/src/IntegerDecoder.cpp index 6279653e22..5abbadbdf3 100644 --- a/core/libs/prx/libSceLibreSsl3/src/IntegerDecoder.cpp +++ b/core/libs/prx/libSceLibreSsl3/src/IntegerDecoder.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace { @@ -118,7 +119,7 @@ void FreeInteger(void* value) { auto& integer = *static_cast(value); if (!(integer.flags & 0x10)) { if (integer.data) { - if (integer.length < 0) NotImplemented_nid_no_patch(__func__); + if (integer.length < 0) throw std::invalid_argument("ASN1_STRING: negative length"); ClearAndFree(integer.data, static_cast(integer.length)); } } @@ -135,7 +136,7 @@ extern "C" { void* APS5_VABI d2i_ASN1_INTEGER_nid_postfix(void** output, const unsigned char** input, std::int64_t length) { if (length < 0) return nullptr; - if (!input || !*input) NotImplemented_nid_no_patch(__func__); + if (!input || !*input) throw std::invalid_argument("d2i_ASN1_INTEGER: null input"); std::span remaining(*input, static_cast(length)); auto content = ReadInteger(remaining); if (!content || content->size() > static_cast(std::numeric_limits::max())) return nullptr; diff --git a/core/libs/prx/libSceLibreSsl3/tests/Asn1.cpp b/core/libs/prx/libSceLibreSsl3/tests/Asn1.cpp index f3a0bdc49e..3d139adfb0 100644 --- a/core/libs/prx/libSceLibreSsl3/tests/Asn1.cpp +++ b/core/libs/prx/libSceLibreSsl3/tests/Asn1.cpp @@ -29,6 +29,17 @@ void Require(bool condition, std::source_location location = std::source_locatio std::abort(); } +template +void RequireInvalidArgument(Callable call) { + bool thrown = false; + try { + call(); + } catch (const std::invalid_argument&) { + thrown = true; + } + Require(thrown); +} + std::unordered_map allocations; std::size_t freeCalls = 0; void* dataToClear = nullptr; @@ -169,6 +180,24 @@ void Ownership() { } +void FaultingArguments() { + const unsigned char encoded[] = {2, 1, 42}; + const unsigned char* cursor = nullptr; + RequireInvalidArgument([&] { d2i_ASN1_INTEGER_nid_postfix(nullptr, &cursor, 1); }); + RequireInvalidArgument([&] { d2i_ASN1_INTEGER_nid_postfix(nullptr, nullptr, 1); }); + auto* old = static_cast(Allocate_nid_no_patch(sizeof(String))); + unsigned char bytes[] = {0x2a}; + *old = {-1, 2, bytes, 0}; + void* output = old; + cursor = encoded; + RequireInvalidArgument([&] { d2i_ASN1_INTEGER_nid_postfix(&output, &cursor, sizeof(encoded)); }); + Require(output == old && cursor == encoded); + old->length = 0; + old->data = nullptr; + Release(old); + Require(allocations.empty()); +} + void BerExtremes() { std::vector negative(131, 0); negative[0] = 2; @@ -318,6 +347,7 @@ int main() { Integers(); BerExtremes(); Ownership(); + FaultingArguments(); Rejections(); AllocationFailures(); Require(allocations.empty()); diff --git a/docs/dev/debt/libSceLibreSsl3.md b/docs/dev/debt/libSceLibreSsl3.md index 025d86bc9c..a003bbe57f 100644 --- a/docs/dev/debt/libSceLibreSsl3.md +++ b/docs/dev/debt/libSceLibreSsl3.md @@ -6,7 +6,7 @@ ### Functional -- [ASN.1 integer decoding](../../../core/libs/prx/libSceLibreSsl3/src/IntegerDecoder.cpp) - nothing is checked on a console, and the console's application-heap ABI is unverified. Rejections, allocation failures, their error codes and the old output follow [13.42 native-PC evidence](https://github.com/boykopovar/AnyPS5/pull/10948#issuecomment-6412137728) from a recording allocator. Nonnegative null input faults natively and throws here. Error combinations the evidence does not cover and content over `INT_MAX` bytes follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/asn1). Closes when: compared with the console. +- [ASN.1 integer decoding](../../../core/libs/prx/libSceLibreSsl3/src/IntegerDecoder.cpp) - nothing is checked on a console, and the console's application-heap ABI is unverified. Rejections, allocation failures, their error codes and the old output follow [13.42 native-PC evidence](https://github.com/boykopovar/AnyPS5/pull/10948#issuecomment-6412137728) from a recording allocator. Nonnegative null input faults natively and throws here. A negative length in the old output throws rather than erasing out of bounds, as the parent library does. Error combinations the evidence does not cover and content over `INT_MAX` bytes follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/asn1). Closes when: compared with the console. - [CRYPTO_memcmp](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) follows the public [LibreSSL signature and equality contract](https://man.openbsd.org/CRYPTO_memcmp.3), without PS5 verification. The exact nonzero result for unequal buffers is unspecified by that contract. Content-independent execution of its volatile byte reads still depends on the compiler; inspect optimized code when changing toolchains. Closes when: measured on a PS5 and the supported toolchains' generated code is checked. - [MD5 and SHA1](../../../core/libs/prx/libSceLibreSsl3/src/Digest.cpp) (libSceLibreSsl3) - follow LibreSSL's [md5.c, sha1.c](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto) and hash through libSceLibreSsl's `MD5_*` and `SHA1_*`. A null digest buffer, which LibreSSL 4.0.0 does not check, throws. Nothing is checked on the console - [EVP_PKEY_up_ref, EC_KEY_up_ref and RSA_up_ref](../../../core/libs/prx/libSceLibreSsl3/src/Key.cpp) - console layouts and retain/release interoperability are unverified. The 32-byte EVP, 64-byte EC and 152-byte RSA layouts, with counters at offsets 4, 48 and 104, follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto) `evp/evp_local.h`, `evp/p_lib.c`, `ec/ec_local.h`, `ec/ec_key.c`, `rsa/rsa_local.h` and `rsa/rsa_lib.c`; the RSA layout has no `engine` field, unlike libSceLibreSsl's `LibreSslRsa`. Atomic retains replace its internal mutex; future releases must synchronize with the same counters. With no key constructors/destructors in this module, tests use caller-owned objects without validating allocator or destruction compatibility. Null keys and signed-counter overflow, undefined upstream, throw without mutation. Closes when: console layouts and lifetime interoperability are measured. From a37dcc1320fb22da78e9afe2744334cb0dac66aa Mon Sep 17 00:00:00 2001 From: Bipin Date: Sun, 1 Nov 2026 12:41:47 +0530 Subject: [PATCH 179/612] feat(libkernel_sys): implement _execvpe export --- core/libs/CMakeLists.txt | 10 ++ .../prx/libkernel_sys/AdditionalExports.cpp | 123 +++++++++++++- .../include/AdditionalExports.hpp | 2 +- core/libs/tests/GuestKernelSysExecvpe.cpp | 150 ++++++++++++++++++ docs/dev/debt/libkernel_sys.md | 6 +- 5 files changed, 284 insertions(+), 7 deletions(-) create mode 100644 core/libs/tests/GuestKernelSysExecvpe.cpp diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index ee21ad27d6..11808c73a0 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -512,6 +512,16 @@ if(BUILD_TESTING) set_tests_properties(guest_kernel_sys_execve PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_kernel_sys_execvpe_tests tests/GuestKernelSysExecvpe.cpp) + target_include_directories(guest_kernel_sys_execvpe_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_execvpe_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_execvpe_tests) + add_test(NAME guest_kernel_sys_execvpe COMMAND guest_kernel_sys_execvpe_tests) + set_tests_properties(guest_kernel_sys_execvpe PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_execvpe PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_kernel_sys_pthread_tests tests/GuestKernelSysPthread.cpp) target_include_directories(guest_kernel_sys_pthread_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_kernel_sys_pthread_tests PRIVATE libkernel_sys libkernel libc) diff --git a/core/libs/prx/libkernel_sys/AdditionalExports.cpp b/core/libs/prx/libkernel_sys/AdditionalExports.cpp index 3ee3b1e24b..8613f35af2 100644 --- a/core/libs/prx/libkernel_sys/AdditionalExports.cpp +++ b/core/libs/prx/libkernel_sys/AdditionalExports.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include extern "C" { @@ -199,7 +201,7 @@ int APS5_VABI _dup2_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _execve_nid_postfix(const char* path, char* const* argv, char* const* envp) { +int GuestExecve_nid_no_patch(const char* path, char* const* argv, char* const* envp) { constexpr int GuestEfault = 14; constexpr int GuestEacces = 13; constexpr int GuestEnoexec = 8; @@ -295,8 +297,123 @@ int APS5_VABI _execve_nid_postfix(const char* path, char* const* argv, char* con return fail(GuestEnoexec); } -int APS5_VABI _execvpe_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _execve_nid_postfix(const char* path, char* const* argv, char* const* envp) { + return GuestExecve_nid_no_patch(path, argv, envp); +} + +int APS5_VABI _execvpe_nid_postfix(const char* name, char* const* argv, char* const* envp) { + constexpr int GuestEacces = 13; + constexpr int GuestEnoent = 2; + constexpr int GuestE2big = 7; + constexpr int GuestEnoexec = 8; + constexpr int GuestEnomem = 12; + constexpr int GuestEtxtbsy = 26; + constexpr int GuestEloop = 62; + constexpr int GuestEnametoolong = 63; + constexpr int GuestEnotdir = 20; + constexpr int GuestEfault = 14; + constexpr std::size_t GuestCandidateMax = 1024; + const std::string_view DefaultSearchPath = "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"; + const std::string_view ShellPath = "/bin/sh"; + const auto fail = [](int error) { + *__error_nid_postfix() = error; + return -1; + }; + std::string searchPath; + if (!CopyGuestEnvironment_nid_no_patch("PATH", searchPath)) searchPath = std::string(DefaultSearchPath); + std::string nameText; + if (name != nullptr) { + for (std::size_t read = 0;;) { + const auto address = reinterpret_cast(name) + read; + const std::size_t bytes = 4096 - address % 4096; + char buffer[4096]; + if (!GuestCopyIn_nid_no_patch(buffer, name + read, bytes)) return fail(GuestEfault); + const auto end = static_cast(std::memchr(buffer, 0, bytes)); + if (end != nullptr) { + nameText.append(buffer, static_cast(end - buffer)); + break; + } + nameText.append(buffer, bytes); + read += bytes; + } + } else { + return fail(GuestEfault); + } + bool accessFailure = false; + const auto ladder = [&](const std::string& candidate) { + GuestExecve_nid_no_patch(candidate.c_str(), argv, envp); + const int refusal = *__error_nid_postfix(); + switch (refusal) { + case GuestE2big: + case GuestEnomem: + case GuestEtxtbsy: + return true; + case GuestEloop: + case GuestEnametoolong: + case GuestEnoent: + case GuestEnotdir: + return false; + case GuestEnoexec: { + std::size_t count = 0; + for (;;) { + char* entry = nullptr; + if (!GuestCopyIn_nid_no_patch(&entry, argv + count, sizeof(entry))) { + fail(GuestEfault); + return true; + } + if (entry == nullptr) break; + ++count; + } + const std::string shell("sh"); + std::vector shellArgs; + shellArgs.reserve(count + 2); + shellArgs.push_back(const_cast(shell.c_str())); + shellArgs.push_back(const_cast(candidate.c_str())); + for (std::size_t index = 1; index < count; ++index) { + char* next = nullptr; + if (!GuestCopyIn_nid_no_patch(&next, argv + index, sizeof(next))) { + fail(GuestEfault); + return true; + } + shellArgs.push_back(next); + } + shellArgs.push_back(nullptr); + GuestExecve_nid_no_patch(ShellPath.data(), shellArgs.data(), envp); + return true; + } + default: { + std::filesystem::path native; + if (const int resolved = ResolvePathError_nid_no_patch(candidate.c_str(), native)) return false; + std::error_code lookupError; + const auto status = std::filesystem::status(native, lookupError); + if (lookupError || status.type() == std::filesystem::file_type::not_found) return false; + if (refusal == GuestEacces) { + accessFailure = true; + return false; + } + return true; + } + } + }; + if (nameText.find('/') != std::string::npos) { + if (ladder(nameText)) return -1; + return fail(accessFailure ? GuestEacces : GuestEnoent); + } + if (nameText.empty()) return fail(GuestEnoent); + std::size_t offset = 0; + for (bool finished = false; !finished;) { + const auto separator = searchPath.find(':', offset); + finished = separator == std::string::npos; + const std::string_view element(searchPath.data() + offset, finished ? searchPath.size() - offset : separator - offset); + offset = finished ? searchPath.size() : separator + 1; + const std::string_view directory = element.empty() ? std::string_view(".") : element; + if (directory.size() + nameText.size() + 2 > GuestCandidateMax) continue; + std::string candidate(directory); + candidate.push_back('/'); + candidate += nameText; + if (ladder(candidate)) return -1; + } + return fail(accessFailure ? GuestEacces : GuestEnoent); } int APS5_VABI _wait4_nid_postfix() { diff --git a/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp b/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp index fb2c41bb12..5caffb8275 100644 --- a/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp +++ b/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp @@ -52,7 +52,7 @@ int APS5_VABI __ubsan_handle_cfi_check_fail_abort_nid_postfix(); int APS5_VABI __ubsan_handle_cfi_check_fail_nid_postfix(); int APS5_VABI _dup2_nid_postfix(); int APS5_VABI _execve_nid_postfix(const char* path, char* const* argv, char* const* envp); -int APS5_VABI _execvpe_nid_postfix(); +int APS5_VABI _execvpe_nid_postfix(const char* name, char* const* argv, char* const* envp); int APS5_VABI _wait4_nid_postfix(); int APS5_VABI adjtime_nid_postfix(); int APS5_VABI libkernel_sysUnknown_0CV40L3tznI_nid_no_patch(); diff --git a/core/libs/tests/GuestKernelSysExecvpe.cpp b/core/libs/tests/GuestKernelSysExecvpe.cpp new file mode 100644 index 0000000000..c741c07366 --- /dev/null +++ b/core/libs/tests/GuestKernelSysExecvpe.cpp @@ -0,0 +1,150 @@ +#include "prx/libkernel_sys/include/AdditionalExports.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/Api.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int GuestEnoent = 2; +constexpr int GuestEacces = 13; +constexpr int GuestEnoexec = 8; +constexpr int GuestE2big = 7; +constexpr int GuestEfault = 14; + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "kernel sys execvpe: %s\n", message); + std::abort(); + } +} + +void RequireFailure(int result, int error, const char* message) { + Require(result == -1 && *__error_nid_postfix() == error, message); +} + +template void RequireThrows(TCall call, const std::string& expected, const char* message) { + try { + call(); + } catch (const std::runtime_error& value) { + Require(value.what() == expected, message); + return; + } + Require(false, message); +} + +void WriteFile(const char* name, const char* content, std::streamsize bytes, std::filesystem::perms mode) { + std::ofstream file(name, std::ios::binary); + file.write(content, bytes); + file.close(); +#ifndef _WIN32 + std::filesystem::permissions(name, mode); +#else + (void)mode; +#endif +} + +} // namespace + +int main() { + char first[] = "first"; + char second[] = "second"; + char* const argv[] = {first, second, nullptr}; + constexpr auto fullExecute = std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec; + constexpr auto plainRead = std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::group_read | std::filesystem::perms::others_read; + constexpr char elfBody[] = "\x7f" "ELFreference"; + constexpr char scriptBody[] = "#!/bin/sh\necho pairs\n"; + RequireFailure(_execvpe_nid_postfix("", argv, nullptr), GuestEnoent, "execvpe with an empty name did not fail with ENOENT"); + RequireFailure(_execvpe_nid_postfix(nullptr, argv, nullptr), GuestEfault, "execvpe with a null name did not fail with EFAULT"); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_absent.bin", nullptr, nullptr), GuestEnoent, "execvpe with a null argv did not fail with ENOENT"); + const std::string oversized(2048, 'a'); + std::vector storage; + std::vector oversizedArgv; + for (int index = 0; index < 128; ++index) { + storage.push_back(oversized); + oversizedArgv.push_back(storage.back().data()); + } + oversizedArgv.push_back(nullptr); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_absent.bin", oversizedArgv.data(), nullptr), GuestE2big, "execvpe with arguments beyond ARG_MAX did not fail with E2BIG"); + WriteFile("kernel_execvpe_elf.bin", elfBody, sizeof(elfBody) - 1, static_cast(fullExecute)); + RequireThrows([&argv] { _execvpe_nid_postfix("./kernel_execvpe_elf.bin", argv, nullptr); }, + "execve: replacing the process image with ./kernel_execvpe_elf.bin is not supported, program activation is not modelled", + "execvpe with a slash in the name did not refuse activation"); + std::filesystem::remove("kernel_execvpe_elf.bin"); + std::filesystem::create_directory("bin"); + WriteFile("bin/sh", elfBody, sizeof(elfBody) - 1, static_cast(fullExecute)); + RequireThrows([&argv] { _execvpe_nid_postfix("./bin/sh", argv, nullptr); }, + "execve: replacing the process image with ./bin/sh is not supported, program activation is not modelled", + "execvpe of the shell by path did not refuse activation"); + WriteFile("bin/plain.bin", "plain", 5, static_cast(fullExecute)); + RequireThrows([&argv] { _execvpe_nid_postfix("./bin/plain.bin", argv, nullptr); }, + "execve: replacing the process image with /bin/sh is not supported, program activation is not modelled", + "execvpe of a slash-named plain file did not redirect to and refuse the shell"); + std::filesystem::create_directory("sbin"); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_absent.bin", argv, nullptr), GuestEnoent, "execvpe with the default search path and a missing file did not fail with ENOENT"); +#ifndef _WIN32 + WriteFile("bin/kernel_execvpe_refused.bin", "plain", 5, static_cast(plainRead)); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_refused.bin", argv, nullptr), GuestEacces, "execvpe without execute bits did not report EACCES after the search"); + std::filesystem::remove("bin/kernel_execvpe_refused.bin"); +#endif + std::filesystem::create_directory("usr"); + std::filesystem::create_directory("usr/bin"); + WriteFile("usr/bin/kernel_execvpe_elf.bin", elfBody, sizeof(elfBody) - 1, static_cast(fullExecute)); + RequireThrows([&argv] { _execvpe_nid_postfix("kernel_execvpe_elf.bin", argv, nullptr); }, + "execve: replacing the process image with /usr/bin/kernel_execvpe_elf.bin is not supported, program activation is not modelled", + "execvpe did not search the default path and refuse the found image"); + std::filesystem::create_directory("sandbox"); + WriteFile("sandbox/kernel_execvpe_tool.bin", elfBody, sizeof(elfBody) - 1, static_cast(fullExecute)); + setenv_nid_postfix("PATH", "/sandbox", 1); + RequireThrows([&argv] { _execvpe_nid_postfix("kernel_execvpe_tool.bin", argv, nullptr); }, + "execve: replacing the process image with /sandbox/kernel_execvpe_tool.bin is not supported, program activation is not modelled", + "execvpe did not follow the guest PATH and refuse the found image"); + unsetenv_nid_postfix("PATH"); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_tool.bin", argv, nullptr), GuestEnoent, "execvpe with a cleared PATH did not fail with ENOENT"); + WriteFile("kernel_execvpe_tool.bin", elfBody, sizeof(elfBody) - 1, static_cast(fullExecute)); + setenv_nid_postfix("PATH", "", 1); + RequireThrows([&argv] { _execvpe_nid_postfix("kernel_execvpe_tool.bin", argv, nullptr); }, + "execve: replacing the process image with ./kernel_execvpe_tool.bin is not supported, program activation is not modelled", + "execvpe with an empty PATH did not search the current directory"); + unsetenv_nid_postfix("PATH"); + std::filesystem::remove("kernel_execvpe_tool.bin"); + WriteFile("sandbox/kernel_execvpe_job.bin", scriptBody, sizeof(scriptBody) - 1, static_cast(fullExecute)); + setenv_nid_postfix("PATH", "/sandbox", 1); + RequireThrows([&argv] { _execvpe_nid_postfix("kernel_execvpe_job.bin", argv, nullptr); }, + "execve: running the script /sandbox/kernel_execvpe_job.bin is not supported, script interpreters are not modelled", + "execvpe of a script did not refuse the interpreter"); + WriteFile("sandbox/kernel_execvpe_plain.bin", "plain", 5, static_cast(fullExecute)); + RequireThrows([&argv] { _execvpe_nid_postfix("kernel_execvpe_plain.bin", argv, nullptr); }, + "execve: replacing the process image with /bin/sh is not supported, program activation is not modelled", + "execvpe of an unrecognised file did not redirect to and refuse the shell"); + std::filesystem::remove("bin/sh"); + RequireFailure(_execvpe_nid_postfix("./bin/plain.bin", argv, nullptr), GuestEnoent, "execvpe of a slash-named plain file without the shell did not fail with ENOENT"); + std::filesystem::remove("bin/plain.bin"); + std::filesystem::create_directory("scripts"); + WriteFile("scripts/kernel_execvpe_plain.bin", "plain", 5, static_cast(fullExecute)); + setenv_nid_postfix("PATH", "/scripts", 1); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_plain.bin", argv, nullptr), GuestEnoent, "execvpe of an unrecognised file without the shell did not fail with ENOENT"); + setenv_nid_postfix("PATH", "/absent:/scripts", 1); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_plain.bin", argv, nullptr), GuestEnoent, "execvpe over a missing first element did not fail with ENOENT"); + unsetenv_nid_postfix("PATH"); + std::filesystem::remove("sandbox/kernel_execvpe_tool.bin"); + std::filesystem::remove("sandbox/kernel_execvpe_job.bin"); + std::filesystem::remove("sandbox/kernel_execvpe_plain.bin"); + std::filesystem::remove("usr/bin/kernel_execvpe_elf.bin"); + std::filesystem::remove("scripts/kernel_execvpe_plain.bin"); + std::filesystem::remove("bin"); + std::filesystem::remove("sbin"); + std::filesystem::remove("sandbox"); + std::filesystem::remove("scripts"); + std::filesystem::remove("usr/bin"); + std::filesystem::remove("usr"); + std::puts("guest_kernel_sys_execvpe passed"); + return 0; +} diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 7819461d91..a0af526bac 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libkernel_sys additional exports](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) - 324 callable exports have import-resolution declarations only. Parameter lists, return types and behavior remain unverified. Parameterless `int` declarations are placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. Unresolved names use literal NID aliases; no runtime functionality or data objects are fabricated. Existing providers and exports supplied by open pull requests are excluded. Kernel variant imports already resolve through `libkernel`, then `libc`; existing providers remain unchanged. Six native data exports (`errno`, `__stack_chk_guard`, `_sigintr`, `__progname`, `environ`, and NID `AxgW94umKqU`) require separate storage, layout, TLS and ownership validation and are not added as callable aliases or fabricated globals. +- [libkernel_sys additional exports](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) - 323 callable exports have import-resolution declarations only. Parameter lists, return types and behavior remain unverified. Parameterless `int` declarations are placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. Unresolved names use literal NID aliases; no runtime functionality or data objects are fabricated. Existing providers and exports supplied by open pull requests are excluded. Kernel variant imports already resolve through `libkernel`, then `libc`; existing providers remain unchanged. Six native data exports (`errno`, `__stack_chk_guard`, `_sigintr`, `__progname`, `environ`, and NID `AxgW94umKqU`) require separate storage, layout, TLS and ownership validation and are not added as callable aliases or fabricated globals. | Name | NID | | --- | --- | @@ -58,7 +58,6 @@ | `__ubsan_handle_cfi_check_fail` | `K4M0IhFb88Q` | | `__ubsan_handle_cfi_check_fail_abort` | `sPU6b2LaPqg` | | `_dup2` | `W8f1adVl+48` | -| `_execvpe` | `ScfDTOqLOMw` | | `_wait4` | `RFlsu7nfopM` | | `adjtime` | `2t3V7tfWY5s` | | `mmap2` | `tGtH2GTWbyE` | @@ -351,6 +350,7 @@ - [fchmodat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `chmod`; any other relative path is joined to the descriptor's host directory the same way as for `openat`, so guest path aliases and case-insensitive name matching do not apply to it and a `..` component is not kept inside the guest root. A flag other than `0` or `AT_SYMLINK_NOFOLLOW` (0x200) returns `EINVAL`; the mode is masked to `07777`. With `AT_SYMLINK_NOFOLLOW` a symbolic link returns success silently on Windows and Linux, since the host cannot change a link's own mode, while a regular file is changed either way. The mode maps to the host file mode, which on Windows is only the CRT's read-only bit. PPSA99621 calls it through `std::filesystem::permissions`. Console behaviour is unverified - [lchmod](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws for a symbolic link on Windows and Linux, whose hosts cannot change a link's own mode; on macOS the link's mode is changed. Anything else goes to `fchmodat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW)`, as FreeBSD 11.4's [`sys_lchmod`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L2490-L2496) does, so a file or directory is changed as by `chmod`, with `fchmodat`'s path lookup and Windows mode mapping. On Windows any reparse point counts as a link. Console behaviour is unverified - [_execve](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) follows FreeBSD 11.4's [`exec_copyin_args`/`exec_check_permissions`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_exec.c#L1172-L1680): a null `argv` is `EFAULT` before the pathname, guest-read page-wise within `PATH_MAX` (`ENAMETOOLONG`); every `argv` and non-null `envp` entry shares an `ARG_MAX` 262144-byte stringspace counting NULs (`E2BIG` past it; unreadable, `EFAULT`); an empty or null pathname `ENOENT`. The resolver gives `ENOTDIR` from a file prefix; no execute bits or a non-regular file is `EACCES`, an empty file `ENOEXEC`, refused execute permission or a failed reopen `EACCES`. An image with the `ELF` or a PS4/PS5 `SELF` magic throws naming the path; `#!` throws; otherwise `ENOEXEC`. `MNT_NOEXEC`, `ETXTBSY`, `#!` redirection, descriptor-0 exec, Windows mode bits, plain-`ELF` acceptance and resolver search permission are unmodelled. Console behaviour is unverified +- [_execvpe](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) follows FreeBSD 11.4's `_execvpe` and `execvPe`: the search path comes from the guest environment, `_PATH_DEFPATH` when unset; a name containing `/` runs the `_execve` ladder once through the same switch; an empty name is `ENOENT`; each `:`-separated element, empty for the current directory, forms a sub-1024-byte candidate for the `_execve` ladder, where `E2BIG`, `ENOMEM` and `ETXTBSY` return, `ENOENT`, `ENOTDIR`, `ELOOP` and `ENAMETOOLONG` try the next element, `ENOEXEC` retries as `/bin/sh sh ` with the remaining arguments, and any other error returns once the candidate resolves and exists, recording `EACCES` for the final answer or `ENOENT`. A `#!` or executable candidate throws from the ladder naming the path; the too-long-candidate stderr note is unmodelled; FreeBSD's libc would crash on an unreadable name where the copy costs `EFAULT`. Console behaviour is unverified - [pathconf, fpathconf, _fpathconf](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) answer every file and directory as the console's UFS mount `/system_data` does in a payload process on a PS5 (firmware 13.60). Other mounts differ: `/data` and `/user` give `_PC_LINK_MAX` 1, `_PC_SYMLINK_MAX` 0, `_PC_NO_TRUNC` `EINVAL` and 255 for `_PC_MAX_CANON`, `_PC_MAX_INPUT` and `_PC_VDISABLE`; `/`, `/system`, `/hostapp` and `/update` answer only four names; tmpfs gives `_PC_FILESIZEBITS` 0 and `_PC_SYNC_IO` 1. What a title sees under `/app0`, `/savedata0`, `/temp0` and `/download0` is not known. `fpathconf` and `_fpathconf` answer `_PC_ASYNC_IO` 200112 and, for a pipe, only `_PC_PIPE_BUF` 512, as FreeBSD 11's `kern_fpathconf` does; a guest socket fails `fstat` with `EBADF` - [mkdirat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `mkdir`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. On Windows, `mode` is ignored. Console behaviour is unverified - [readlink](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) on Windows reads a symbolic link's target from its reparse data and returns it with `/` separators, only when the target is relative, as `symlink` creates them. A symbolic link with an absolute target returns its print name, or its substitute name when the print name is empty, without a `\??\` or `\\?\` prefix. A directory junction returns the same kind of name when it is a drive path (`X:\...`), using the print name when that is a drive path and the substitute name otherwise. A mounted volume (`\??\Volume{GUID}\`), any other name surrogate and every reparse point without the name surrogate bit (WOF compression, deduplication, cloud files) return `EINVAL`, as FreeBSD does for a file that is not a link and as CPython's `os.readlink`, which reads only symbolic links and junctions, does @@ -402,7 +402,7 @@ The NID set is from the inspected `libkernel_sys` ELF function-export table (defined `STT_FUNC` entries), not the older 3.20 inventory. The module-parameter words at `0x10` and `0x14` are `0x13590001` and `0x13600007`; the earlier 13.60 label refers to SDK-formatted metadata, not independently authenticated retail firmware or an introduction date. The [reproducible static audit](https://github.com/boykopovar/AnyPS5/pull/12367#issuecomment-6470603298) finds 1551 unique function NIDs with set digest `5f1668cc9c69e2950722a83151f31cf7aa94fb1552ab883e5067f43c94d63b16` and confirms all 328 proposed entries. The digest hashes sorted unique ASCII NIDs, each followed by LF. Named declarations are candidate spellings whose repository-computed NID exactly matches an observed function NID; the hashed table establishes neither plaintext spelling nor C ABI. -The 35 formerly literal aliases are named by the [repository-pinned aerolib table](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). There are now 164 named declarations and 160 unresolved NID aliases; every one of the 324 entries remains a throwing placeholder. `sceKernelHasTrinityMode` left the inventory for its `libkernel` implementation and `_execve` left it for its implementation in `AdditionalExports.cpp`. +The 35 formerly literal aliases are named by the [repository-pinned aerolib table](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). There are now 163 named declarations and 159 unresolved NID aliases; every one of the 323 entries remains a throwing placeholder. `sceKernelHasTrinityMode` left the inventory for its `libkernel` implementation and `_execve` and `_execvpe` left it for their implementations in `AdditionalExports.cpp`. The following eight candidate spellings are absent from that aerolib revision. Their name/NID pairs occur in the [public ps5rs identity database at commit `2304be5131221bae0dac24b9767cabffda33f747`](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv) and independently recompute to the function NIDs in the inspected table. This database supports candidate spelling only; it does not establish firmware membership, introduction version or ABI. From 580379c1285abf4f97d9199feca6d9fc7995d0b5 Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Sun, 1 Nov 2026 08:20:32 +0000 Subject: [PATCH 180/612] fix(ci): remove the open PR limit label when a reopened pull request is allowed pr-intake.yml removed the open PR limit label on a reopened pull request only for maintainers. Any other author who reopened a pull request inside the limit reached an early return with the label still set, while CONTRIBUTING.md tells authors to reopen once one of their open pull requests closes or merges. The label is now removed on each allowed reopen path: maintainers, authors with 20 or more merged pull requests, and authors within the open limit. A failed permission check still applies the limit, as before. --- .github/workflows/pr-intake.yml | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pr-intake.yml b/.github/workflows/pr-intake.yml index 3225af812b..42ea8d8bae 100644 --- a/.github/workflows/pr-intake.yml +++ b/.github/workflows/pr-intake.yml @@ -23,6 +23,16 @@ jobs: script: | const { owner, repo } = context.repo; const pull_number = context.payload.pull_request.number; + const reopened = context.payload.action === 'reopened'; + const removeLimitLabel = async () => { + try { + await github.rest.issues.removeLabel({ owner, repo, issue_number: pull_number, name: 'open PR limit' }); + } catch (error) { + if (error.status !== 404) { + core.warning(`Could not remove the open PR limit label: ${error.message}`); + } + } + }; const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number }); if (pr.state !== 'open' || pr.base.ref !== 'main') { return; @@ -38,13 +48,7 @@ jobs: core.warning(`Could not check the permission of ${context.payload.sender.login}, so the open PR limit applies: ${error.message}`); } if (maintainer) { - try { - await github.rest.issues.removeLabel({ owner, repo, issue_number: pull_number, name: 'open PR limit' }); - } catch (error) { - if (error.status !== 404) { - core.warning(`Could not remove the open PR limit label: ${error.message}`); - } - } + await removeLimitLabel(); return; } } @@ -57,18 +61,23 @@ jobs: } const merged = mergedPRs.total_count; if (merged >= 20) { + if (reopened) { + await removeLimitLabel(); + } return; } const allowedOpen = merged >= 1 ? 20 : 1; const openPRs = await github.paginate(github.rest.pulls.list, { owner, repo, state: 'open', base: 'main', per_page: 100 }); - const reopened = context.payload.action === 'reopened'; const open = new Set(openPRs .filter((p) => p.user.login === pr.user.login && (reopened || p.number < pull_number)) .map((p) => p.number)); open.add(pull_number); if (open.size <= allowedOpen) { + if (reopened) { + await removeLimitLabel(); + } return; } const name = 'open PR limit'; From 0cb0aaedcc58eb32360321c6e2ab97ded59708cb Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Sun, 1 Nov 2026 08:20:33 +0000 Subject: [PATCH 181/612] test(ci): run the pr-intake script for each reopen case in node tools/tests/test_pr_intake_workflow.py extracts the github-script body from pr-intake.yml and runs it in node against mocked GitHub calls for eight cases. On the previous workflow five cases fail: a non-maintainer reopen inside the limit, with 1 or with 25 merged pull requests, and the permission-failure and label-removal-error cases, because the old code never calls removeLabel for them. The test skips when node is not installed and is registered as pr_intake_reopen. --- CMakeLists.txt | 3 + tools/tests/test_pr_intake_workflow.py | 144 +++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 tools/tests/test_pr_intake_workflow.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 204b976daa..109ada1510 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -293,6 +293,9 @@ if(BUILD_TESTING) add_test(NAME check_skips_ctest COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_skips_ctest.py) set_tests_properties(check_skips_ctest PROPERTIES TIMEOUT 30) + add_test(NAME pr_intake_reopen COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_intake_workflow.py) + set_tests_properties(pr_intake_reopen PROPERTIES TIMEOUT 60) add_test(NAME tools_pe_inventory COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_pe_symbol_inventory.py) set_tests_properties(tools_pe_inventory PROPERTIES TIMEOUT 20) add_test(NAME check_skips COMMAND ${Python3_EXECUTABLE} diff --git a/tools/tests/test_pr_intake_workflow.py b/tools/tests/test_pr_intake_workflow.py new file mode 100644 index 0000000000..c85f969af4 --- /dev/null +++ b/tools/tests/test_pr_intake_workflow.py @@ -0,0 +1,144 @@ +import json +import shutil +import subprocess +import tempfile +import unittest +from pathlib import Path + +WORKFLOW = Path(__file__).resolve().parents[2] / ".github/workflows/pr-intake.yml" + +HARNESS = r""" +const fs = require('fs'); +const script = fs.readFileSync(process.argv[2], 'utf8'); +const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor; +const run = new AsyncFunction('github', 'context', 'core', script); + +async function one(scenario) { + const calls = []; + const rest = { + pulls: { + get: async () => ({ data: { state: 'open', base: { ref: 'main' }, user: { login: 'author' } } }), + list: async () => [], + update: async () => { calls.push('close'); return { data: {} }; }, + }, + repos: { + getCollaboratorPermissionLevel: async () => { + if (scenario.permissionError) throw new Error('forbidden'); + return { data: { role_name: scenario.permission } }; + }, + }, + search: { + issuesAndPullRequests: async () => ({ data: { total_count: scenario.merged, incomplete_results: false } }), + }, + issues: { + removeLabel: async () => { + calls.push('removeLabel'); + if (scenario.removeError) { + const error = new Error('label'); + error.status = scenario.removeError; + throw error; + } + return { data: {} }; + }, + getLabel: async () => ({ data: {} }), + createLabel: async () => ({ data: {} }), + addLabels: async () => { calls.push('addLabels'); return { data: [] }; }, + listComments: async () => ({ data: [] }), + createComment: async () => { calls.push('createComment'); return { data: {} }; }, + }, + }; + const others = Array.from({ length: scenario.others }, (_, index) => ({ number: 90 + index, user: { login: 'author' } })); + const github = { + rest, + paginate: async (method) => { + if (method === rest.pulls.list) return others; + if (method === rest.issues.listComments) return []; + throw new Error('unexpected paginate target'); + }, + }; + const core = { warning: () => calls.push('warning') }; + const context = { + repo: { owner: 'boykopovar', repo: 'AnyPS5' }, + payload: { action: scenario.action, sender: { login: 'contributor' }, pull_request: { number: 14000 } }, + }; + try { + await run(github, context, core); + } catch (error) { + calls.push('THREW:' + error.message); + } + return { name: scenario.name, calls }; +} + +let input = ''; +process.stdin.on('data', (chunk) => { input += chunk; }); +process.stdin.on('end', async () => { + const results = []; + for (const scenario of JSON.parse(input)) { + results.push(await one(scenario)); + } + process.stdout.write(JSON.stringify(results)); +}); +""" + +SCENARIOS = [ + {"name": "reopened by a maintainer", "action": "reopened", "permission": "write", "merged": 0, "others": 0, + "label_removed": True, "closed": False, "warned": False}, + {"name": "reopened inside the limit with one merged", "action": "reopened", "permission": "read", "merged": 1, + "others": 0, "label_removed": True, "closed": False, "warned": False}, + {"name": "reopened inside the limit with 25 merged", "action": "reopened", "permission": "read", "merged": 25, + "others": 0, "label_removed": True, "closed": False, "warned": False}, + {"name": "reopened over the limit", "action": "reopened", "permission": "read", "merged": 0, "others": 1, + "label_removed": False, "closed": True, "warned": False}, + {"name": "opened inside the limit", "action": "opened", "permission": "read", "merged": 0, "others": 0, + "label_removed": False, "closed": False, "warned": False}, + {"name": "reopened when the permission check fails", "action": "reopened", "permissionError": True, + "merged": 0, "others": 0, "label_removed": True, "closed": False, "warned": True}, + {"name": "reopened when the label is already gone", "action": "reopened", "permission": "read", "merged": 1, + "others": 0, "removeError": 404, "label_removed": True, "closed": False, "warned": False}, + {"name": "reopened when label removal fails", "action": "reopened", "permission": "read", "merged": 1, + "others": 0, "removeError": 500, "label_removed": True, "closed": False, "warned": True}, +] + + +def intake_script(): + lines = WORKFLOW.read_text(encoding="utf-8").splitlines() + start = next(index for index, line in enumerate(lines) if line.strip() == "script: |") + 1 + indent = len(lines[start]) - len(lines[start].lstrip()) + script = [] + for line in lines[start:]: + if line.strip() and len(line) - len(line.lstrip()) < indent: + break + script.append(line[indent:]) + return "\n".join(script) + + +class PrIntakeReopenTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.node = shutil.which("node") + if not cls.node: + raise unittest.SkipTest("the intake script runs in node") + cls.temp = tempfile.TemporaryDirectory() + cls.addClassCleanup(cls.temp.cleanup) + root = Path(cls.temp.name) + (root / "harness.js").write_text(HARNESS, encoding="utf-8") + (root / "intake.js").write_text(intake_script(), encoding="utf-8") + result = subprocess.run( + [cls.node, "harness.js", "intake.js"], cwd=root, input=json.dumps(SCENARIOS), text=True, + encoding="utf-8", capture_output=True, timeout=60, check=True, + ) + cls.results = {item["name"]: item["calls"] for item in json.loads(result.stdout)} + + def test_label_close_and_warning_effects_match_each_scenario(self): + for scenario in SCENARIOS: + with self.subTest(scenario=scenario["name"]): + calls = self.results[scenario["name"]] + self.assertFalse([call for call in calls if call.startswith("THREW")], calls) + self.assertEqual("removeLabel" in calls, scenario["label_removed"], calls) + self.assertEqual("close" in calls, scenario["closed"], calls) + self.assertEqual("addLabels" in calls, scenario["closed"], calls) + self.assertEqual("warning" in calls, scenario["warned"], calls) + + +if __name__ == "__main__": + unittest.main() From e207dcbf4e148c5ed936a687935fede12c4cb0c6 Mon Sep 17 00:00:00 2001 From: hyvvs Date: Sun, 1 Nov 2026 10:20:27 +0100 Subject: [PATCH 182/612] feat(libcairo): implement font options lifecycle and settings --- core/libs/CMakeLists.txt | 8 ++ core/libs/prx/libcairo/Export.cpp | 32 ------ core/libs/prx/libcairo/FontOptions.cpp | 63 +++++++++++ .../prx/libcairo/include/InventoryExports.hpp | 50 +++++++-- core/libs/tests/GuestCairoFontOptions.cpp | 102 ++++++++++++++++++ docs/dev/debt/libcairo.md | 10 +- 6 files changed, 216 insertions(+), 49 deletions(-) create mode 100644 core/libs/prx/libcairo/FontOptions.cpp create mode 100644 core/libs/tests/GuestCairoFontOptions.cpp diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index c9b739fb83..dfe1d5cde8 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -215,6 +215,8 @@ foreach(LIB_DIR ${LIBS_DIRS}) endif() endforeach() +target_sources(libcairo PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/prx/libcairo/FontOptions.cpp) + if(WIN32) file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/release-implibs.txt CONTENT "${RELEASE_IMPLIBS}") endif() @@ -1272,3 +1274,9 @@ endif() include(prx/libSceFreeType/CurveBounds.cmake) include(prx/libSceBeisobmf/Tests.cmake) + +if(BUILD_TESTING) + add_test_executable(guest_cairo_font_options_tests tests/GuestCairoFontOptions.cpp prx/libcairo/FontOptions.cpp) + target_include_directories(guest_cairo_font_options_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + add_test(NAME guest_cairo_font_options COMMAND guest_cairo_font_options_tests) +endif() diff --git a/core/libs/prx/libcairo/Export.cpp b/core/libs/prx/libcairo/Export.cpp index db5ce4bbcd..b20a093cb9 100644 --- a/core/libs/prx/libcairo/Export.cpp +++ b/core/libs/prx/libcairo/Export.cpp @@ -12,10 +12,6 @@ int APS5_VABI cairo_paint() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_set_hint_metrics() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_set_font_options() { NotImplemented_nid_no_patch(__func__); } @@ -228,10 +224,6 @@ int APS5_VABI cairo_region_get_rectangle() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_set_hint_style() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_set_miter_limit() { NotImplemented_nid_no_patch(__func__); } @@ -312,10 +304,6 @@ int APS5_VABI cairo_mesh_pattern_set_corner_color_rgba() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_copy() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_identity_matrix() { NotImplemented_nid_no_patch(__func__); } @@ -352,10 +340,6 @@ int APS5_VABI cairo_ft_scaled_font_unlock_face() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_get_antialias() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_mesh_pattern_move_to() { NotImplemented_nid_no_patch(__func__); } @@ -492,10 +476,6 @@ int APS5_VABI cairo_push_group() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_destroy() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_ft_font_options_substitute() { NotImplemented_nid_no_patch(__func__); } @@ -504,18 +484,10 @@ int APS5_VABI cairo_image_surface_get_width() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_create() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_device_to_user() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_set_antialias() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_glyph_free() { NotImplemented_nid_no_patch(__func__); } @@ -552,10 +524,6 @@ int APS5_VABI cairo_scaled_font_glyph_extents() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_set_subpixel_order() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_show_text() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libcairo/FontOptions.cpp b/core/libs/prx/libcairo/FontOptions.cpp new file mode 100644 index 0000000000..b3c1428ef3 --- /dev/null +++ b/core/libs/prx/libcairo/FontOptions.cpp @@ -0,0 +1,63 @@ +#include "prx/libcairo/include/InventoryExports.hpp" +#include + +struct cairo_font_options_t { + cairo_antialias_t antialias; + cairo_subpixel_order_t subpixel_order; + cairo_hint_style_t hint_style; + cairo_hint_metrics_t hint_metrics; +}; + +namespace { + +const cairo_font_options_t nilOptions{}; + +bool Invalid(const cairo_font_options_t* options) { + return options == nullptr || options == &nilOptions; +} + +cairo_font_options_t* Nil() { + return const_cast(&nilOptions); +} + +} + +extern "C" { + +cairo_font_options_t* APS5_VABI cairo_font_options_create() { + auto* options = static_cast(std::calloc(1, sizeof(cairo_font_options_t))); + return options != nullptr ? options : Nil(); +} + +cairo_font_options_t* APS5_VABI cairo_font_options_copy(const cairo_font_options_t* original) { + if (Invalid(original)) return Nil(); + auto* options = cairo_font_options_create(); + if (options != Nil()) *options = *original; + return options; +} + +void APS5_VABI cairo_font_options_destroy(cairo_font_options_t* options) { + if (!Invalid(options)) std::free(options); +} + +void APS5_VABI cairo_font_options_set_antialias(cairo_font_options_t* options, cairo_antialias_t antialias) { + if (!Invalid(options)) options->antialias = antialias; +} + +cairo_antialias_t APS5_VABI cairo_font_options_get_antialias(const cairo_font_options_t* options) { + return Invalid(options) ? CAIRO_ANTIALIAS_DEFAULT : options->antialias; +} + +void APS5_VABI cairo_font_options_set_hint_style(cairo_font_options_t* options, cairo_hint_style_t hint_style) { + if (!Invalid(options)) options->hint_style = hint_style; +} + +void APS5_VABI cairo_font_options_set_hint_metrics(cairo_font_options_t* options, cairo_hint_metrics_t hint_metrics) { + if (!Invalid(options)) options->hint_metrics = hint_metrics; +} + +void APS5_VABI cairo_font_options_set_subpixel_order(cairo_font_options_t* options, cairo_subpixel_order_t subpixel_order) { + if (!Invalid(options)) options->subpixel_order = subpixel_order; +} + +} diff --git a/core/libs/prx/libcairo/include/InventoryExports.hpp b/core/libs/prx/libcairo/include/InventoryExports.hpp index c5e5ee360e..2506495f35 100644 --- a/core/libs/prx/libcairo/include/InventoryExports.hpp +++ b/core/libs/prx/libcairo/include/InventoryExports.hpp @@ -2,11 +2,45 @@ #include "SceTypes.hpp" +struct cairo_font_options_t; + +enum cairo_antialias_t : int { + CAIRO_ANTIALIAS_DEFAULT, + CAIRO_ANTIALIAS_NONE, + CAIRO_ANTIALIAS_GRAY, + CAIRO_ANTIALIAS_SUBPIXEL, + CAIRO_ANTIALIAS_FAST, + CAIRO_ANTIALIAS_GOOD, + CAIRO_ANTIALIAS_BEST, +}; + +enum cairo_subpixel_order_t : int { + CAIRO_SUBPIXEL_ORDER_DEFAULT, + CAIRO_SUBPIXEL_ORDER_RGB, + CAIRO_SUBPIXEL_ORDER_BGR, + CAIRO_SUBPIXEL_ORDER_VRGB, + CAIRO_SUBPIXEL_ORDER_VBGR, +}; + +enum cairo_hint_style_t : int { + CAIRO_HINT_STYLE_DEFAULT, + CAIRO_HINT_STYLE_NONE, + CAIRO_HINT_STYLE_SLIGHT, + CAIRO_HINT_STYLE_MEDIUM, + CAIRO_HINT_STYLE_FULL, +}; + +enum cairo_hint_metrics_t : int { + CAIRO_HINT_METRICS_DEFAULT, + CAIRO_HINT_METRICS_OFF, + CAIRO_HINT_METRICS_ON, +}; + extern "C" { int APS5_VABI cairo_scaled_font_get_font_face(); int APS5_VABI cairo_paint(); -int APS5_VABI cairo_font_options_set_hint_metrics(); +void APS5_VABI cairo_font_options_set_hint_metrics(cairo_font_options_t* options, cairo_hint_metrics_t hint_metrics); int APS5_VABI cairo_set_font_options(); int APS5_VABI cairo_device_to_user_distance(); int APS5_VABI cairo_font_face_set_user_data(); @@ -60,7 +94,7 @@ int APS5_VABI cairo_scaled_font_get_font_matrix(); int APS5_VABI cairo_mesh_pattern_line_to(); int APS5_VABI cairo_get_line_cap(); int APS5_VABI cairo_region_get_rectangle(); -int APS5_VABI cairo_font_options_set_hint_style(); +void APS5_VABI cairo_font_options_set_hint_style(cairo_font_options_t* options, cairo_hint_style_t hint_style); int APS5_VABI cairo_set_miter_limit(); int APS5_VABI cairo_translate(); int APS5_VABI cairo_font_face_destroy(); @@ -81,7 +115,7 @@ int APS5_VABI cairo_transform(); int APS5_VABI cairo_set_line_join(); int APS5_VABI cairo_pop_group_to_source(); int APS5_VABI cairo_mesh_pattern_set_corner_color_rgba(); -int APS5_VABI cairo_font_options_copy(); +cairo_font_options_t* APS5_VABI cairo_font_options_copy(const cairo_font_options_t* original); int APS5_VABI cairo_identity_matrix(); int APS5_VABI cairo_save(); int APS5_VABI cairo_clip(); @@ -91,7 +125,7 @@ int APS5_VABI cairo_arc(); int APS5_VABI cairo_stroke_preserve(); int APS5_VABI cairo_path_extents(); int APS5_VABI cairo_ft_scaled_font_unlock_face(); -int APS5_VABI cairo_font_options_get_antialias(); +cairo_antialias_t APS5_VABI cairo_font_options_get_antialias(const cairo_font_options_t* options); int APS5_VABI cairo_mesh_pattern_move_to(); int APS5_VABI cairo_paint_with_alpha(); int APS5_VABI cairo_set_fill_rule(); @@ -126,12 +160,12 @@ int APS5_VABI cairo_set_dash(); int APS5_VABI cairo_pattern_set_filter(); int APS5_VABI cairo_new_path(); int APS5_VABI cairo_push_group(); -int APS5_VABI cairo_font_options_destroy(); +void APS5_VABI cairo_font_options_destroy(cairo_font_options_t* options); int APS5_VABI cairo_ft_font_options_substitute(); int APS5_VABI cairo_image_surface_get_width(); -int APS5_VABI cairo_font_options_create(); +cairo_font_options_t* APS5_VABI cairo_font_options_create(); int APS5_VABI cairo_device_to_user(); -int APS5_VABI cairo_font_options_set_antialias(); +void APS5_VABI cairo_font_options_set_antialias(cairo_font_options_t* options, cairo_antialias_t antialias); int APS5_VABI cairo_glyph_free(); int APS5_VABI cairo_select_font_face(); int APS5_VABI cairo_line_to(); @@ -141,7 +175,7 @@ int APS5_VABI cairo_font_face_reference(); int APS5_VABI cairo_set_line_cap(); int APS5_VABI cairo_scaled_font_text_to_glyphs(); int APS5_VABI cairo_scaled_font_glyph_extents(); -int APS5_VABI cairo_font_options_set_subpixel_order(); +void APS5_VABI cairo_font_options_set_subpixel_order(cairo_font_options_t* options, cairo_subpixel_order_t subpixel_order); int APS5_VABI cairo_show_text(); int APS5_VABI cairo_set_font_face(); int APS5_VABI cairo_close_path(); diff --git a/core/libs/tests/GuestCairoFontOptions.cpp b/core/libs/tests/GuestCairoFontOptions.cpp new file mode 100644 index 0000000000..c7e546bd2a --- /dev/null +++ b/core/libs/tests/GuestCairoFontOptions.cpp @@ -0,0 +1,102 @@ +#include "prx/libcairo/include/InventoryExports.hpp" +#include +#include +#include +#include + +namespace { + +struct Values { + cairo_antialias_t antialias; + cairo_subpixel_order_t subpixel_order; + cairo_hint_style_t hint_style; + cairo_hint_metrics_t hint_metrics; +}; + +static_assert(sizeof(Values) == sizeof(int) * 4); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); + +void Require(bool valid, const char* reason) { + if (valid) return; + std::fprintf(stderr, "cairo font options: %s\n", reason); + std::abort(); +} + +Values ReadValues(const cairo_font_options_t* options) { + Values values{}; + std::memcpy(&values, options, sizeof(values)); + return values; +} + +void RequireValues(const cairo_font_options_t* options, Values expected) { + auto values = ReadValues(options); + Require(values.antialias == expected.antialias, "antialias was not retained"); + Require(values.subpixel_order == expected.subpixel_order, "subpixel order was not retained"); + Require(values.hint_style == expected.hint_style, "hint style was not retained"); + Require(values.hint_metrics == expected.hint_metrics, "hint metrics were not retained"); +} + +} + +int main() { + Require(CAIRO_ANTIALIAS_FAST == 4 && CAIRO_ANTIALIAS_GOOD == 5 && CAIRO_ANTIALIAS_BEST == 6, + "antialias enum values differ from Cairo"); + Require(CAIRO_SUBPIXEL_ORDER_VBGR == 4 && CAIRO_HINT_STYLE_FULL == 4 && CAIRO_HINT_METRICS_ON == 2, + "font hinting enum values differ from Cairo"); + + auto* nil = cairo_font_options_copy(nullptr); + Require(nil != nullptr && nil == cairo_font_options_copy(nil), "null copy does not return the shared error object"); + Require(cairo_font_options_get_antialias(nullptr) == CAIRO_ANTIALIAS_DEFAULT, + "null getter does not return the default antialias mode"); + cairo_font_options_set_antialias(nullptr, CAIRO_ANTIALIAS_BEST); + cairo_font_options_set_hint_style(nullptr, CAIRO_HINT_STYLE_FULL); + cairo_font_options_set_hint_metrics(nullptr, CAIRO_HINT_METRICS_ON); + cairo_font_options_set_subpixel_order(nullptr, CAIRO_SUBPIXEL_ORDER_VBGR); + cairo_font_options_set_antialias(nil, CAIRO_ANTIALIAS_BEST); + cairo_font_options_set_hint_style(nil, CAIRO_HINT_STYLE_FULL); + cairo_font_options_set_hint_metrics(nil, CAIRO_HINT_METRICS_ON); + cairo_font_options_set_subpixel_order(nil, CAIRO_SUBPIXEL_ORDER_VBGR); + Require(cairo_font_options_get_antialias(nil) == CAIRO_ANTIALIAS_DEFAULT, + "error object can be changed"); + cairo_font_options_destroy(nullptr); + cairo_font_options_destroy(nil); + + for (int iteration = 0; iteration != 4096; ++iteration) { + auto* original = cairo_font_options_create(); + Require(original != nullptr && original != nil, "create did not allocate a mutable object"); + RequireValues(original, {CAIRO_ANTIALIAS_DEFAULT, CAIRO_SUBPIXEL_ORDER_DEFAULT, + CAIRO_HINT_STYLE_DEFAULT, CAIRO_HINT_METRICS_DEFAULT}); + Require(cairo_font_options_get_antialias(original) == CAIRO_ANTIALIAS_DEFAULT, + "new options have nondefault antialiasing"); + + const auto antialias = static_cast(iteration % 9 - 1); + const auto subpixel = static_cast(iteration % 7 - 1); + const auto style = static_cast(iteration % 8 - 1); + const auto metrics = static_cast(iteration % 5 - 1); + cairo_font_options_set_antialias(original, antialias); + cairo_font_options_set_subpixel_order(original, subpixel); + cairo_font_options_set_hint_style(original, style); + cairo_font_options_set_hint_metrics(original, metrics); + RequireValues(original, {antialias, subpixel, style, metrics}); + Require(cairo_font_options_get_antialias(original) == antialias, + "getter rejects an enum value that Cairo accepts"); + + auto* copy = cairo_font_options_copy(original); + Require(copy != nullptr && copy != original && copy != nil, "copy did not allocate a new object"); + RequireValues(copy, {antialias, subpixel, style, metrics}); + cairo_font_options_set_antialias(copy, CAIRO_ANTIALIAS_BEST); + cairo_font_options_set_subpixel_order(copy, CAIRO_SUBPIXEL_ORDER_VBGR); + cairo_font_options_set_hint_style(copy, CAIRO_HINT_STYLE_FULL); + cairo_font_options_set_hint_metrics(copy, CAIRO_HINT_METRICS_ON); + RequireValues(original, {antialias, subpixel, style, metrics}); + cairo_font_options_destroy(original); + RequireValues(copy, {CAIRO_ANTIALIAS_BEST, CAIRO_SUBPIXEL_ORDER_VBGR, + CAIRO_HINT_STYLE_FULL, CAIRO_HINT_METRICS_ON}); + cairo_font_options_destroy(copy); + } + + std::puts("cairo font options tests passed"); + return 0; +} diff --git a/docs/dev/debt/libcairo.md b/docs/dev/debt/libcairo.md index 796dae4746..0446ba0d98 100644 --- a/docs/dev/debt/libcairo.md +++ b/docs/dev/debt/libcairo.md @@ -2,13 +2,12 @@ ### Callable exports -- [Callable exports](../../../core/libs/prx/libcairo/Export.cpp) - 161 callable NIDs match the [public 3.20 catalogue](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libcairo.c) and the [selected export metadata](https://gist.github.com/kyon-eth/a0bb48f2b3070a5eac8a638810f195f2/a3838ac54ebcc0bbfeb3cc46002a9a2dcce4dbdc) from two version-labelled images (SHA-256 `ec2158e0c5875316d200037d7f4bc48861626451752523f6930d687307c75ef8`). The labels are not authenticated. Prototypes and behavior are unverified; these declarations throw through `NotImplemented_nid_no_patch` and implement no runtime functionality. +- [Callable exports](../../../core/libs/prx/libcairo/Export.cpp) - 161 callable NIDs match the [public 3.20 catalogue](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libcairo.c) and the [selected export metadata](https://gist.github.com/kyon-eth/a0bb48f2b3070a5eac8a638810f195f2/a3838ac54ebcc46002a9a2dcce4dbdc) from two version-labelled images (SHA-256 `ec2158e0c5875316d200037d7f4bc48861626451752523f6930d687307c75ef8`). The labels are not authenticated. Eight font-options exports have Cairo-compatible signatures and implementations in [FontOptions.cpp](../../../core/libs/prx/libcairo/FontOptions.cpp). The remaining 153 exports below have unverified prototypes and behavior and throw through `NotImplemented_nid_no_patch`. | Name | NID | | --- | --- | | `cairo_scaled_font_get_font_face` | `+BL3-h8zGDo` | | `cairo_paint` | `+PEo5Z7DOlY` | -| `cairo_font_options_set_hint_metrics` | `+TktRPDfw0Y` | | `cairo_set_font_options` | `-aqFIBn0h78` | | `cairo_device_to_user_distance` | `-v5CZVGkN3k` | | `cairo_font_face_set_user_data` | `1GGpdjyZslI` | @@ -62,7 +61,6 @@ | `cairo_mesh_pattern_line_to` | `HTB1uUwgSVQ` | | `cairo_get_line_cap` | `Hkp7M+pJILk` | | `cairo_region_get_rectangle` | `I5wa74016LQ` | -| `cairo_font_options_set_hint_style` | `IDmch1bKy34` | | `cairo_set_miter_limit` | `IKnEk6g6ZV0` | | `cairo_translate` | `IdKE2DrNBck` | | `cairo_font_face_destroy` | `IgBFcd4cF28` | @@ -83,7 +81,6 @@ | `cairo_set_line_join` | `Ori3CK8Sx6g` | | `cairo_pop_group_to_source` | `Oyzf6kDk6Ns` | | `cairo_mesh_pattern_set_corner_color_rgba` | `P+Hm5enx01Y` | -| `cairo_font_options_copy` | `P2IjaGD66NE` | | `cairo_identity_matrix` | `PaSEtyxiC0o` | | `cairo_save` | `Pj8asGd55jA` | | `cairo_clip` | `PkGZZi1G2Gw` | @@ -93,7 +90,6 @@ | `cairo_stroke_preserve` | `RzM4ZhlKzPg` | | `cairo_path_extents` | `S6ieIssLpCA` | | `cairo_ft_scaled_font_unlock_face` | `SJMLSxaYvWU` | -| `cairo_font_options_get_antialias` | `SNSLa0ggfpw` | | `cairo_mesh_pattern_move_to` | `T0JOV6EajrA` | | `cairo_paint_with_alpha` | `TAIhaLjmAlg` | | `cairo_set_fill_rule` | `THcHgZElshs` | @@ -128,12 +124,9 @@ | `cairo_pattern_set_filter` | `gsqESe70WD0` | | `cairo_new_path` | `hhuvB3JXuMM` | | `cairo_push_group` | `hlh15JjNtWg` | -| `cairo_font_options_destroy` | `huXPyr6A9to` | | `cairo_ft_font_options_substitute` | `ii895dyBjMo` | | `cairo_image_surface_get_width` | `inWumYPZgtw` | -| `cairo_font_options_create` | `j2xKmRzIV84` | | `cairo_device_to_user` | `jyf32aEUoMs` | -| `cairo_font_options_set_antialias` | `kNua19l9-0I` | | `cairo_glyph_free` | `lAEbTbSUTTo` | | `cairo_select_font_face` | `ldk9m5h+-no` | | `cairo_line_to` | `mSda1B-vfkM` | @@ -143,7 +136,6 @@ | `cairo_set_line_cap` | `oFltTuV0f7Y` | | `cairo_scaled_font_text_to_glyphs` | `ojbXRC0CmUo` | | `cairo_scaled_font_glyph_extents` | `oy7NcAkex1I` | -| `cairo_font_options_set_subpixel_order` | `pk6gDcQRKxs` | | `cairo_show_text` | `psdqLsaPnkk` | | `cairo_set_font_face` | `qzEwpfbLVxI` | | `cairo_close_path` | `rENdf2qgOE4` | From c254b5babdb8339af366d5a1ae873ba081c6dfbf Mon Sep 17 00:00:00 2001 From: DeveshB-1 Date: Sun, 1 Nov 2026 15:17:27 +0530 Subject: [PATCH 183/612] fix(libSceCompanionHttpd): return INVALID_PARAM from sceCompanionHttpdOptParamInitialize for an unwritable block --- core/libs/prx/libSceCompanionHttpd/Export.cpp | 7 ++++++- core/libs/tests/GuestCompanionHttpd.cpp | 1 + docs/dev/debt/libSceCompanionHttpd.md | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceCompanionHttpd/Export.cpp b/core/libs/prx/libSceCompanionHttpd/Export.cpp index d999be4d16..5401068823 100644 --- a/core/libs/prx/libSceCompanionHttpd/Export.cpp +++ b/core/libs/prx/libSceCompanionHttpd/Export.cpp @@ -1,4 +1,5 @@ #include "prx/libSceCompanionHttpd/include/Api.hpp" +#include #include #include #include @@ -37,7 +38,11 @@ extern "C" { int APS5_VABI sceCompanionHttpdOptParamInitialize(CompanionHttpdOptParam* param) { if (param == nullptr) return CompanionHttpdErrorInvalidParam; - std::memset(param, 0, sizeof(*param)); + CompanionHttpdOptParam zero{}; + const int savedErrno = errno; + const bool copied = GuestCopyOut_nid_no_patch(param, &zero, sizeof(zero)); + errno = savedErrno; + if (!copied) return CompanionHttpdErrorInvalidParam; return 0; } diff --git a/core/libs/tests/GuestCompanionHttpd.cpp b/core/libs/tests/GuestCompanionHttpd.cpp index 05aad8a7d1..387201ea75 100644 --- a/core/libs/tests/GuestCompanionHttpd.cpp +++ b/core/libs/tests/GuestCompanionHttpd.cpp @@ -44,6 +44,7 @@ void CheckOptionBlock() { unsigned char block[0x70]; std::memset(block, 0x5a, sizeof(block)); Require(sceCompanionHttpdOptParamInitialize(nullptr) == InvalidParam, "a null option block must be rejected"); + Require(sceCompanionHttpdOptParamInitialize(reinterpret_cast(0x10)) == InvalidParam, "an unwritable option block must return InvalidParam"); Require(sceCompanionHttpdOptParamInitialize(reinterpret_cast(block)) == 0, "sceCompanionHttpdOptParamInitialize failed"); for (std::size_t index = 0; index < sizeof(block); ++index) { Require(block[index] == (index < 0x60 ? 0u : 0x5au), "the option block must be zeroed over its 0x60 bytes and nothing past them"); diff --git a/docs/dev/debt/libSceCompanionHttpd.md b/docs/dev/debt/libSceCompanionHttpd.md index 357c1a0dcb..fba4d6871b 100644 --- a/docs/dev/debt/libSceCompanionHttpd.md +++ b/docs/dev/debt/libSceCompanionHttpd.md @@ -2,7 +2,7 @@ ### Unknown function info -- [sceCompanionHttpdOptParamInitialize, sceCompanionHttpdInitialize](../../../core/libs/prx/libSceCompanionHttpd/Export.cpp) (libSceCompanionHttpd) - the option block is taken as 0x60 bytes: titles fill a work memory pointer at +0x08, its size at +0x10 and a temporary data mount point at +0x58 after `sceCompanionHttpdOptParamInitialize`. Its other fields and the defaults the console writes are unknown, so `sceCompanionHttpdOptParamInitialize` zeroes the 0x60 bytes and `sceCompanionHttpdInitialize` reads none of them; both reject a null block with `INVALID_PARAM` +- [sceCompanionHttpdOptParamInitialize, sceCompanionHttpdInitialize](../../../core/libs/prx/libSceCompanionHttpd/Export.cpp) (libSceCompanionHttpd) - the option block is taken as 0x60 bytes: titles fill a work memory pointer at +0x08, its size at +0x10 and a temporary data mount point at +0x58 after `sceCompanionHttpdOptParamInitialize`. Its other fields and the defaults the console writes are unknown, so `sceCompanionHttpdOptParamInitialize` zeroes the 0x60 bytes and `sceCompanionHttpdInitialize` reads none of them; both reject a null block with `INVALID_PARAM`; returning `INVALID_PARAM` for an unwritable block is a host-safety fallback as console behavior for unwritable memory has not been measured - [sceCompanionHttpdAddHeader, sceCompanionHttpdSetStatus, sceCompanionHttpdSetBody](../../../core/libs/prx/libSceCompanionHttpd/Export.cpp) (libSceCompanionHttpd) - the PS5 13.60 exports at NIDs `8pWltDG7h6A`, `w7oz0AWHpT4` and `h3OvVxzX4qM` each consist of `xor eax, eax; ret` in `libSceCompanionHttpd.sprx` (SHA-256 `BA52B40AB6B88596E4FA4B4D5A66D8E8E38B5CD43C08D3882730D7C2C565E293`); their typed declarations agree with the [public CompanionHttpd ABI](https://github.com/shadps4-emu/shadPS4/blob/860d73b7e99559106a3ffa3d3d69f0f9237e115e/src/core/libraries/companion/companion_httpd.h#L453-L455). They return zero without reading or writing their arguments. Response construction remains unsupported - [sceCompanionHttpdGet2ndScreenStatus, sceCompanionHttpdGetUserId, sceCompanionHttpdInitialize2, sceCompanionHttpdRegisterRequestBodyReceptionCallback, sceCompanionHttpdRegisterRequestCallback2, sceCompanionHttpdUnregisterRequestBodyReceptionCallback](../../../core/libs/prx/libSceCompanionHttpd/Export.cpp) (libSceCompanionHttpd) - the rest of the library's exports, declared without parameters and without an implementation; their signatures are not verified, so every call throws From 1a05d1ebbe8404bf685c3478b2feb07adda8a4ad Mon Sep 17 00:00:00 2001 From: DeveshB-1 Date: Sun, 1 Nov 2026 15:25:46 +0530 Subject: [PATCH 184/612] fix(libSceCdlgG2P): return INVALID_ARGUMENT from sceG2PDialogGetResult for an unwritable result --- core/libs/prx/libSceCdlgG2P/Export.cpp | 7 ++++++- core/libs/tests/GuestG2PDialog.cpp | 1 + docs/dev/debt/libSceCdlgG2P.md | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceCdlgG2P/Export.cpp b/core/libs/prx/libSceCdlgG2P/Export.cpp index a6f7113420..dba3faa623 100644 --- a/core/libs/prx/libSceCdlgG2P/Export.cpp +++ b/core/libs/prx/libSceCdlgG2P/Export.cpp @@ -2,6 +2,7 @@ #include "prx/libSceUserService/UserService.hpp" #include "SceTypes.hpp" #include +#include #include #include #include @@ -60,7 +61,11 @@ int APS5_VABI sceG2PDialogGetResult(CommonDialogResultPrefix* result) { const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; - result->result = COMMON_DIALOG_RESULT_USER_CANCELED; + const std::int32_t cancelResult = COMMON_DIALOG_RESULT_USER_CANCELED; + const int savedErrno = errno; + const bool copied = GuestCopyOut_nid_no_patch(&result->result, &cancelResult, sizeof(cancelResult)); + errno = savedErrno; + if (!copied) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; return 0; } diff --git a/core/libs/tests/GuestG2PDialog.cpp b/core/libs/tests/GuestG2PDialog.cpp index 7a363fc84d..150fbdbc7e 100644 --- a/core/libs/tests/GuestG2PDialog.cpp +++ b/core/libs/tests/GuestG2PDialog.cpp @@ -77,6 +77,7 @@ int main() { Require(sceG2PDialogOpen(nullptr) == kErrArgNull); Require(sceG2PDialogGetResult(nullptr) == kErrArgNull); + Require(sceG2PDialogGetResult(reinterpret_cast(0x10)) == static_cast(0x80B8000Au)); std::int32_t prefixResult[8] = {-1, -1, -1, -1, 0x5a, 0x5a, 0x5a, 0x5a}; Require(sceG2PDialogGetResult(reinterpret_cast(prefixResult)) == 0); Require(prefixResult[2] == kResultUserCanceled); diff --git a/docs/dev/debt/libSceCdlgG2P.md b/docs/dev/debt/libSceCdlgG2P.md index ad18657b3d..15e544a2c4 100644 --- a/docs/dev/debt/libSceCdlgG2P.md +++ b/docs/dev/debt/libSceCdlgG2P.md @@ -2,4 +2,4 @@ ### Silent stubs -- [libSceCdlgG2P](../../../core/libs/prx/libSceCdlgG2P/Export.cpp) - the G2P dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled. The 128-byte open parameters, common-header validation and result field at byte 8 are recovered. Native service state transitions remain unimplemented; PPSA01289 imports it +- [libSceCdlgG2P](../../../core/libs/prx/libSceCdlgG2P/Export.cpp) - the G2P dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled. The 128-byte open parameters, common-header validation and result field at byte 8 are recovered. Native service state transitions remain unimplemented; PPSA01289 imports it; returning `COMMON_DIALOG_ERROR_INVALID_ARGUMENT` for an unwritable result is a host-safety fallback as console behavior for unwritable memory has not been measured From 60b75d0d0b9a0743c54b9de554f3eb5788e03199 Mon Sep 17 00:00:00 2001 From: DeveshB-1 Date: Sun, 1 Nov 2026 15:46:38 +0530 Subject: [PATCH 185/612] fix(libScePadTracker): return error from scePadTrackerGetWorkingMemorySize for unwritable outputs --- core/libs/prx/libScePadTracker/Export.cpp | 14 ++++++++++++-- core/libs/tests/GuestPadTracker.cpp | 2 ++ docs/dev/debt/libScePadTracker.md | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libScePadTracker/Export.cpp b/core/libs/prx/libScePadTracker/Export.cpp index 2ddae2fad6..5fc489e90f 100644 --- a/core/libs/prx/libScePadTracker/Export.cpp +++ b/core/libs/prx/libScePadTracker/Export.cpp @@ -1,4 +1,5 @@ #include "prx/libScePadTracker/include/Api.hpp" +#include #include #include #include "SceTypes.hpp" @@ -13,8 +14,17 @@ int APS5_VABI scePadTrackerCalibrate() { int APS5_VABI scePadTrackerGetWorkingMemorySize(std::uint32_t* first, std::uint32_t* second) { if (first == nullptr || second == nullptr) return static_cast(0x80c40003); - *first = 0x800000; - *second = 0x800000; + std::uint32_t originalFirst = 0; + const int savedErrno = errno; + const bool readableFirst = GuestCopyIn_nid_no_patch(&originalFirst, first, sizeof(originalFirst)); + const std::uint32_t memorySize = 0x800000; + const bool copiedFirst = GuestCopyOut_nid_no_patch(first, &memorySize, sizeof(memorySize)); + const bool copiedSecond = copiedFirst ? GuestCopyOut_nid_no_patch(second, &memorySize, sizeof(memorySize)) : false; + if (copiedFirst && !copiedSecond && readableFirst) { + GuestCopyOut_nid_no_patch(first, &originalFirst, sizeof(originalFirst)); + } + errno = savedErrno; + if (!copiedFirst || !copiedSecond) return static_cast(0x80c40003); return 0; } diff --git a/core/libs/tests/GuestPadTracker.cpp b/core/libs/tests/GuestPadTracker.cpp index 2fb34b53f5..0a358c8b6c 100644 --- a/core/libs/tests/GuestPadTracker.cpp +++ b/core/libs/tests/GuestPadTracker.cpp @@ -18,6 +18,8 @@ int main() { std::array second{kFill, kFill}; Require(scePadTrackerGetWorkingMemorySize(nullptr, nullptr) == kErrInvalidArg); + Require(scePadTrackerGetWorkingMemorySize(reinterpret_cast(0x10), second.data()) == kErrInvalidArg); + Require(scePadTrackerGetWorkingMemorySize(first.data(), reinterpret_cast(0x10)) == kErrInvalidArg); Require(scePadTrackerGetWorkingMemorySize(first.data(), nullptr) == kErrInvalidArg); Require(scePadTrackerGetWorkingMemorySize(nullptr, second.data()) == kErrInvalidArg); Require(first[0] == kFill && first[1] == kFill && second[0] == kFill && second[1] == kFill); diff --git a/docs/dev/debt/libScePadTracker.md b/docs/dev/debt/libScePadTracker.md index 09d4dc9521..f029d96311 100644 --- a/docs/dev/debt/libScePadTracker.md +++ b/docs/dev/debt/libScePadTracker.md @@ -3,4 +3,4 @@ ### Unknown function info - [scePadTrackerCalibrate, scePadTrackerInit, scePadTrackerReadState, scePadTrackerTerm and scePadTrackerUpdate](../../../core/libs/prx/libScePadTracker/Export.cpp) (libScePadTracker) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libScePadTracker.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified -- [scePadTrackerGetWorkingMemorySize](../../../core/libs/prx/libScePadTracker/Export.cpp) (libScePadTracker) - what the two sizes are for is unknown. It follows firmware 13.42's libScePadTracker.sprx, read from its disassembly and run natively on x86-64 (not a console): a null first or second pointer returns `0x80c40003` and writes nothing; otherwise it stores the 32-bit value 0x800000 through each pointer and returns 0. Both values are module data that no other code in the module references. +- [scePadTrackerGetWorkingMemorySize](../../../core/libs/prx/libScePadTracker/Export.cpp) (libScePadTracker) - what the two sizes are for is unknown. It follows firmware 13.42's libScePadTracker.sprx, read from its disassembly and run natively on x86-64 (not a console): a null first or second pointer returns `0x80c40003` and writes nothing; otherwise it stores the 32-bit value 0x800000 through each pointer and returns 0. Both values are module data that no other code in the module references. Unwritable pointer outputs also return `0x80c40003`. From bf382bf10401215daf5f0a2ccb498b724391818d Mon Sep 17 00:00:00 2001 From: akkadbakkad89 Date: Sun, 1 Nov 2026 15:49:27 +0530 Subject: [PATCH 186/612] docs(libSceAppContent): move the store-country debt entry to the section end #13909 and #14141 both extend the debt entries around the Functional heading; placing this entry at the end of the list keeps all three PRs textually mergeable in any order. --- docs/dev/debt/libSceAppContent.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/dev/debt/libSceAppContent.md b/docs/dev/debt/libSceAppContent.md index d511282b57..adf5b5489d 100644 --- a/docs/dev/debt/libSceAppContent.md +++ b/docs/dev/debt/libSceAppContent.md @@ -18,8 +18,6 @@ - [libSceAppContent pending exports](../../../core/libs/prx/libSceAppContent/Export.cpp) - 8 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAppContent.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. ### Functional -- [sceAppContentGetDownloadedStoreCountry](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - the firmware 4.03 `libSceAppContent.sprx` body (SHA-256 `36039805f77483b32861b7936ddcd859708b764682984a4625cb7f8d1efdabc4`) is a thin RPC client: initialization guard, null-parameter checks, an entitlement-label padding gate at SDK 0x01500000, then a vtable call marshalling a system-service request (selector `0x20005`, `{serviceLabel}` + a 0x14-byte label input + a 0x18-byte reply buffer) and translating the reply status into the `0x80d9xxxx` family; the translator passes `0x80d91000` GET_STORE_COUNTRY through unchanged. - - The export signature is thunk-verified (`uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, void* storeCountry`); the body was not compared with 13.60 — its export inventory establishes symbol presence only. With no host system service the export answers `GET_STORE_COUNTRY` after the verified checks as the chosen fallback for an absent service, not a measured native result; the real service replies and the 0x18-byte reply layout are unverified. - [Initialization](../../../core/libs/prx/libSceAppContent/Export.cpp) - service readiness and boot attributes are not modeled; successful initialization returns a zero boot record. Initialization-state guards, SDK 0x01500000 repeated-initialization errors and reserved-field checks are recovered. SDK-dependent service-error translation remains incomplete. - [Additional content](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out; `sceAppContentGetAddcontInfo` answers `DRM_NO_ENTITLEMENT` for every label and `sceAppContentGetAddcontInfoList` reports 0 entries; `sceAppContentAddcontEnqueueDownload` answers `PARAMETER` for a null label and `NOT_FOUND` otherwise, since no download is ever queued; `sceAppContentAddcontDelete` answers `PARAMETER` for a null label and `NOT_FOUND` otherwise, since no additional content is ever installed. `sceAppContentGetAddcontDownloadProgress` answers `PARAMETER` for a null label or progress and `NOT_FOUND` otherwise, as [dlc_emu](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.cpp#L2328-L2341) does for a label that is not installed additional content, and never writes the progress. Its 16-byte layout (`DataSize`, then `DownloadedSize`, both 64-bit) follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/AppContent/AppContent.cs#L45-L54) and is unused here. The signatures of `sceAppContentAddcontEnqueueDownload` and `sceAppContentGetAddcontDownloadProgress` follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/AppContent/AppContent.cs#L140-L148), and the signature of `sceAppContentAddcontDelete` follows [dlc_emu](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.h#L91-L92); dlc_emu is not a console measurement and no console source gives the error codes, so the signatures and the error codes are unverified @@ -27,3 +25,6 @@ - [sceAppContentGetAddcontInfoByEntitlementId, sceAppContentGetAddcontInfoListByIroTag and sceAppContentGetPftFlag](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - unverified on a PS5. After the initialization guard they give [dlc_emu's](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.cpp#L2040-L2086) answers for content it does not know: `DRM_NO_ENTITLEMENT` without writing the info, 0 entries for any tag (a null `hitNum` beside a list returns 0, unlike `sceAppContentGetAddcontInfoList`), and a [PFT flag of 0](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.cpp#L2343-L2347), and `PARAMETER` for null arguments, with dlc_emu's [prototypes](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.h#L74-L131) - [sceAppContentGetEntitlementKey](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - the service result without an entitlement is unverified: this model returns `DRM_NO_ENTITLEMENT` and leaves the 16-byte key unchanged. Initialization, null-pointer and SDK 0x01500000 label-padding checks match the reported [firmware 13.42 module probe](https://github.com/boykopovar/AnyPS5/pull/9638#issuecomment-6390573735), run in an emulator, not on hardware. That module forwards service labels 0, 7, 8 and 0xFFFFFFFF without checking them; this library ignores the service label. The system service's own validation and errors remain unknown. - [Temporary app-content storage](../../../core/libs/prx/libSceAppContent/Export.cpp) console behaviour and hardware return values are unverified, including behaviour with open files across unmount; host filesystem failures throw. Storage models one `/temp0` mount, preserving files on unmount while rejecting new path resolutions until remount, and leaving existing open host descriptors usable. `sceAppContentTemporaryDataMount` delegates to `sceAppContentTemporaryDataMount2` with option 1 (`TEMPORARY_DATA_OPTION_FORMAT`) to format on mount as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/app_content/app_content.cpp#L550-L553) does. Mount, format, space-query and unmount errors follow [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/sys/sys_path.pas); pointer ABI follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/AppContent/AppContent.cs). + +- [sceAppContentGetDownloadedStoreCountry](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - the firmware 4.03 `libSceAppContent.sprx` body (SHA-256 `36039805f77483b32861b7936ddcd859708b764682984a4625cb7f8d1efdabc4`) is a thin RPC client: initialization guard, null-parameter checks, an entitlement-label padding gate at SDK 0x01500000, then a vtable call marshalling a system-service request (selector `0x20005`, `{serviceLabel}` + a 0x14-byte label input + a 0x18-byte reply buffer) and translating the reply status into the `0x80d9xxxx` family; the translator passes `0x80d91000` GET_STORE_COUNTRY through unchanged. + - The export signature is thunk-verified (`uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, void* storeCountry`); the body was not compared with 13.60 — its export inventory establishes symbol presence only. With no host system service the export answers `GET_STORE_COUNTRY` after the verified checks as the chosen fallback for an absent service, not a measured native result; the real service replies and the 0x18-byte reply layout are unverified. From 6151b2cca0b7132b7c4056694df1db569c3eee5d Mon Sep 17 00:00:00 2001 From: DeveshB-1 Date: Sun, 1 Nov 2026 15:49:38 +0530 Subject: [PATCH 187/612] fix(libSceMoveTracker): return error from sceMoveTrackerGetWorkingMemorySize for unwritable outputs --- core/libs/prx/libSceMoveTracker/Export.cpp | 15 +++++++++++++-- core/libs/tests/GuestMoveTracker.cpp | 2 ++ docs/dev/debt/libSceMoveTracker.md | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceMoveTracker/Export.cpp b/core/libs/prx/libSceMoveTracker/Export.cpp index f285fddbbf..aa15464487 100644 --- a/core/libs/prx/libSceMoveTracker/Export.cpp +++ b/core/libs/prx/libSceMoveTracker/Export.cpp @@ -1,4 +1,5 @@ #include "prx/libSceMoveTracker/include/Api.hpp" +#include #include #include #include "SceTypes.hpp" @@ -47,8 +48,18 @@ int APS5_VABI sceMoveTrackerGetVersion() { int APS5_VABI sceMoveTrackerGetWorkingMemorySize(std::uint32_t* totalSize, std::uint32_t* processingSize) { if (totalSize == nullptr || processingSize == nullptr) return static_cast(0x80ef0003); - *totalSize = 0xa00000; - *processingSize = 0x800000; + std::uint32_t originalTotal = 0; + const int savedErrno = errno; + const bool readableTotal = GuestCopyIn_nid_no_patch(&originalTotal, totalSize, sizeof(originalTotal)); + const std::uint32_t total = 0xa00000; + const std::uint32_t processing = 0x800000; + const bool copiedTotal = GuestCopyOut_nid_no_patch(totalSize, &total, sizeof(total)); + const bool copiedProcessing = copiedTotal ? GuestCopyOut_nid_no_patch(processingSize, &processing, sizeof(processing)) : false; + if (copiedTotal && !copiedProcessing && readableTotal) { + GuestCopyOut_nid_no_patch(totalSize, &originalTotal, sizeof(originalTotal)); + } + errno = savedErrno; + if (!copiedTotal || !copiedProcessing) return static_cast(0x80ef0003); return 0; } diff --git a/core/libs/tests/GuestMoveTracker.cpp b/core/libs/tests/GuestMoveTracker.cpp index 0d141bf511..4fbe7c3434 100644 --- a/core/libs/tests/GuestMoveTracker.cpp +++ b/core/libs/tests/GuestMoveTracker.cpp @@ -17,6 +17,8 @@ int main() { std::array processing{kFill, kFill}; Require(sceMoveTrackerGetWorkingMemorySize(nullptr, nullptr) == kErrInvalidArg); + Require(sceMoveTrackerGetWorkingMemorySize(reinterpret_cast(0x10), processing.data()) == kErrInvalidArg); + Require(sceMoveTrackerGetWorkingMemorySize(total.data(), reinterpret_cast(0x10)) == kErrInvalidArg); Require(sceMoveTrackerGetWorkingMemorySize(total.data(), nullptr) == kErrInvalidArg); Require(sceMoveTrackerGetWorkingMemorySize(nullptr, processing.data()) == kErrInvalidArg); Require(total[0] == kFill && total[1] == kFill && processing[0] == kFill && processing[1] == kFill); diff --git a/docs/dev/debt/libSceMoveTracker.md b/docs/dev/debt/libSceMoveTracker.md index 594d9b89ec..134a325d24 100644 --- a/docs/dev/debt/libSceMoveTracker.md +++ b/docs/dev/debt/libSceMoveTracker.md @@ -7,4 +7,4 @@ ### Unknown function info - [libSceMoveTracker](../../../core/libs/prx/libSceMoveTracker/Export.cpp) remains unimplemented. Calibration direction, camera/controller input, capture initialization, state query, memory-size query and playback output signatures are recovered. Camera and controller records occupy 24 bytes, state records 160 bytes, and playback metadata 16 bytes; their complete field meanings still require reconstruction. Initialization consumes a work-memory pointer, a required nonzero 64-bit argument and two 32-bit GPU queue selectors below eight. The second argument is only checked for zero; its declared type remains unresolved. Capture/playback helper signatures need further checking against caller register use. -- [sceMoveTrackerGetWorkingMemorySize](../../../core/libs/prx/libSceMoveTracker/Export.cpp) (libSceMoveTracker) - what the two sizes cover is unknown beyond the parameter names. It follows firmware 13.42's libSceMoveTracker.sprx, read from its disassembly and run natively on x86-64 (not a console): a null pointer returns `0x80ef0003` and writes nothing; otherwise it stores the 32-bit values 0xa00000 and 0x800000 through the first and second pointer and returns 0. The first is module data 0x200000 plus the 0x800000 that an internal function returns; every rip-relative reference to that data in the module reads it. +- [sceMoveTrackerGetWorkingMemorySize](../../../core/libs/prx/libSceMoveTracker/Export.cpp) (libSceMoveTracker) - what the two sizes cover is unknown beyond the parameter names. It follows firmware 13.42's libSceMoveTracker.sprx, read from its disassembly and run natively on x86-64 (not a console): a null pointer returns `0x80ef0003` and writes nothing; otherwise it stores the 32-bit values 0xa00000 and 0x800000 through the first and second pointer and returns 0. The first is module data 0x200000 plus the 0x800000 that an internal function returns; every rip-relative reference to that data in the module reads it. Unwritable pointer outputs also return `0x80ef0003`. From 6a4b61d6c29e08aa814c6fe688f3533e26a041d5 Mon Sep 17 00:00:00 2001 From: edubox Date: Mon, 26 Oct 2026 07:26:16 +0100 Subject: [PATCH 188/612] feat(ngs2): allow the custom UserFx module on custom sampler racks --- .../libs/prx/libSceNgs2.native/src/Custom.cpp | 15 ++++- .../libSceNgs2.native/tests/Ngs2Custom.cpp | 60 ++++++++++++++++++- docs/dev/debt/libSceNgs2.native.md | 2 +- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceNgs2.native/src/Custom.cpp b/core/libs/prx/libSceNgs2.native/src/Custom.cpp index 675c7e7101..545e87a099 100644 --- a/core/libs/prx/libSceNgs2.native/src/Custom.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Custom.cpp @@ -78,7 +78,9 @@ int Ngs2CheckCustomRack(std::uint32_t rackId, const Ngs2CustomRackOption& option } else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_FILTER) { if (info.option != nullptr || info.state_size != 0 || info.state_offset != 0) APS5_INVALID_ARG_EX; } else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX) { - if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) throw std::runtime_error("NGS2: the custom UserFx module on a custom sampler rack is not implemented"); + if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER && rackId != SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) { + throw std::runtime_error("NGS2: the custom UserFx module on this rack is not implemented"); + } if (info.option != nullptr) throw std::runtime_error("NGS2: a custom UserFx module option is not implemented"); } else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_DELAY) { CheckDelayModule(rackId, info); @@ -374,8 +376,15 @@ void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { return; } if ((param.id & ~0x1fu) == SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX) { - if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack"); - SetCustomUserFxParam(voice, param.id & 0x1f, ParamAs(param)); + if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER && rackId != SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) { + throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack"); + } + auto index = param.id & 0x1f; + if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) { + if (index == 0) APS5_INVALID_ARG_EX; + index--; + } + SetCustomUserFxParam(voice, index, ParamAs(param)); return; } if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack"); diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp index af4804eebb..d30726cb01 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp @@ -651,7 +651,7 @@ static void TestCustomRackChecks() { for (std::uint32_t index = 0; index < count; ++index) { sampler.custom_rack_option.module[index].module_id = SCE_NGS2_CUSTOM_MODULE_ID_FILTER; } - sampler.custom_rack_option.module[count - 1].module_id = 0x19; + sampler.custom_rack_option.module[count - 1].module_id = 0x99; Require(CreateSamplerThrows(sampler)); for (const bool useAllocator : {false, true}) { rack = 0; @@ -778,6 +778,63 @@ static void TestCustomLegacyUserFx(std::uint32_t moduleIndex) { Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); } +static Ngs2CustomSamplerRackOption LegacySamplerRackOption(const Ngs2CustomModuleOption* option, std::uint32_t moduleIndex) { + Ngs2CustomSamplerRackOption rack{}; + auto& common = rack.custom_rack_option.rack_option; + common.size = sizeof(rack); + common.max_grain_samples = 512; + common.max_voices = 1; + common.max_matrices = 1; + common.max_ports = 1; + rack.custom_rack_option.num_buffers = 1; + rack.custom_rack_option.num_modules = moduleIndex + 1; + for (std::uint32_t index = 0; index <= moduleIndex; ++index) { + rack.custom_rack_option.module[index].option = option; + rack.custom_rack_option.module[index].module_id = SCE_NGS2_CUSTOM_MODULE_ID_USER_FX; + } + rack.max_channel_works = 1; + rack.max_waveform_blocks = 1; + return rack; +} + +static void TestCustomSamplerLegacyUserFx(std::uint32_t moduleIndex) { + const auto system = CreateSystem(); + const auto master = Mastering(system, 2); + const Ngs2CustomModuleOption header{sizeof(Ngs2CustomModuleOption)}; + auto option = LegacySamplerRackOption(nullptr, moduleIndex); + Ngs2ContextBufferInfo query{}; + Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &option.custom_rack_option.rack_option, &query) == SCE_NGS2_OK); + const auto info = Buffer(query); + uintptr_t rack = 0; + Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &option.custom_rack_option.rack_option, &info, &rack) == SCE_NGS2_OK); + + const auto voice = Voice(rack); + Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_SETUP, + Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, 48000, 0, 0, 0}}); + std::vector pcm(Grain * 2 * 8, 16384); + const Ngs2WaveformBlock block{0, pcm.size() * sizeof(std::int16_t), 1, 0, static_cast(pcm.size() / 2), 0, 0}; + Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, + Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), 0, 1, &block}); + Patch(voice, master); + Event(voice, SCE_NGS2_VOICE_EVENT_PLAY); + + LegacyUserFxState state{}; + const auto param = Ngs2CustomVoiceUserFxParam{{}, ProcessLegacy, reinterpret_cast(&state), 123, 456}; + Control(voice, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | (moduleIndex + 1), param); + auto out = Render(system); + Require(state.calls == 1 && state.flags == 3); + for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.25f && out[i * 2 + 1] == 0.5f); + out = Render(system); + Require(state.calls == 2 && state.flags == 0); + for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.25f && out[i * 2 + 1] == 0.5f); + + Require(ControlThrows(voice, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | 0, param)); + Require(ControlThrows(voice, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | (moduleIndex + 2), param)); + Require(CreateSamplerThrows(LegacySamplerRackOption(&header, moduleIndex))); + Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + struct DelayOption { std::uint32_t size = 20; std::uint32_t type = 0; @@ -1153,6 +1210,7 @@ int main() { TestCustomSampler(); TestCustomFilter(); for (const auto moduleIndex : {0u, 1u, 23u}) TestCustomLegacyUserFx(moduleIndex); + for (const auto moduleIndex : {0u, 1u}) TestCustomSamplerLegacyUserFx(moduleIndex); TestCustomSamplerGain(); TestCustomGain(); TestCustomModuleInfo(); diff --git a/docs/dev/debt/libSceNgs2.native.md b/docs/dev/debt/libSceNgs2.native.md index 5d2473e0c9..7d756021cb 100644 --- a/docs/dev/debt/libSceNgs2.native.md +++ b/docs/dev/debt/libSceNgs2.native.md @@ -36,7 +36,7 @@ - [NGS2 reverb channel conversion](../../../core/libs/prx/libSceNgs2.native/src/Reverb.cpp) (libSceNgs2.native) - a reverb voice whose `num_input_channels` differs from `num_output_channels` (PPSA03747 sets up 2 inputs and 8 outputs) mixes its ports in the input layout and folds that to mono with the input count's gains, as an equal-count voice does, and runs the comb and allpass stages per output channel. The PS4 library's handling of the dry signal in this case is not checked: here it reaches the output layout through the PS4 default rows, not the PS5 port map, so a stereo input keeps its dry signal on the front left and right and the other outputs carry only the reverb - [NGS2 port matrix](../../../core/libs/prx/libSceNgs2.native/src/Render.cpp) (libSceNgs2.native) - gaps: a mastering voice into a buffer of another count throws when either count is 3, 4, 5 or 7, though PS5 firmware 3.20 and 4.03 render them; the PS4 library has no rows into 3, 4, 5 or 7 and throws, its mastering output is unmeasured, and its reverb conversion and Pan keep the PS4 rows; a port into more than 8 channels without a matrix throws, which is unreachable (`0x804A8052` for a custom submixer voice with over 8 channels, 13.42 module). Not run on PS5: non-sampler sources into 4 or 7 channels and from 1, 2, 6 or 8 channels into 3, 4, 5 or 7, an active reverb (I3DL2), custom submixers with modules and custom mastering 0x4003; they use the sampler rows here. The PS5 rows were measured on that module run on a PC, not a console. A matrix with too few levels mixes only its whole rows - [NGS2 custom submixer rack](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, rack 0x4002) - UserFx2 process flags and setup/process/cleanup order follow only [KytyPS5](https://github.com/KytyPS5/KytyPS5), not a console measurement; layouts follow [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_custom.h`. A module control handler, unequal input and output channels, setup flags and default rack options throw. Module state is a separate buffer, not the `state_offset` slice of voice state; `sceNgs2VoiceGetState` throws for this rack. UserFx2 runs only on grains with input samples, so an effect tail stops with the input. Setup changes from custom process callbacks throw before later modules can use the changed layout; inconsistent input-mixing layouts after callbacks also throw. Console reentry behavior is unknown -- [NGS2 custom UserFx](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native) - the 48-byte process context, process flags, callback error handling and null-handler behavior retain the existing submixer callback model and are not console-verified. Non-null module options throw because their console error is unknown. UserFx on custom sampler rack 0x4001 throws: its parameter indexing and routing are not implemented. Module id 0x19, the 40-byte voice parameter (handler and three user-data words), parameter id `0x40001900 | index`, and lack of option and module state follow [fma-rc's PS5 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943), including parameter dispatch at 0x25ec4. +- [NGS2 custom UserFx](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native) - the 48-byte process context, process flags, callback error handling and null-handler behavior retain the existing submixer callback model and are not console-verified. Non-null module options throw because their console error is unknown. On custom sampler rack 0x4001 the parameter index is one higher than the module index, as fma-rc reads; index 0 throws, and the sampler's single buffer is used because multi-buffer routing is not implemented. Module id 0x19, the 40-byte voice parameter (handler and three user-data words), parameter id `0x40001900 | index`, and lack of option and module state follow [fma-rc's PS5 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943), including parameter dispatch at 0x25ec4. - [NGS2 custom Delay](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, 0x1d) - processing and defaults remain unverified on PS5. The [agreed compatibility path](https://github.com/boykopovar/AnyPS5/pull/9337#issuecomment-6351205140) preserves main's PCM pass-through for Void Terrarium (PPSA03061) until any Delay parameter is set; every `0x40001dxx` parameter throws. Only one-buffer custom submixers with no module state and a 20-byte option are accepted. Option type 0/1, taps 1..8, length 1..20000 and reserved 0 follow [fma-rc's 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943). Other shapes and values throw because their console errors are unknown. This preserves host behavior, not console-equivalent Delay processing - [NGS2 custom module ids](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native) - Reverb (0x1b) and Convolution (0x23) are not processed: a rack that passes their creation checks throws. Per [fma-rc's 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6294323150), creation returns `0x804A8302` for a wrong non-zero state size, and `0x804A8013`, `0x804A8099` and `0x804A8098` for a bad Reverb option size or word and PitchShift word. Unknown: check order, errors for a Gain option, missing Reverb options, Convolution option sizes, and Convolution's state (64 bytes from SDK 3.0). PPSA04540 uses 0x15, 0x19, 0x1b and 0x23 (#8039), PPSA35169 0x20 (#4246) and PPSA33655 0x15, 0x19 and 0x1b From 3fcc7f75801f78978277b1f239d34583188a0795 Mon Sep 17 00:00:00 2001 From: Bipin Date: Sun, 1 Nov 2026 16:50:15 +0530 Subject: [PATCH 189/612] fix(libkernel_sys): report unsupported adjtime adjustments consistently --- .../prx/libkernel_sys/AdditionalExports.cpp | 9 +------ core/libs/tests/GuestKernelSysAdjtime.cpp | 27 ++++++++++--------- docs/dev/debt/libkernel_sys.md | 2 +- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/core/libs/prx/libkernel_sys/AdditionalExports.cpp b/core/libs/prx/libkernel_sys/AdditionalExports.cpp index 59156b17ed..e64ee9bc6d 100644 --- a/core/libs/prx/libkernel_sys/AdditionalExports.cpp +++ b/core/libs/prx/libkernel_sys/AdditionalExports.cpp @@ -1,9 +1,6 @@ #include "prx/libkernel_sys/include/AdditionalExports.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/Api.hpp" -#include -#include -#include extern "C" { @@ -214,11 +211,7 @@ int APS5_VABI adjtime_nid_postfix(const KernelTimeval* delta, KernelTimeval* old *__error_nid_postfix() = GuestEfault; return -1; } - if (delta != nullptr) { - char requested[48]; - std::snprintf(requested, sizeof(requested), "%lld.%06lld", static_cast(adjustment.tv_sec), static_cast(adjustment.tv_usec)); - throw std::runtime_error(std::string("adjtime: adjusting the clock by ") + requested + " is not supported, the host clock is not changed"); - } + if (delta != nullptr) NotImplemented_nid_no_patch(__func__); if (oldDelta != nullptr) { const KernelTimeval pending{}; if (!GuestCopyOut_nid_no_patch(oldDelta, &pending, sizeof(pending))) { diff --git a/core/libs/tests/GuestKernelSysAdjtime.cpp b/core/libs/tests/GuestKernelSysAdjtime.cpp index 36c968b8f1..f935f74e13 100644 --- a/core/libs/tests/GuestKernelSysAdjtime.cpp +++ b/core/libs/tests/GuestKernelSysAdjtime.cpp @@ -4,14 +4,12 @@ #include #include -#include +#include #include namespace { constexpr int GuestEfault = 14; -constexpr char SecondsAdjustment[] = "adjtime: adjusting the clock by 2.000000 is not supported, the host clock is not changed"; -constexpr char SubSecondAdjustment[] = "adjtime: adjusting the clock by 0.-00001 is not supported, the host clock is not changed"; void Require(bool condition, const char* message) { if (!condition) { @@ -20,14 +18,17 @@ void Require(bool condition, const char* message) { } } -void RequireAdjustment(const KernelTimeval& delta, const char* expected) { +void RequireAdjustment(const KernelTimeval& delta) { + KernelTimeval remaining{-3, -2}; + const auto before = NotImplementedCalls_nid_no_patch(); try { - adjtime_nid_postfix(&delta, nullptr); - } catch (const std::runtime_error& value) { - Require(std::strcmp(value.what(), expected) == 0, value.what()); + adjtime_nid_postfix(&delta, &remaining); + } catch (const std::runtime_error&) { + Require(NotImplementedCalls_nid_no_patch() == before + 1, "unsupported adjustment bypassed the unimplemented-call counter"); + Require(remaining.tv_sec == -3 && remaining.tv_usec == -2, "unsupported adjustment modified oldDelta"); return; } - Require(false, expected); + Require(false, "unsupported adjustment returned success"); } } // namespace @@ -37,11 +38,11 @@ int main() { KernelTimeval remaining{-3, -2}; Require(adjtime_nid_postfix(nullptr, &remaining) == 0, "adjtime with a null delta did not succeed"); Require(remaining.tv_sec == 0 && remaining.tv_usec == 0, "adjtime did not report a zero pending adjustment"); - Require(adjtime_nid_postfix(reinterpret_cast(1), nullptr) == -1 && *__error_nid_postfix() == GuestEfault, "adjtime with an unreadable delta did not fail with EFAULT"); - const KernelTimeval seconds{2, 0}; - RequireAdjustment(seconds, SecondsAdjustment); - const KernelTimeval subSecond{0, -1}; - RequireAdjustment(subSecond, SubSecondAdjustment); + Require(adjtime_nid_postfix(nullptr, reinterpret_cast(1)) == -1 && *__error_nid_postfix() == GuestEfault, "adjtime with an unwritable oldDelta did not fail with EFAULT"); + remaining = {-3, -2}; + Require(adjtime_nid_postfix(reinterpret_cast(1), &remaining) == -1 && *__error_nid_postfix() == GuestEfault, "adjtime with an unreadable delta did not fail with EFAULT"); + Require(remaining.tv_sec == -3 && remaining.tv_usec == -2, "adjtime wrote oldDelta before reading delta"); + for (const KernelTimeval delta : {KernelTimeval{2, 0}, KernelTimeval{0, -1}, KernelTimeval{0, 0}}) RequireAdjustment(delta); std::puts("guest_kernel_sys_adjtime passed"); return 0; } diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 445291d2be..b31b079052 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -383,7 +383,7 @@ - [sceKernelGetProcessType](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - not measured on a console; as in shadPS4, the title's own pid, -1 and 0 are type 0 (big application) and any other pid is `ESRCH` - [cpuset, cpuset_getid, cpuset_setid](../../../core/libs/prx/libkernel_sys/Cpuset.cpp) (libkernel_sys) - console root IDs and inheritance are unverified; non-process scopes (TID, CPUSET, JAIL) throw as unmodelled as per-thread cpusets and hierarchy visibility are not tracked. CPU set assignment and queries follow FreeBSD 11.4 [kern_cpuset.c](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_cpuset.c): cpuset allocates sequential IDs from 2; cpuset_setid supports CPU_WHICH_PID for known IDs (or root 0, default 1); dynamic sets are single-process referenced and unlinked when vacated, returning ESRCH on lookup; cpuset_getid returns root 0 for CPU_LEVEL_ROOT and the assigned ID for CPU_LEVEL_CPUSET. Output writes use GuestCopyOut_nid_no_patch (EFAULT on bad pointers), with level/which/id errors returned before copyout; levels outside 1 to 3 return EINVAL where FreeBSD returns 0 with the base set ID; other which return EINVAL, unknown pids return ESRCH - [sceKernelSettimeofday](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - forwards to `settimeofday` and returns its result, or `0x80020000` plus `errno` for a failure, as shadPS4's [`sceKernelSettimeofday`](https://github.com/shadps4-emu/shadPS4/blob/7ea677e56d75bf3d90ace2b0a5144a665d1f38b6/src/core/libraries/kernel/time.cpp#L565-L571) maps its call through `ErrnoToSceKernelError`; that branch passes the call's return value, and shadPS4's own `settimeofday` never fails, so the errno form the other sce wrappers here use is taken. Nothing is measured on the console, and a valid time inherits `settimeofday`'s refusal to set the host clock -- [adjtime](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) checks like FreeBSD 11.4's [`sys_adjtime`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_ntptime.c#L955-L978) calling `kern_adjtime`: `EFAULT` for an unreadable `delta`, read before anything is stored. A valid adjustment throws, naming it, since the host clock is never adjusted and no pending-adjustment state is kept. A null `delta` reports the pending adjustment, which is zero, as FreeBSD's `time_adjtime` is before any `ntp_adjtime` access. FreeBSD applies an adjustment only with `PRIV_ADJTIME`, which a jailed process fails with `EPERM`; whether a title holds it on the console is unverified +- [adjtime](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) does not model clock slewing or adjustment privileges; every readable non-null `delta`, including zero, calls `NotImplemented_nid_no_patch`. A null `delta` reports zero pending adjustment because adjustments are never accepted. Checked guest reads and writes return `EFAULT`, with input read before output, following FreeBSD 11.4 [`sys_adjtime` and `kern_adjtime`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_ntptime.c#L961-L1010). Console behavior and whether a title has `PRIV_ADJTIME` are unverified. ### Unknown function info From 664b12ae454ad4738a2fc15c3432ee04473253e1 Mon Sep 17 00:00:00 2001 From: Bipin Date: Sun, 1 Nov 2026 16:55:08 +0530 Subject: [PATCH 190/612] fix(libkernel_sys): share exec activation and address review feedback --- core/libs/prx/libkernel/CMakeLists.txt | 12 +++- .../prx/libkernel_sys/AdditionalExports.cpp | 33 +++++++--- core/libs/tests/GuestExecHost.cpp | 11 ++++ core/libs/tests/GuestKernelSysExecve.cpp | 36 ----------- core/libs/tests/GuestKernelSysExecvpe.cpp | 64 ++++++------------- docs/dev/debt/libkernel_sys.md | 6 +- 6 files changed, 66 insertions(+), 96 deletions(-) diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 79f5520b73..70fcabbf5c 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -143,7 +143,7 @@ if(BUILD_TESTING) endforeach() add_library(guest_exec_host SHARED ${LIBS_INCLUDE_DIR}/tests/GuestExecHost.cpp) target_include_directories(guest_exec_host PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_exec_host PRIVATE libkernel libc libSceAgcDriver) + target_link_libraries(guest_exec_host PRIVATE libkernel libkernel_sys libc libSceAgcDriver) set_target_properties(guest_exec_host PROPERTIES PREFIX "" SUFFIX ".prx" BUILD_RPATH "$ORIGIN") configure_windows_unwind(guest_exec_host) find_package(Python3 COMPONENTS Interpreter QUIET) @@ -151,9 +151,17 @@ if(BUILD_TESTING) add_test(NAME exec_arguments COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/core/relinker/relinker/tests/test_exec_arguments.py $ $ - $ $ $ ${CMAKE_CXX_COMPILER}) + $ $ $ $ ${CMAKE_CXX_COMPILER}) set_tests_properties(exec_arguments PROPERTIES TIMEOUT 30) add_asan_test_options(exec_arguments verify_asan_link_order=0) + foreach(mode IN ITEMS ve vpe) + add_test(NAME exec_sys_${mode}_arguments COMMAND ${Python3_EXECUTABLE} + ${CMAKE_SOURCE_DIR}/core/relinker/relinker/tests/test_exec_arguments.py + $ $ + $ $ $ $ ${CMAKE_CXX_COMPILER}) + set_tests_properties(exec_sys_${mode}_arguments PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_KERNEL_SYS_EXEC_MODE=${mode}") + add_asan_test_options(exec_sys_${mode}_arguments verify_asan_link_order=0) + endforeach() endif() if(NOT WIN32) add_library(guest_memory_lifetime_module MODULE ${LIBS_INCLUDE_DIR}/tests/GuestMemoryLifetimeModule.cpp) diff --git a/core/libs/prx/libkernel_sys/AdditionalExports.cpp b/core/libs/prx/libkernel_sys/AdditionalExports.cpp index 8613f35af2..fe5b5ceec8 100644 --- a/core/libs/prx/libkernel_sys/AdditionalExports.cpp +++ b/core/libs/prx/libkernel_sys/AdditionalExports.cpp @@ -2,13 +2,15 @@ #include "prx/libc/include/General.hpp" #include "prx/libc/include/Api.hpp" #include "prx/libc/include/FilesystemError.hpp" +#include "prx/libkernel/include/Api.hpp" +#include #include #include #include -#include #include #include +#include #include extern "C" { @@ -201,7 +203,7 @@ int APS5_VABI _dup2_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int GuestExecve_nid_no_patch(const char* path, char* const* argv, char* const* envp) { +static int GuestExecve_nid_no_patch(const char* path, char* const* argv, char* const* envp) { constexpr int GuestEfault = 14; constexpr int GuestEacces = 13; constexpr int GuestEnoexec = 8; @@ -220,7 +222,7 @@ int GuestExecve_nid_no_patch(const char* path, char* const* argv, char* const* e const std::size_t boundary = 4096 - address % 4096; const std::size_t bytes = boundary < maxLength - read ? boundary : maxLength - read; char buffer[4096]; - if (!GuestCopyIn_nid_no_patch(buffer, guest + read, bytes)) return GuestEfault; + if (!GuestCopyIn_nid_no_patch(buffer, reinterpret_cast(address), bytes)) return GuestEfault; const auto end = static_cast(std::memchr(buffer, 0, bytes)); if (end != nullptr) { copy.append(buffer, static_cast(end - buffer)); @@ -238,6 +240,8 @@ int GuestExecve_nid_no_patch(const char* path, char* const* argv, char* const* e if (error != 0) return fail(error); } std::string text; + std::vector arguments; + std::vector environment; std::size_t stringspace = GuestArgMax; for (char* const* slot = argv;; ++slot) { char* argument = nullptr; @@ -247,6 +251,7 @@ int GuestExecve_nid_no_patch(const char* path, char* const* argv, char* const* e const int error = gather(argument, stringspace, text); if (error != 0) return fail(error == GuestEnametoolong ? GuestE2big : error); stringspace -= text.size() + 1; + arguments.push_back(std::move(text)); } if (envp != nullptr) { for (char* const* slot = envp;; ++slot) { @@ -257,9 +262,11 @@ int GuestExecve_nid_no_patch(const char* path, char* const* argv, char* const* e const int error = gather(variable, stringspace, text); if (error != 0) return fail(error == GuestEnametoolong ? GuestE2big : error); stringspace -= text.size() + 1; + environment.push_back(std::move(text)); } } - if (path == nullptr || fname.empty()) return fail(GuestEnoent); + if (path == nullptr) NotImplemented_nid_no_patch(__func__); + if (fname.empty()) return fail(GuestEnoent); std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(fname.c_str(), native)) return fail(error); std::error_code statusError; @@ -284,7 +291,7 @@ int GuestExecve_nid_no_patch(const char* path, char* const* argv, char* const* e image.read(reinterpret_cast(header), sizeof(header)); if (image.gcount() < static_cast(sizeof(header))) return fail(GuestEnoexec); if (header[0] == '#' && header[1] == '!') { - throw std::runtime_error(std::string("execve: running the script ") + fname + " is not supported, script interpreters are not modelled"); + NotImplemented_nid_no_patch(__func__); } constexpr unsigned char ElfMagic[] = {0x7f, 0x45, 0x4c, 0x46}; constexpr unsigned char Ps4SelfMagic[] = {0x4f, 0x15, 0x3d, 0x1d}; @@ -292,7 +299,13 @@ int GuestExecve_nid_no_patch(const char* path, char* const* argv, char* const* e if (std::memcmp(header, ElfMagic, sizeof(ElfMagic)) == 0 || std::memcmp(header, Ps4SelfMagic, sizeof(Ps4SelfMagic)) == 0 || std::memcmp(header, Ps5SelfMagic, sizeof(Ps5SelfMagic)) == 0) { - throw std::runtime_error(std::string("execve: replacing the process image with ") + fname + " is not supported, program activation is not modelled"); + std::vector argumentPointers; + for (auto& argument : arguments) argumentPointers.push_back(argument.data()); + argumentPointers.push_back(nullptr); + std::vector environmentPointers; + for (auto& variable : environment) environmentPointers.push_back(variable.data()); + environmentPointers.push_back(nullptr); + return execve_nid_postfix(fname.c_str(), argumentPointers.data(), environmentPointers.data()); } return fail(GuestEnoexec); } @@ -323,11 +336,12 @@ int APS5_VABI _execvpe_nid_postfix(const char* name, char* const* argv, char* co if (!CopyGuestEnvironment_nid_no_patch("PATH", searchPath)) searchPath = std::string(DefaultSearchPath); std::string nameText; if (name != nullptr) { - for (std::size_t read = 0;;) { + for (std::size_t read = 0; read < GuestCandidateMax;) { const auto address = reinterpret_cast(name) + read; - const std::size_t bytes = 4096 - address % 4096; + const std::size_t boundary = 4096 - address % 4096; + const std::size_t bytes = std::min(boundary, GuestCandidateMax - read); char buffer[4096]; - if (!GuestCopyIn_nid_no_patch(buffer, name + read, bytes)) return fail(GuestEfault); + if (!GuestCopyIn_nid_no_patch(buffer, reinterpret_cast(address), bytes)) return fail(GuestEfault); const auto end = static_cast(std::memchr(buffer, 0, bytes)); if (end != nullptr) { nameText.append(buffer, static_cast(end - buffer)); @@ -339,6 +353,7 @@ int APS5_VABI _execvpe_nid_postfix(const char* name, char* const* argv, char* co } else { return fail(GuestEfault); } + if (nameText.size() == GuestCandidateMax) return fail(GuestEnoent); bool accessFailure = false; const auto ladder = [&](const std::string& candidate) { GuestExecve_nid_no_patch(candidate.c_str(), argv, envp); diff --git a/core/libs/tests/GuestExecHost.cpp b/core/libs/tests/GuestExecHost.cpp index 82faaf1b6b..ecb942c04a 100644 --- a/core/libs/tests/GuestExecHost.cpp +++ b/core/libs/tests/GuestExecHost.cpp @@ -2,6 +2,7 @@ #include "prx/libc/include/Shutdown.hpp" #include "prx/libc/include/Api.hpp" #include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel_sys/include/AdditionalExports.hpp" #include #include #include @@ -198,6 +199,16 @@ extern "C" void APS5_VABI ExecFirst_nid_no_patch(const std::uintptr_t* block) { char* const target[] = {const_cast("/app0/picker/second.bin"), const_cast("argument with spaces"), const_cast("quote\"and\\slash\\ Кириллица 東京"), nullptr}; try { + const char* mode = std::getenv("ANYPS5_KERNEL_SYS_EXEC_MODE"); + if (mode != nullptr && std::strcmp(mode, "ve") == 0) { + _execve_nid_postfix("/app0/picker/second.bin", target, environment.data()); + Require(false, "_execve returned instead of replacing the image"); + } + if (mode != nullptr && std::strcmp(mode, "vpe") == 0) { + Require(setenv_nid_postfix("PATH", "/missing:/app0/picker", 1) == 0, "setting guest PATH failed"); + _execvpe_nid_postfix("second.bin", target, environment.data()); + Require(false, "_execvpe returned instead of replacing the image"); + } execve_nid_postfix("/app0/picker/second.bin", target, environment.data()); } catch (...) { Record("throw"); diff --git a/core/libs/tests/GuestKernelSysExecve.cpp b/core/libs/tests/GuestKernelSysExecve.cpp index 543a96620f..a1f117785c 100644 --- a/core/libs/tests/GuestKernelSysExecve.cpp +++ b/core/libs/tests/GuestKernelSysExecve.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -32,16 +31,6 @@ void RequireFailure(int result, int error, const char* message) { Require(result == -1 && *__error_nid_postfix() == error, message); } -template void RequireThrows(TCall call, const std::string& expected, const char* message) { - try { - call(); - } catch (const std::runtime_error& value) { - Require(value.what() == expected, message); - return; - } - Require(false, message); -} - std::string RepeatedComponents(std::size_t length) { std::string path = "a"; while (path.size() + 2 <= length) path += "/a"; @@ -83,7 +72,6 @@ int main() { Require(tooLongPath.size() == 1025, "the 1024 byte unterminated pathname test built the wrong length"); RequireFailure(_execve_nid_postfix(tooLongPath.c_str(), emptyArgv, nullptr), GuestEnametoolong, "execve with an unterminated 1024 byte pathname did not fail with ENAMETOOLONG"); RequireFailure(_execve_nid_postfix("", emptyArgv, nullptr), GuestEnoent, "execve with an empty pathname did not fail with ENOENT"); - RequireFailure(_execve_nid_postfix(nullptr, emptyArgv, nullptr), GuestEnoent, "execve with a null pathname did not fail with ENOENT"); RequireFailure(_execve_nid_postfix("kernel_execve_missing.bin", argv, environment), GuestEnoent, "execve of a missing file did not fail with ENOENT"); constexpr char prefixBody[] = "prefix"; WriteFile("kernel_execve_prefix.bin", prefixBody, sizeof(prefixBody) - 1, std::filesystem::perms::owner_all); @@ -100,26 +88,6 @@ int main() { WriteFile("kernel_execve_unexec.bin", "just data", 9, std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::group_read | std::filesystem::perms::others_read); RequireFailure(_execve_nid_postfix("kernel_execve_unexec.bin", argv, nullptr), GuestEacces, "execve without execute bits did not fail with EACCES"); #endif - constexpr char elfBody[] = "\x7f" "ELFreference"; - WriteFile("kernel_execve_elf.bin", elfBody, sizeof(elfBody) - 1, std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec); - RequireThrows([&emptyArgv] { _execve_nid_postfix("kernel_execve_elf.bin", emptyArgv, nullptr); }, - "execve: replacing the process image with kernel_execve_elf.bin is not supported, program activation is not modelled", - "execve of an ELF image did not refuse replacement"); - constexpr unsigned char selfBody[] = {0x4f, 0x15, 0x3d, 0x1d, 'r', 'e', 'f', 'e', 'r', 'e', 'n', 'c', 'e'}; - WriteFile("kernel_execve_self.bin", reinterpret_cast(selfBody), sizeof(selfBody), std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec); - RequireThrows([&emptyArgv] { _execve_nid_postfix("kernel_execve_self.bin", emptyArgv, nullptr); }, - "execve: replacing the process image with kernel_execve_self.bin is not supported, program activation is not modelled", - "execve of a SELF image did not refuse replacement"); - constexpr unsigned char payloadBody[] = {0x54, 0x14, 0xf5, 0xee, 'r', 'e', 'f'}; - WriteFile("kernel_execve_payload.bin", reinterpret_cast(payloadBody), sizeof(payloadBody), std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec); - RequireThrows([&emptyArgv] { _execve_nid_postfix("kernel_execve_payload.bin", emptyArgv, nullptr); }, - "execve: replacing the process image with kernel_execve_payload.bin is not supported, program activation is not modelled", - "execve of a PS5 SELF payload did not refuse replacement"); - constexpr char scriptBody[] = "#!/bin/sh\necho double\n"; - WriteFile("kernel_execve_script.bin", scriptBody, sizeof(scriptBody) - 1, std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec); - RequireThrows([&emptyArgv] { _execve_nid_postfix("kernel_execve_script.bin", emptyArgv, nullptr); }, - "execve: running the script kernel_execve_script.bin is not supported, script interpreters are not modelled", - "execve of a script did not refuse the interpreter"); const std::string oversized(2048, 'a'); std::vector storage; storage.reserve(128); @@ -136,10 +104,6 @@ int main() { std::filesystem::remove("kernel_execve_short.bin"); std::filesystem::remove("kernel_execve_plain.bin"); std::filesystem::remove("kernel_execve_unexec.bin"); - std::filesystem::remove("kernel_execve_elf.bin"); - std::filesystem::remove("kernel_execve_self.bin"); - std::filesystem::remove("kernel_execve_payload.bin"); - std::filesystem::remove("kernel_execve_script.bin"); std::filesystem::remove("kernel_execve_dir"); std::puts("guest_kernel_sys_execve passed"); return 0; diff --git a/core/libs/tests/GuestKernelSysExecvpe.cpp b/core/libs/tests/GuestKernelSysExecvpe.cpp index c741c07366..c1aac07446 100644 --- a/core/libs/tests/GuestKernelSysExecvpe.cpp +++ b/core/libs/tests/GuestKernelSysExecvpe.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -30,16 +29,6 @@ void RequireFailure(int result, int error, const char* message) { Require(result == -1 && *__error_nid_postfix() == error, message); } -template void RequireThrows(TCall call, const std::string& expected, const char* message) { - try { - call(); - } catch (const std::runtime_error& value) { - Require(value.what() == expected, message); - return; - } - Require(false, message); -} - void WriteFile(const char* name, const char* content, std::streamsize bytes, std::filesystem::perms mode) { std::ofstream file(name, std::ios::binary); file.write(content, bytes); @@ -59,11 +48,13 @@ int main() { char* const argv[] = {first, second, nullptr}; constexpr auto fullExecute = std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec; constexpr auto plainRead = std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::group_read | std::filesystem::perms::others_read; - constexpr char elfBody[] = "\x7f" "ELFreference"; - constexpr char scriptBody[] = "#!/bin/sh\necho pairs\n"; + constexpr char plainBody[] = "plain data"; RequireFailure(_execvpe_nid_postfix("", argv, nullptr), GuestEnoent, "execvpe with an empty name did not fail with ENOENT"); RequireFailure(_execvpe_nid_postfix(nullptr, argv, nullptr), GuestEfault, "execvpe with a null name did not fail with EFAULT"); RequireFailure(_execvpe_nid_postfix("kernel_execvpe_absent.bin", nullptr, nullptr), GuestEnoent, "execvpe with a null argv did not fail with ENOENT"); + const std::string longName(1024 * 1024, 'x'); + RequireFailure(_execvpe_nid_postfix(longName.c_str(), argv, nullptr), GuestEnoent, "an overlong name did not end the bounded search"); + RequireFailure(_execvpe_nid_postfix(("/" + longName).c_str(), argv, nullptr), GuestEnoent, "an overlong slash-qualified name did not end the bounded search"); const std::string oversized(2048, 'a'); std::vector storage; std::vector oversizedArgv; @@ -73,20 +64,14 @@ int main() { } oversizedArgv.push_back(nullptr); RequireFailure(_execvpe_nid_postfix("kernel_execvpe_absent.bin", oversizedArgv.data(), nullptr), GuestE2big, "execvpe with arguments beyond ARG_MAX did not fail with E2BIG"); - WriteFile("kernel_execvpe_elf.bin", elfBody, sizeof(elfBody) - 1, static_cast(fullExecute)); - RequireThrows([&argv] { _execvpe_nid_postfix("./kernel_execvpe_elf.bin", argv, nullptr); }, - "execve: replacing the process image with ./kernel_execvpe_elf.bin is not supported, program activation is not modelled", - "execvpe with a slash in the name did not refuse activation"); - std::filesystem::remove("kernel_execvpe_elf.bin"); + WriteFile("kernel_execvpe_data.bin", plainBody, sizeof(plainBody) - 1, static_cast(fullExecute)); + RequireFailure(_execvpe_nid_postfix("./kernel_execvpe_data.bin", argv, nullptr), GuestEnoent, "execvpe did not return ENOENT for a missing shell"); + std::filesystem::remove("kernel_execvpe_data.bin"); std::filesystem::create_directory("bin"); - WriteFile("bin/sh", elfBody, sizeof(elfBody) - 1, static_cast(fullExecute)); - RequireThrows([&argv] { _execvpe_nid_postfix("./bin/sh", argv, nullptr); }, - "execve: replacing the process image with ./bin/sh is not supported, program activation is not modelled", - "execvpe of the shell by path did not refuse activation"); + WriteFile("bin/sh", "", 0, static_cast(fullExecute)); + RequireFailure(_execvpe_nid_postfix("./bin/sh", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); WriteFile("bin/plain.bin", "plain", 5, static_cast(fullExecute)); - RequireThrows([&argv] { _execvpe_nid_postfix("./bin/plain.bin", argv, nullptr); }, - "execve: replacing the process image with /bin/sh is not supported, program activation is not modelled", - "execvpe of a slash-named plain file did not redirect to and refuse the shell"); + RequireFailure(_execvpe_nid_postfix("./bin/plain.bin", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); std::filesystem::create_directory("sbin"); RequireFailure(_execvpe_nid_postfix("kernel_execvpe_absent.bin", argv, nullptr), GuestEnoent, "execvpe with the default search path and a missing file did not fail with ENOENT"); #ifndef _WIN32 @@ -96,34 +81,22 @@ int main() { #endif std::filesystem::create_directory("usr"); std::filesystem::create_directory("usr/bin"); - WriteFile("usr/bin/kernel_execvpe_elf.bin", elfBody, sizeof(elfBody) - 1, static_cast(fullExecute)); - RequireThrows([&argv] { _execvpe_nid_postfix("kernel_execvpe_elf.bin", argv, nullptr); }, - "execve: replacing the process image with /usr/bin/kernel_execvpe_elf.bin is not supported, program activation is not modelled", - "execvpe did not search the default path and refuse the found image"); + WriteFile("usr/bin/kernel_execvpe_data.bin", plainBody, sizeof(plainBody) - 1, static_cast(fullExecute)); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_data.bin", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); std::filesystem::create_directory("sandbox"); - WriteFile("sandbox/kernel_execvpe_tool.bin", elfBody, sizeof(elfBody) - 1, static_cast(fullExecute)); + WriteFile("sandbox/kernel_execvpe_tool.bin", plainBody, sizeof(plainBody) - 1, static_cast(fullExecute)); setenv_nid_postfix("PATH", "/sandbox", 1); - RequireThrows([&argv] { _execvpe_nid_postfix("kernel_execvpe_tool.bin", argv, nullptr); }, - "execve: replacing the process image with /sandbox/kernel_execvpe_tool.bin is not supported, program activation is not modelled", - "execvpe did not follow the guest PATH and refuse the found image"); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_tool.bin", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); unsetenv_nid_postfix("PATH"); RequireFailure(_execvpe_nid_postfix("kernel_execvpe_tool.bin", argv, nullptr), GuestEnoent, "execvpe with a cleared PATH did not fail with ENOENT"); - WriteFile("kernel_execvpe_tool.bin", elfBody, sizeof(elfBody) - 1, static_cast(fullExecute)); + WriteFile("kernel_execvpe_tool.bin", plainBody, sizeof(plainBody) - 1, static_cast(fullExecute)); setenv_nid_postfix("PATH", "", 1); - RequireThrows([&argv] { _execvpe_nid_postfix("kernel_execvpe_tool.bin", argv, nullptr); }, - "execve: replacing the process image with ./kernel_execvpe_tool.bin is not supported, program activation is not modelled", - "execvpe with an empty PATH did not search the current directory"); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_tool.bin", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); unsetenv_nid_postfix("PATH"); std::filesystem::remove("kernel_execvpe_tool.bin"); - WriteFile("sandbox/kernel_execvpe_job.bin", scriptBody, sizeof(scriptBody) - 1, static_cast(fullExecute)); setenv_nid_postfix("PATH", "/sandbox", 1); - RequireThrows([&argv] { _execvpe_nid_postfix("kernel_execvpe_job.bin", argv, nullptr); }, - "execve: running the script /sandbox/kernel_execvpe_job.bin is not supported, script interpreters are not modelled", - "execvpe of a script did not refuse the interpreter"); WriteFile("sandbox/kernel_execvpe_plain.bin", "plain", 5, static_cast(fullExecute)); - RequireThrows([&argv] { _execvpe_nid_postfix("kernel_execvpe_plain.bin", argv, nullptr); }, - "execve: replacing the process image with /bin/sh is not supported, program activation is not modelled", - "execvpe of an unrecognised file did not redirect to and refuse the shell"); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_plain.bin", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); std::filesystem::remove("bin/sh"); RequireFailure(_execvpe_nid_postfix("./bin/plain.bin", argv, nullptr), GuestEnoent, "execvpe of a slash-named plain file without the shell did not fail with ENOENT"); std::filesystem::remove("bin/plain.bin"); @@ -135,9 +108,8 @@ int main() { RequireFailure(_execvpe_nid_postfix("kernel_execvpe_plain.bin", argv, nullptr), GuestEnoent, "execvpe over a missing first element did not fail with ENOENT"); unsetenv_nid_postfix("PATH"); std::filesystem::remove("sandbox/kernel_execvpe_tool.bin"); - std::filesystem::remove("sandbox/kernel_execvpe_job.bin"); std::filesystem::remove("sandbox/kernel_execvpe_plain.bin"); - std::filesystem::remove("usr/bin/kernel_execvpe_elf.bin"); + std::filesystem::remove("usr/bin/kernel_execvpe_data.bin"); std::filesystem::remove("scripts/kernel_execvpe_plain.bin"); std::filesystem::remove("bin"); std::filesystem::remove("sbin"); diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 8c0cb02cd4..be25aa4d4b 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -349,8 +349,8 @@ - [openat, _openat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) pass `AT_FDCWD` (-100) and absolute paths to `open`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. They return `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. As with `open`, `O_NONBLOCK` is ignored. PPSA99003 opens directory entries through `openat`. Console behaviour is unverified - [fchmodat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `chmod`; any other relative path is joined to the descriptor's host directory the same way as for `openat`, so guest path aliases and case-insensitive name matching do not apply to it and a `..` component is not kept inside the guest root. A flag other than `0` or `AT_SYMLINK_NOFOLLOW` (0x200) returns `EINVAL`; the mode is masked to `07777`. With `AT_SYMLINK_NOFOLLOW` a symbolic link returns success silently on Windows and Linux, since the host cannot change a link's own mode, while a regular file is changed either way. The mode maps to the host file mode, which on Windows is only the CRT's read-only bit. PPSA99621 calls it through `std::filesystem::permissions`. Console behaviour is unverified - [lchmod](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws for a symbolic link on Windows and Linux, whose hosts cannot change a link's own mode; on macOS the link's mode is changed. Anything else goes to `fchmodat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW)`, as FreeBSD 11.4's [`sys_lchmod`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L2490-L2496) does, so a file or directory is changed as by `chmod`, with `fchmodat`'s path lookup and Windows mode mapping. On Windows any reparse point counts as a link. Console behaviour is unverified -- [_execve](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) follows FreeBSD 11.4's [`exec_copyin_args`/`exec_check_permissions`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_exec.c#L1172-L1680): a null `argv` is `EFAULT` before the pathname, guest-read page-wise within `PATH_MAX` (`ENAMETOOLONG`); every `argv` and non-null `envp` entry shares an `ARG_MAX` 262144-byte stringspace counting NULs (`E2BIG` past it; unreadable, `EFAULT`); an empty or null pathname `ENOENT`. The resolver gives `ENOTDIR` from a file prefix; no execute bits or a non-regular file is `EACCES`, an empty file `ENOEXEC`, refused execute permission or a failed reopen `EACCES`. An image with the `ELF` or a PS4/PS5 `SELF` magic throws naming the path; `#!` throws; otherwise `ENOEXEC`. `MNT_NOEXEC`, `ETXTBSY`, `#!` redirection, descriptor-0 exec, Windows mode bits, plain-`ELF` acceptance and resolver search permission are unmodelled. Console behaviour is unverified -- [_execvpe](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) follows FreeBSD 11.4's `_execvpe` and `execvPe`: the search path comes from the guest environment, `_PATH_DEFPATH` when unset; a name containing `/` runs the `_execve` ladder once through the same switch; an empty name is `ENOENT`; each `:`-separated element, empty for the current directory, forms a sub-1024-byte candidate for the `_execve` ladder, where `E2BIG`, `ENOMEM` and `ETXTBSY` return, `ENOENT`, `ENOTDIR`, `ELOOP` and `ENAMETOOLONG` try the next element, `ENOEXEC` retries as `/bin/sh sh ` with the remaining arguments, and any other error returns once the candidate resolves and exists, recording `EACCES` for the final answer or `ENOENT`. A `#!` or executable candidate throws from the ladder naming the path; the too-long-candidate stderr note is unmodelled; FreeBSD's libc would crash on an unreadable name where the copy costs `EFAULT`. Console behaviour is unverified +- [_execve](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) shares libkernel `execve` process replacement after checked argument copying and file validation. Native mount `MNT_NOEXEC`, write-open `ETXTBSY`, `#!` interpreter parsing, descriptor-based execution, Windows mode bits and directory search permissions are unmodelled; `#!` and null pathnames call `NotImplemented_nid_no_patch`. Converted-target naming, missing conversions and host support inherit libkernel exec limitations. Plain ELF acceptance and console error ordering are unverified. The validation order is derived from FreeBSD 11.4 [`exec_copyin_args` and `exec_check_permissions`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_exec.c#L1172-L1680), not console measurements. +- [_execvpe](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) inherits `_execve` activation and permission gaps. FreeBSD 11.4 [`execvPe`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/exec.c#L145-L281) supplies guest `PATH` search, errno precedence and the `ENOEXEC` retry through `/bin/sh`; the shell requires a supported converted target. The too-long-candidate stderr diagnostic is omitted. Names are read only within 1024 bytes: oversized names return the search's final `ENOENT`, and unreadable names return `EFAULT`, rather than libc's unchecked access. Console behavior is unverified. - [pathconf, fpathconf, _fpathconf](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) answer every file and directory as the console's UFS mount `/system_data` does in a payload process on a PS5 (firmware 13.60). Other mounts differ: `/data` and `/user` give `_PC_LINK_MAX` 1, `_PC_SYMLINK_MAX` 0, `_PC_NO_TRUNC` `EINVAL` and 255 for `_PC_MAX_CANON`, `_PC_MAX_INPUT` and `_PC_VDISABLE`; `/`, `/system`, `/hostapp` and `/update` answer only four names; tmpfs gives `_PC_FILESIZEBITS` 0 and `_PC_SYNC_IO` 1. What a title sees under `/app0`, `/savedata0`, `/temp0` and `/download0` is not known. `fpathconf` and `_fpathconf` answer `_PC_ASYNC_IO` 200112 and, for a pipe, only `_PC_PIPE_BUF` 512, as FreeBSD 11's `kern_fpathconf` does; a guest socket fails `fstat` with `EBADF` - [mkdirat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `mkdir`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. On Windows, `mode` is ignored. Console behaviour is unverified - [readlink](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) on Windows reads a symbolic link's target from its reparse data and returns it with `/` separators, only when the target is relative, as `symlink` creates them. A symbolic link with an absolute target returns its print name, or its substitute name when the print name is empty, without a `\??\` or `\\?\` prefix. A directory junction returns the same kind of name when it is a drive path (`X:\...`), using the print name when that is a drive path and the substitute name otherwise. A mounted volume (`\??\Volume{GUID}\`), any other name surrogate and every reparse point without the name surrogate bit (WOF compression, deduplication, cloud files) return `EINVAL`, as FreeBSD does for a file that is not a link and as CPython's `os.readlink`, which reads only symbolic links and junctions, does @@ -402,7 +402,7 @@ The NID set is from the inspected `libkernel_sys` ELF function-export table (defined `STT_FUNC` entries), not the older 3.20 inventory. The module-parameter words at `0x10` and `0x14` are `0x13590001` and `0x13600007`; the earlier 13.60 label refers to SDK-formatted metadata, not independently authenticated retail firmware or an introduction date. The [reproducible static audit](https://github.com/boykopovar/AnyPS5/pull/12367#issuecomment-6470603298) finds 1551 unique function NIDs with set digest `5f1668cc9c69e2950722a83151f31cf7aa94fb1552ab883e5067f43c94d63b16` and confirms all 328 proposed entries. The digest hashes sorted unique ASCII NIDs, each followed by LF. Named declarations are candidate spellings whose repository-computed NID exactly matches an observed function NID; the hashed table establishes neither plaintext spelling nor C ABI. -The 35 formerly literal aliases are named by the [repository-pinned aerolib table](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). There are now 163 named declarations and 159 unresolved NID aliases; every one of the 323 entries remains a throwing placeholder. `sceKernelHasTrinityMode` left the inventory for its `libkernel` implementation and `_execve` and `_execvpe` left it for their implementations in `AdditionalExports.cpp`. +The 35 formerly literal aliases are named by the [repository-pinned aerolib table](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). There are now 163 named declarations and 160 unresolved NID aliases; every one of the 323 entries remains a throwing placeholder. `sceKernelHasTrinityMode` left the inventory for its `libkernel` implementation and `_execve` and `_execvpe` left it for their implementations in `AdditionalExports.cpp`. The following eight candidate spellings are absent from that aerolib revision. Their name/NID pairs occur in the [public ps5rs identity database at commit `2304be5131221bae0dac24b9767cabffda33f747`](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv) and independently recompute to the function NIDs in the inspected table. This database supports candidate spelling only; it does not establish firmware membership, introduction version or ABI. From d42bf12bafb4d82855ddd59d72f7a146a80ce44f Mon Sep 17 00:00:00 2001 From: Bipin Date: Sun, 1 Nov 2026 16:57:02 +0530 Subject: [PATCH 191/612] docs(dev): register exec test fixture mode --- docs/dev/ENVIRONMENT.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index 8ecfba5ad4..0a90215ee3 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -10,6 +10,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `ANYPS5_JOB_MANAGER_CASE` | string | `core/libs/tests/JobManagerReference.cpp` | | selects the CPU fixture run by `tools/run_job_manager_reference.py` | | `ANYPS5_JOB_MANAGER_HOST` | presence | `core/libs/tests/JobManagerReference.cpp` | | runs the reference adapter against the host fallback instead of the loaded guest module | | `ANYPS5_LOADEXEC_PARENT` | string | `core/libs/tests/GuestLoadExecHost.cpp` | | variable the Windows entry stub reads; its wide name is built in `core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp` | +| `ANYPS5_KERNEL_SYS_EXEC_MODE` | string | `core/libs/tests/GuestExecHost.cpp` | | selects `_execve` (`ve`) or `_execvpe` (`vpe`) in the exec replacement test fixture; unset tests libkernel `execve` | | `ANYPS5_LOADEXEC_TEST_ROOT` | string | `core/libs/tests/GuestLoadExecHost.cpp` | | directory the load-exec host test reads its guest executable from | | `ANYPS5_POSIX_SPAWN_ROLE` | string | `core/libs/tests/GuestPosixSpawn.cpp` | | role of the test's own copy spawned as a `posix_spawn` child, which exits or sleeps on it and otherwise fails | | `ANYPS5_TEST_QUOTED_ARGV0` | presence | `core/libs/tests/GuestLoadExecHost.cpp` | | makes the load-exec test's first argument one with quotes, a backslash and non-ASCII text | From 399bb8f19a88a90515b9b77cc9ecf6cb56b7c3a8 Mon Sep 17 00:00:00 2001 From: Bipin Date: Sun, 1 Nov 2026 16:58:30 +0530 Subject: [PATCH 192/612] test(libkernel_sys): isolate execvpe search fixtures --- core/libs/tests/GuestKernelSysExecvpe.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/libs/tests/GuestKernelSysExecvpe.cpp b/core/libs/tests/GuestKernelSysExecvpe.cpp index c1aac07446..2fd217cfd4 100644 --- a/core/libs/tests/GuestKernelSysExecvpe.cpp +++ b/core/libs/tests/GuestKernelSysExecvpe.cpp @@ -3,6 +3,7 @@ #include "prx/libc/include/Api.hpp" #include +#include #include #include #include @@ -43,6 +44,14 @@ void WriteFile(const char* name, const char* content, std::streamsize bytes, std } // namespace int main() { + const auto originalDirectory = std::filesystem::current_path(); + const auto stamp = std::chrono::steady_clock::now().time_since_epoch().count(); + std::filesystem::path work; + for (unsigned int attempt = 0;; ++attempt) { + work = std::filesystem::temp_directory_path() / ("anyps5-execvpe-" + std::to_string(stamp) + "-" + std::to_string(attempt)); + if (std::filesystem::create_directory(work)) break; + } + std::filesystem::current_path(work); char first[] = "first"; char second[] = "second"; char* const argv[] = {first, second, nullptr}; @@ -117,6 +126,8 @@ int main() { std::filesystem::remove("scripts"); std::filesystem::remove("usr/bin"); std::filesystem::remove("usr"); + std::filesystem::current_path(originalDirectory); + std::filesystem::remove_all(work); std::puts("guest_kernel_sys_execvpe passed"); return 0; } From 07857a0650086d3d972a77e62c3063c6ad9d575e Mon Sep 17 00:00:00 2001 From: Cloudy <52116030+xdCloudy@users.noreply.github.com> Date: Sun, 1 Nov 2026 03:38:35 -0800 Subject: [PATCH 193/612] feat(libSceLibcInternal): implement zero-return showmanyc exports --- .../prx/libSceLibcInternal/CMakeLists.txt | 11 +++++++++ .../libSceLibcInternal/InventoryExports.cpp | 8 +++---- .../include/InventoryExports.hpp | 5 ++-- .../libs/tests/GuestLibcInternalShowmanyc.cpp | 24 +++++++++++++++++++ docs/dev/debt/libSceLibcInternal.md | 6 ++--- 5 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 core/libs/tests/GuestLibcInternalShowmanyc.cpp diff --git a/core/libs/prx/libSceLibcInternal/CMakeLists.txt b/core/libs/prx/libSceLibcInternal/CMakeLists.txt index 2655d73c0b..7fe131552d 100644 --- a/core/libs/prx/libSceLibcInternal/CMakeLists.txt +++ b/core/libs/prx/libSceLibcInternal/CMakeLists.txt @@ -8,6 +8,17 @@ target_sources(libSceLibcInternal PRIVATE InitOnce.cpp) target_sources(libSceLibcInternal PRIVATE Mutex.cpp) target_sources(libSceLibcInternal PRIVATE AlignedNew.cpp) if(BUILD_TESTING) + add_test_executable(guest_libc_internal_showmanyc_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcInternalShowmanyc.cpp) + target_include_directories(guest_libc_internal_showmanyc_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libc_internal_showmanyc_tests PRIVATE libSceLibcInternal) + configure_windows_unwind(guest_libc_internal_showmanyc_tests) + add_test(NAME guest_libc_internal_showmanyc COMMAND guest_libc_internal_showmanyc_tests) + set_tests_properties(guest_libc_internal_showmanyc PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_libc_internal_showmanyc PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_libc_internal_aligned_new_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcInternalAlignedNew.cpp) target_include_directories(guest_libc_internal_aligned_new_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_libc_internal_aligned_new_tests PRIVATE libSceLibcInternal libkernel libc) diff --git a/core/libs/prx/libSceLibcInternal/InventoryExports.cpp b/core/libs/prx/libSceLibcInternal/InventoryExports.cpp index 056e657d70..4355278c81 100644 --- a/core/libs/prx/libSceLibcInternal/InventoryExports.cpp +++ b/core/libs/prx/libSceLibcInternal/InventoryExports.cpp @@ -239,8 +239,8 @@ int APS5_VABI libSceLibcInternalInventory_046_nid_no_patch() { } APS5_EXPORT("sXaxl1QGorg", libSceLibcInternalInventory_046_nid_no_patch); -int APS5_VABI libSceLibcInternalInventory_047_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +std::streamsize APS5_VABI libSceLibcInternalInventory_047_nid_no_patch(std::basic_streambuf*) { + return 0; } APS5_EXPORT("IAEl1Ta7yVU", libSceLibcInternalInventory_047_nid_no_patch); @@ -254,8 +254,8 @@ int APS5_VABI libSceLibcInternalInventory_049_nid_no_patch() { } APS5_EXPORT("041c37QfoUc", libSceLibcInternalInventory_049_nid_no_patch); -int APS5_VABI libSceLibcInternalInventory_050_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +std::streamsize APS5_VABI libSceLibcInternalInventory_050_nid_no_patch(std::basic_streambuf*) { + return 0; } APS5_EXPORT("olsoiZsezkk", libSceLibcInternalInventory_050_nid_no_patch); diff --git a/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp b/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp index f513e0d264..81a99bcac5 100644 --- a/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp +++ b/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp @@ -1,6 +1,7 @@ #pragma once #include "SceTypes.hpp" +#include extern "C" { @@ -51,10 +52,10 @@ int APS5_VABI libSceLibcInternalInventory_043_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_044_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_045_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_046_nid_no_patch(); -int APS5_VABI libSceLibcInternalInventory_047_nid_no_patch(); +std::streamsize APS5_VABI libSceLibcInternalInventory_047_nid_no_patch(std::basic_streambuf*); int APS5_VABI libSceLibcInternalInventory_048_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_049_nid_no_patch(); -int APS5_VABI libSceLibcInternalInventory_050_nid_no_patch(); +std::streamsize APS5_VABI libSceLibcInternalInventory_050_nid_no_patch(std::basic_streambuf*); int APS5_VABI libSceLibcInternalInventory_051_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_052_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_053_nid_no_patch(); diff --git a/core/libs/tests/GuestLibcInternalShowmanyc.cpp b/core/libs/tests/GuestLibcInternalShowmanyc.cpp new file mode 100644 index 0000000000..e4ef867d03 --- /dev/null +++ b/core/libs/tests/GuestLibcInternalShowmanyc.cpp @@ -0,0 +1,24 @@ +#include "prx/libSceLibcInternal/include/InventoryExports.hpp" +#include +#include +#include +#include + +static_assert(std::is_same_v*)>); +static_assert(std::is_same_v*)>); + +int main() { + std::stringbuf narrow("data"); + std::wstringbuf wide(L"data"); + if (libSceLibcInternalInventory_047_nid_no_patch(&narrow) != 0 || narrow.str() != "data") { + std::fputs("narrow showmanyc did not return zero without changing the buffer\n", stderr); + return EXIT_FAILURE; + } + if (libSceLibcInternalInventory_050_nid_no_patch(&wide) != 0 || wide.str() != L"data") { + std::fputs("wide showmanyc did not return zero without changing the buffer\n", stderr); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/docs/dev/debt/libSceLibcInternal.md b/docs/dev/debt/libSceLibcInternal.md index 82d71328ac..af1ab2e59a 100644 --- a/docs/dev/debt/libSceLibcInternal.md +++ b/docs/dev/debt/libSceLibcInternal.md @@ -6,7 +6,9 @@ The named entries below use provider-specific NID aliases to avoid interposing h - The [revision-pinned identity inventory](https://gist.github.com/kyon-eth/6bd58c3d7a0fa933dd4b524754c43265/e4e5dbc43890a24e35dc1c656135000210e28390) records module, library, symbol type and NIDs for [InventoryExports.cpp](../../../core/libs/prx/libSceLibcInternal/InventoryExports.cpp); it is a metadata summary rather than independent verification. One entry belongs to the logical library `libSceLibcInternalExt` within this module. -- [Additional callable exports](../../../core/libs/prx/libSceLibcInternal/InventoryExports.cpp) - 297 entries observed in a 3.20-labelled image and a later 13.60-labelled image; 291 match the public 3.20 catalogue and 6 do not. These are declarations only; neither image label authenticates a retail firmware version. Signatures and behavior are unverified; parameterless `int` declarations are placeholders. Every entry throws through `NotImplemented_nid_no_patch`. All declarations use literal NID aliases; the table records candidate names where known. No runtime functionality is implemented; data exports need separate analysis. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). +- [Additional callable exports](../../../core/libs/prx/libSceLibcInternal/InventoryExports.cpp) - 297 entries observed in a 3.20-labelled image and a later 13.60-labelled image; 291 match the public 3.20 catalogue and 6 do not. Apart from the two `showmanyc` exports below, these remain import-resolution declarations: signatures and behavior are unverified, parameterless `int` declarations are placeholders, and their bodies throw through `NotImplemented_nid_no_patch`. All declarations use literal NID aliases; the table records candidate names where known. No other runtime functionality is implemented; data exports need separate analysis. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). + +`IAEl1Ta7yVU` and `olsoiZsezkk` are the narrow and wide `std::basic_streambuf::showmanyc` exports. Ghidra inspection of the locally held firmware 13.60 `libSceLibcInternal.sprx` shows `xor eax, eax; ret` at both NID-resolved export addresses. Their Itanium names match the standard-library declarations, whose return type is `std::streamsize`; the base `showmanyc` contract returns zero ([C++ draft](https://eel.is/c++draft/streambuf.virt.get)). The bodies do not access the stream buffer. Firmware identity and implementation were inspected locally; no firmware material is included here. | Name | NID | | --- | --- | @@ -57,10 +59,8 @@ The named entries below use provider-specific NID aliases to avoid interposing h | `_ZNSt14overflow_errorD2Ev` | `OypvNf3Uq3c` | | `_ZNSt15basic_streambufIcSt11char_traitsIcEE6xsgetnEPci` | `VpwymQiS4ck` | | `_ZNSt15basic_streambufIcSt11char_traitsIcEE6xsputnEPKci` | `sXaxl1QGorg` | -| `_ZNSt15basic_streambufIcSt11char_traitsIcEE9showmanycEv` | `IAEl1Ta7yVU` | | `_ZNSt15basic_streambufIwSt11char_traitsIwEE6xsgetnEPwi` | `lZVehk7yFok` | | `_ZNSt15basic_streambufIwSt11char_traitsIwEE6xsputnEPKwi` | `041c37QfoUc` | -| `_ZNSt15basic_streambufIwSt11char_traitsIwEE9showmanycEv` | `olsoiZsezkk` | | `_ZNSt15underflow_errorD2Ev` | `xiqd+QkuYXc` | | `_ZNSt16nested_exceptionD1Ev` | `+qKS53qzWdA` | | `_ZNSt16nested_exceptionD2Ev` | `8R00hgzXQDY` | From e6f53ddd6a0cae0e5849a9887779b08fa029375f Mon Sep 17 00:00:00 2001 From: DeveshB-1 Date: Sun, 1 Nov 2026 17:32:02 +0530 Subject: [PATCH 194/612] docs(libScePadTracker): qualify unwritable output error as host-safety fallback --- docs/dev/debt/libScePadTracker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libScePadTracker.md b/docs/dev/debt/libScePadTracker.md index f029d96311..831bedd903 100644 --- a/docs/dev/debt/libScePadTracker.md +++ b/docs/dev/debt/libScePadTracker.md @@ -3,4 +3,4 @@ ### Unknown function info - [scePadTrackerCalibrate, scePadTrackerInit, scePadTrackerReadState, scePadTrackerTerm and scePadTrackerUpdate](../../../core/libs/prx/libScePadTracker/Export.cpp) (libScePadTracker) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libScePadTracker.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified -- [scePadTrackerGetWorkingMemorySize](../../../core/libs/prx/libScePadTracker/Export.cpp) (libScePadTracker) - what the two sizes are for is unknown. It follows firmware 13.42's libScePadTracker.sprx, read from its disassembly and run natively on x86-64 (not a console): a null first or second pointer returns `0x80c40003` and writes nothing; otherwise it stores the 32-bit value 0x800000 through each pointer and returns 0. Both values are module data that no other code in the module references. Unwritable pointer outputs also return `0x80c40003`. +- [scePadTrackerGetWorkingMemorySize](../../../core/libs/prx/libScePadTracker/Export.cpp) (libScePadTracker) - what the two sizes are for is unknown. It follows firmware 13.42's libScePadTracker.sprx, read from its disassembly and run natively on x86-64 (not a console): a null first or second pointer returns `0x80c40003` and writes nothing; otherwise it stores the 32-bit value 0x800000 through each pointer and returns 0. Both values are module data that no other code in the module references. Returning `0x80c40003` for an unwritable output is a host-safety fallback; console behaviour for unwritable memory has not been measured. From 9a49e515926cf35bddd5bd73f1bd74c5e5ce987e Mon Sep 17 00:00:00 2001 From: DeveshB-1 Date: Sun, 1 Nov 2026 17:33:03 +0530 Subject: [PATCH 195/612] docs(libSceMoveTracker): qualify unwritable output error as host-safety fallback --- docs/dev/debt/libSceMoveTracker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceMoveTracker.md b/docs/dev/debt/libSceMoveTracker.md index 134a325d24..c73d479979 100644 --- a/docs/dev/debt/libSceMoveTracker.md +++ b/docs/dev/debt/libSceMoveTracker.md @@ -7,4 +7,4 @@ ### Unknown function info - [libSceMoveTracker](../../../core/libs/prx/libSceMoveTracker/Export.cpp) remains unimplemented. Calibration direction, camera/controller input, capture initialization, state query, memory-size query and playback output signatures are recovered. Camera and controller records occupy 24 bytes, state records 160 bytes, and playback metadata 16 bytes; their complete field meanings still require reconstruction. Initialization consumes a work-memory pointer, a required nonzero 64-bit argument and two 32-bit GPU queue selectors below eight. The second argument is only checked for zero; its declared type remains unresolved. Capture/playback helper signatures need further checking against caller register use. -- [sceMoveTrackerGetWorkingMemorySize](../../../core/libs/prx/libSceMoveTracker/Export.cpp) (libSceMoveTracker) - what the two sizes cover is unknown beyond the parameter names. It follows firmware 13.42's libSceMoveTracker.sprx, read from its disassembly and run natively on x86-64 (not a console): a null pointer returns `0x80ef0003` and writes nothing; otherwise it stores the 32-bit values 0xa00000 and 0x800000 through the first and second pointer and returns 0. The first is module data 0x200000 plus the 0x800000 that an internal function returns; every rip-relative reference to that data in the module reads it. Unwritable pointer outputs also return `0x80ef0003`. +- [sceMoveTrackerGetWorkingMemorySize](../../../core/libs/prx/libSceMoveTracker/Export.cpp) (libSceMoveTracker) - what the two sizes cover is unknown beyond the parameter names. It follows firmware 13.42's libSceMoveTracker.sprx, read from its disassembly and run natively on x86-64 (not a console): a null pointer returns `0x80ef0003` and writes nothing; otherwise it stores the 32-bit values 0xa00000 and 0x800000 through the first and second pointer and returns 0. The first is module data 0x200000 plus the 0x800000 that an internal function returns; every rip-relative reference to that data in the module reads it. Returning `0x80ef0003` for an unwritable output is a host-safety fallback; console behaviour for unwritable memory has not been measured. From 4e5c7a5d759e6435ce633b52bc06a0941d55b5c7 Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Sun, 1 Nov 2026 13:04:36 +0100 Subject: [PATCH 196/612] fix(ngs2): return INVALID_PARAM_ID for sampler UserFx index errors --- .../libs/prx/libSceNgs2.native/src/Custom.cpp | 2 +- .../libSceNgs2.native/tests/Ngs2Custom.cpp | 34 +++++++------------ docs/dev/debt/libSceNgs2.native.md | 2 +- 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/core/libs/prx/libSceNgs2.native/src/Custom.cpp b/core/libs/prx/libSceNgs2.native/src/Custom.cpp index 545e87a099..e91f680d9a 100644 --- a/core/libs/prx/libSceNgs2.native/src/Custom.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Custom.cpp @@ -381,7 +381,7 @@ void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { } auto index = param.id & 0x1f; if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) { - if (index == 0) APS5_INVALID_ARG_EX; + if (index == 0 || index - 1 >= voice.customUserFx.size()) throw Ngs2ReturnCode{SCE_NGS2_ERROR_INVALID_PARAM_ID}; index--; } SetCustomUserFxParam(voice, index, ParamAs(param)); diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp index d30726cb01..570df1ef10 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp @@ -651,24 +651,13 @@ static void TestCustomRackChecks() { for (std::uint32_t index = 0; index < count; ++index) { sampler.custom_rack_option.module[index].module_id = SCE_NGS2_CUSTOM_MODULE_ID_FILTER; } - sampler.custom_rack_option.module[count - 1].module_id = 0x99; - Require(CreateSamplerThrows(sampler)); - for (const bool useAllocator : {false, true}) { - rack = 0; - rejected = false; - try { - if (useAllocator) { - sceNgs2RackCreateWithAllocator(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &allocator, &rack); - - } else { - sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &info, &rack); - - } - } catch (const std::runtime_error&) { - rejected = true; - } - Require(rejected && rack == 0 && allocCalls == 0); - } + sampler.custom_rack_option.module[count - 1].module_id = SCE_NGS2_CUSTOM_MODULE_ID_USER_FX; + Ngs2ContextBufferInfo counted{}; + Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &counted) == SCE_NGS2_OK); + const auto countedInfo = Buffer(counted); + rack = 0; + Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &countedInfo, &rack) == SCE_NGS2_OK); + Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); } Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); } @@ -800,7 +789,6 @@ static Ngs2CustomSamplerRackOption LegacySamplerRackOption(const Ngs2CustomModul static void TestCustomSamplerLegacyUserFx(std::uint32_t moduleIndex) { const auto system = CreateSystem(); const auto master = Mastering(system, 2); - const Ngs2CustomModuleOption header{sizeof(Ngs2CustomModuleOption)}; auto option = LegacySamplerRackOption(nullptr, moduleIndex); Ngs2ContextBufferInfo query{}; Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &option.custom_rack_option.rack_option, &query) == SCE_NGS2_OK); @@ -828,9 +816,11 @@ static void TestCustomSamplerLegacyUserFx(std::uint32_t moduleIndex) { Require(state.calls == 2 && state.flags == 0); for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.25f && out[i * 2 + 1] == 0.5f); - Require(ControlThrows(voice, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | 0, param)); - Require(ControlThrows(voice, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | (moduleIndex + 2), param)); - Require(CreateSamplerThrows(LegacySamplerRackOption(&header, moduleIndex))); + Ngs2CustomVoiceUserFxParam invalid{}; + invalid.header = {sizeof(invalid), 0, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | 0}; + Require(sceNgs2VoiceControl(voice, &invalid.header) == SCE_NGS2_ERROR_INVALID_PARAM_ID); + invalid.header.id = SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | (moduleIndex + 2); + Require(sceNgs2VoiceControl(voice, &invalid.header) == SCE_NGS2_ERROR_INVALID_PARAM_ID); Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); } diff --git a/docs/dev/debt/libSceNgs2.native.md b/docs/dev/debt/libSceNgs2.native.md index 7d756021cb..15f0219d4e 100644 --- a/docs/dev/debt/libSceNgs2.native.md +++ b/docs/dev/debt/libSceNgs2.native.md @@ -36,7 +36,7 @@ - [NGS2 reverb channel conversion](../../../core/libs/prx/libSceNgs2.native/src/Reverb.cpp) (libSceNgs2.native) - a reverb voice whose `num_input_channels` differs from `num_output_channels` (PPSA03747 sets up 2 inputs and 8 outputs) mixes its ports in the input layout and folds that to mono with the input count's gains, as an equal-count voice does, and runs the comb and allpass stages per output channel. The PS4 library's handling of the dry signal in this case is not checked: here it reaches the output layout through the PS4 default rows, not the PS5 port map, so a stereo input keeps its dry signal on the front left and right and the other outputs carry only the reverb - [NGS2 port matrix](../../../core/libs/prx/libSceNgs2.native/src/Render.cpp) (libSceNgs2.native) - gaps: a mastering voice into a buffer of another count throws when either count is 3, 4, 5 or 7, though PS5 firmware 3.20 and 4.03 render them; the PS4 library has no rows into 3, 4, 5 or 7 and throws, its mastering output is unmeasured, and its reverb conversion and Pan keep the PS4 rows; a port into more than 8 channels without a matrix throws, which is unreachable (`0x804A8052` for a custom submixer voice with over 8 channels, 13.42 module). Not run on PS5: non-sampler sources into 4 or 7 channels and from 1, 2, 6 or 8 channels into 3, 4, 5 or 7, an active reverb (I3DL2), custom submixers with modules and custom mastering 0x4003; they use the sampler rows here. The PS5 rows were measured on that module run on a PC, not a console. A matrix with too few levels mixes only its whole rows - [NGS2 custom submixer rack](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, rack 0x4002) - UserFx2 process flags and setup/process/cleanup order follow only [KytyPS5](https://github.com/KytyPS5/KytyPS5), not a console measurement; layouts follow [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_custom.h`. A module control handler, unequal input and output channels, setup flags and default rack options throw. Module state is a separate buffer, not the `state_offset` slice of voice state; `sceNgs2VoiceGetState` throws for this rack. UserFx2 runs only on grains with input samples, so an effect tail stops with the input. Setup changes from custom process callbacks throw before later modules can use the changed layout; inconsistent input-mixing layouts after callbacks also throw. Console reentry behavior is unknown -- [NGS2 custom UserFx](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native) - the 48-byte process context, process flags, callback error handling and null-handler behavior retain the existing submixer callback model and are not console-verified. Non-null module options throw because their console error is unknown. On custom sampler rack 0x4001 the parameter index is one higher than the module index, as fma-rc reads; index 0 throws, and the sampler's single buffer is used because multi-buffer routing is not implemented. Module id 0x19, the 40-byte voice parameter (handler and three user-data words), parameter id `0x40001900 | index`, and lack of option and module state follow [fma-rc's PS5 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943), including parameter dispatch at 0x25ec4. +- [NGS2 custom UserFx](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native) - the 48-byte process context, process flags, callback error handling and null-handler behavior retain the existing submixer callback model and are not console-verified. Non-null module options throw because their console error is unknown. On custom sampler rack 0x4001 the parameter index is one higher than the module index, as fma-rc reads; index 0 and an index past the module count return `INVALID_PARAM_ID`, and the sampler's single buffer is used because multi-buffer routing is not implemented. Module id 0x19, the 40-byte voice parameter (handler and three user-data words), parameter id `0x40001900 | index`, and lack of option and module state follow [fma-rc's PS5 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943), including parameter dispatch at 0x25ec4. - [NGS2 custom Delay](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, 0x1d) - processing and defaults remain unverified on PS5. The [agreed compatibility path](https://github.com/boykopovar/AnyPS5/pull/9337#issuecomment-6351205140) preserves main's PCM pass-through for Void Terrarium (PPSA03061) until any Delay parameter is set; every `0x40001dxx` parameter throws. Only one-buffer custom submixers with no module state and a 20-byte option are accepted. Option type 0/1, taps 1..8, length 1..20000 and reserved 0 follow [fma-rc's 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943). Other shapes and values throw because their console errors are unknown. This preserves host behavior, not console-equivalent Delay processing - [NGS2 custom module ids](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native) - Reverb (0x1b) and Convolution (0x23) are not processed: a rack that passes their creation checks throws. Per [fma-rc's 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6294323150), creation returns `0x804A8302` for a wrong non-zero state size, and `0x804A8013`, `0x804A8099` and `0x804A8098` for a bad Reverb option size or word and PitchShift word. Unknown: check order, errors for a Gain option, missing Reverb options, Convolution option sizes, and Convolution's state (64 bytes from SDK 3.0). PPSA04540 uses 0x15, 0x19, 0x1b and 0x23 (#8039), PPSA35169 0x20 (#4246) and PPSA33655 0x15, 0x19 and 0x1b From f9474c5040b453603ae1a07668607e022812363b Mon Sep 17 00:00:00 2001 From: Bipin Date: Sun, 1 Nov 2026 17:36:45 +0530 Subject: [PATCH 197/612] docs(libkernel_sys): clarify exec null paths and environment merge --- docs/dev/debt/libkernel_sys.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index be25aa4d4b..3547d328ac 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -349,7 +349,7 @@ - [openat, _openat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) pass `AT_FDCWD` (-100) and absolute paths to `open`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. They return `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. As with `open`, `O_NONBLOCK` is ignored. PPSA99003 opens directory entries through `openat`. Console behaviour is unverified - [fchmodat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `chmod`; any other relative path is joined to the descriptor's host directory the same way as for `openat`, so guest path aliases and case-insensitive name matching do not apply to it and a `..` component is not kept inside the guest root. A flag other than `0` or `AT_SYMLINK_NOFOLLOW` (0x200) returns `EINVAL`; the mode is masked to `07777`. With `AT_SYMLINK_NOFOLLOW` a symbolic link returns success silently on Windows and Linux, since the host cannot change a link's own mode, while a regular file is changed either way. The mode maps to the host file mode, which on Windows is only the CRT's read-only bit. PPSA99621 calls it through `std::filesystem::permissions`. Console behaviour is unverified - [lchmod](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws for a symbolic link on Windows and Linux, whose hosts cannot change a link's own mode; on macOS the link's mode is changed. Anything else goes to `fchmodat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW)`, as FreeBSD 11.4's [`sys_lchmod`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L2490-L2496) does, so a file or directory is changed as by `chmod`, with `fchmodat`'s path lookup and Windows mode mapping. On Windows any reparse point counts as a link. Console behaviour is unverified -- [_execve](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) shares libkernel `execve` process replacement after checked argument copying and file validation. Native mount `MNT_NOEXEC`, write-open `ETXTBSY`, `#!` interpreter parsing, descriptor-based execution, Windows mode bits and directory search permissions are unmodelled; `#!` and null pathnames call `NotImplemented_nid_no_patch`. Converted-target naming, missing conversions and host support inherit libkernel exec limitations. Plain ELF acceptance and console error ordering are unverified. The validation order is derived from FreeBSD 11.4 [`exec_copyin_args` and `exec_check_permissions`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_exec.c#L1172-L1680), not console measurements. +- [_execve](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) merges host `environ` with `envp` name overrides instead of replacing the environment. A null path and `#!` throw. FreeBSD 11.4 [`do_execve`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_exec.c#L433-L465) uses descriptor-based execution for a null pathname; that activation is unmodelled. Whether this export shares libkernel `execve`'s measured null-path `EBADF` is unverified. Mount `MNT_NOEXEC`, write-open `ETXTBSY`, Windows mode bits and directory search permissions are unmodelled. Converted-target and host restrictions follow libkernel exec. Console error ordering and plain ELF acceptance are unverified. - [_execvpe](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) inherits `_execve` activation and permission gaps. FreeBSD 11.4 [`execvPe`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/exec.c#L145-L281) supplies guest `PATH` search, errno precedence and the `ENOEXEC` retry through `/bin/sh`; the shell requires a supported converted target. The too-long-candidate stderr diagnostic is omitted. Names are read only within 1024 bytes: oversized names return the search's final `ENOENT`, and unreadable names return `EFAULT`, rather than libc's unchecked access. Console behavior is unverified. - [pathconf, fpathconf, _fpathconf](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) answer every file and directory as the console's UFS mount `/system_data` does in a payload process on a PS5 (firmware 13.60). Other mounts differ: `/data` and `/user` give `_PC_LINK_MAX` 1, `_PC_SYMLINK_MAX` 0, `_PC_NO_TRUNC` `EINVAL` and 255 for `_PC_MAX_CANON`, `_PC_MAX_INPUT` and `_PC_VDISABLE`; `/`, `/system`, `/hostapp` and `/update` answer only four names; tmpfs gives `_PC_FILESIZEBITS` 0 and `_PC_SYNC_IO` 1. What a title sees under `/app0`, `/savedata0`, `/temp0` and `/download0` is not known. `fpathconf` and `_fpathconf` answer `_PC_ASYNC_IO` 200112 and, for a pipe, only `_PC_PIPE_BUF` 512, as FreeBSD 11's `kern_fpathconf` does; a guest socket fails `fstat` with `EBADF` - [mkdirat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `mkdir`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. On Windows, `mode` is ignored. Console behaviour is unverified From 70530ea4b847b37ee6561aae1149a0da13811cad Mon Sep 17 00:00:00 2001 From: Ahmad Mujahid Date: Sun, 1 Nov 2026 19:22:32 +0400 Subject: [PATCH 198/612] docs(debt): set the libSceLibreSsl pending export count to 289 --- docs/dev/debt/libSceLibreSsl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 141a8a8fce..9969bfd95b 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 291 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 289 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. From 9a2b40f2336b7c5f32f234d5a7ff1ca19692d7ed Mon Sep 17 00:00:00 2001 From: Jamie <55252868+chitters2004@users.noreply.github.com> Date: Sun, 1 Nov 2026 15:55:53 +0000 Subject: [PATCH 199/612] fix(agc): bound the frame replay relaunch count and test the relaunch limit An inherited negative or malformed APS5_FRAME_REPLAY_ATTEMPT went through atoi, so the limit of eight relaunches did not hold, and a failed _putenv_s restarted the budget in the child. The count now has to be a whole number from 0 to 8, and the relaunch stops when it cannot be passed on. The new Windows test links without high-entropy addresses so its stack is an arena host region in every process, and covers the eight-relaunch limit, malformed counts and exit codes through the chain. Co-Authored-By: Claude Opus 5.5 --- .../tests/FrameReplayRelaunch.cpp | 98 +++++++++++++++++++ ...c_driver_frame_replay_relaunch_tests.cmake | 12 +++ .../prx/libSceAgcDriver/tools/FrameReplay.cpp | 23 +++-- 3 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/FrameReplayRelaunch.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_relaunch_tests.cmake diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplayRelaunch.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameReplayRelaunch.cpp new file mode 100644 index 0000000000..e5b984dae3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplayRelaunch.cpp @@ -0,0 +1,98 @@ +#define main frameReplayToolMain +#include "../tools/FrameReplay.cpp" +#undef main +#include + +namespace { + +using AgcDriver::FrameCaptureWriter; + +constexpr int Skip = 77; +constexpr DWORD ChildLimit = 60000; + +void check(bool condition, const std::string& reason) { + if (!condition) throw std::runtime_error(reason); +} + +void append(const std::filesystem::path& log, const std::string& line) { + std::ofstream file(log, std::ios::app); + file << line << '\n'; + check(file.good(), "cannot write the relaunch log"); +} + +int child(const std::filesystem::path& directory, int exitCode) { + const int local = 0; + const auto address = reinterpret_cast(&local) & ~std::uintptr_t{0xfff}; + if (!GuestArena::GuestArenaHostRegionOverlaps_nid_postfix(address, 4096)) return Skip; + const char* const attempt = std::getenv("APS5_FRAME_REPLAY_ATTEMPT"); + append(directory / "log.txt", attempt == nullptr ? "none" : attempt); + const auto capture = directory / std::to_string(GetCurrentProcessId()); + FrameCaptureWriter writer(capture); + writer.WriteRange(address, 4096, FrameRangeKind::Watched, 3, AgcDriver::FrameRangeAccess::Readable | AgcDriver::FrameRangeAccess::Gpu); + writer.Finish("{}"); + const FrameCaptureReader reader(capture); + relaunchIfHostRegionBlocks(reader); + append(directory / "log.txt", "continued"); + return exitCode; +} + +int start(const std::filesystem::path& directory, const char* attempt, int exitCode) { + std::filesystem::remove_all(directory); + std::filesystem::create_directories(directory); + check(_putenv_s("APS5_FRAME_REPLAY_ATTEMPT", attempt == nullptr ? "" : attempt) == 0, "cannot set the relaunch count"); + std::wstring self(32768, L'\0'); + self.resize(GetModuleFileNameW(nullptr, self.data(), static_cast(self.size()))); + std::wstring commandLine = L"\"" + self + L"\" child \"" + directory.wstring() + L"\" " + std::to_wstring(exitCode); + STARTUPINFOW startup{}; + startup.cb = sizeof(startup); + PROCESS_INFORMATION process{}; + check(CreateProcessW(nullptr, commandLine.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup, &process) != 0, "cannot start the replay child"); + CloseHandle(process.hThread); + const bool exited = WaitForSingleObject(process.hProcess, ChildLimit) == WAIT_OBJECT_0; + if (!exited) TerminateProcess(process.hProcess, 1); + DWORD code = 1; + GetExitCodeProcess(process.hProcess, &code); + CloseHandle(process.hProcess); + check(exited, "the replay child did not exit"); + return static_cast(code); +} + +std::vector lines(const std::filesystem::path& path) { + std::vector result; + std::ifstream file(path); + for (std::string line; std::getline(file, line);) result.push_back(line); + return result; +} + +bool expectRun(const std::filesystem::path& directory, const char* attempt, int exitCode, int expected, const std::vector& log) { + const int code = start(directory, attempt, exitCode); + if (code == Skip) return false; + const std::string name = attempt == nullptr ? "no count" : attempt; + check(code == expected, name + ": exited with " + std::to_string(code)); + check(lines(directory / "log.txt") == log, name + ": wrong process sequence"); + return true; +} + +} + +int main(int argc, char** argv) { + try { + if (argc == 4 && std::string_view(argv[1]) == "child") return child(argv[2], std::atoi(argv[3])); + check(argc == 2, "usage: agc_driver_frame_replay_relaunch_tests "); + const std::filesystem::path directory = argv[1]; + const std::vector full{"none", "1", "2", "3", "4", "5", "6", "7", "8", "continued"}; + if (!expectRun(directory, nullptr, 0, 0, full)) { + std::puts("skipped: the stack is not an arena host region"); + return Skip; + } + check(expectRun(directory, nullptr, 42, 42, full), "the failing run skipped"); + check(expectRun(directory, "7", 0, 0, {"7", "8", "continued"}), "the last relaunch skipped"); + check(expectRun(directory, "8", 5, 5, {"8", "continued"}), "the exhausted count skipped"); + for (const char* const malformed : {"-1", "-2147483648", "9", "99999999999", "abc", "1x", " 1", "+1"}) check(expectRun(directory, malformed, 0, 2, {malformed}), "a malformed count skipped"); + std::puts("frame replay relaunch tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "FAILED: %s\n", error.what()); + return argc == 4 ? 2 : 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_relaunch_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_relaunch_tests.cmake new file mode 100644 index 0000000000..a3bb15ba3d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_relaunch_tests.cmake @@ -0,0 +1,12 @@ +if(BUILD_TESTING AND WIN32) + add_test_executable(agc_driver_frame_replay_relaunch_tests tests/FrameReplayRelaunch.cpp Execution/src/FrameCapture.cpp) + target_include_directories(agc_driver_frame_replay_relaunch_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) + target_link_libraries(agc_driver_frame_replay_relaunch_tests PRIVATE libSceAgcDriver libkernel libc libzstd_static) + target_link_options(agc_driver_frame_replay_relaunch_tests PRIVATE -Wl,--disable-high-entropy-va) + set_target_properties(agc_driver_frame_replay_relaunch_tests PROPERTIES CXX_EXTENSIONS OFF) + configure_windows_unwind(agc_driver_frame_replay_relaunch_tests) + add_test(NAME agc_driver_frame_replay_relaunch COMMAND agc_driver_frame_replay_relaunch_tests "${CMAKE_CURRENT_BINARY_DIR}/frame replay relaunch") + set_tests_properties(agc_driver_frame_replay_relaunch PROPERTIES + TIMEOUT 120 SKIP_RETURN_CODE 77 + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") +endif() diff --git a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp index 199da031e3..d9b7b3f0b9 100644 --- a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp @@ -795,22 +795,33 @@ FrameCheck compareFrames(const FrameCaptureReader& reader, const Options& option } #ifdef _WIN32 +constexpr int RelaunchAttempts = 8; + +int relaunchAttempt() { + const char* const text = std::getenv("APS5_FRAME_REPLAY_ATTEMPT"); + if (text == nullptr) return 0; + const std::string_view value = text; + int attempt = 0; + const auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), attempt); + if (error != std::errc{} || end != value.data() + value.size() || attempt < 0 || attempt > RelaunchAttempts) + throw std::runtime_error("frame replay: APS5_FRAME_REPLAY_ATTEMPT must be a whole number from 0 to " + std::to_string(RelaunchAttempts) + ", not '" + std::string(value) + "'"); + return attempt; +} + void relaunchIfHostRegionBlocks(const FrameCaptureReader& reader) { - constexpr int Attempts = 8; + const int attempt = relaunchAttempt() + 1; const auto& chunks = reader.Chunks(); const auto blocked = std::find_if(chunks.begin(), chunks.end(), [](const FrameMemoryChunk& chunk) { return chunk.kind == FrameMemoryKind::Range && chunk.rangeKind != FrameRangeKind::Host && (chunk.access & FrameRangeAccess::Gpu) != 0 && GuestArena::GuestArenaHostRegionOverlaps_nid_postfix(static_cast(chunk.address), static_cast(chunk.bytes)); }); if (blocked == chunks.end()) return; - const char* const previous = std::getenv("APS5_FRAME_REPLAY_ATTEMPT"); - const int attempt = previous == nullptr ? 1 : std::atoi(previous) + 1; - if (attempt > Attempts) return; + if (attempt > RelaunchAttempts) return; std::fprintf(stderr, "[frame-replay] a host region sits in the captured GPU range 0x%llx+0x%llx; relaunching (%d of %d)\n", static_cast(blocked->address), - static_cast(blocked->bytes), attempt, Attempts); + static_cast(blocked->bytes), attempt, RelaunchAttempts); std::fflush(stdout); std::fflush(stderr); - _putenv_s("APS5_FRAME_REPLAY_ATTEMPT", std::to_string(attempt).c_str()); + if (_putenv_s("APS5_FRAME_REPLAY_ATTEMPT", std::to_string(attempt).c_str()) != 0) throw std::runtime_error("frame replay: cannot pass the relaunch count to the next process"); std::wstring commandLine = GetCommandLineW(); STARTUPINFOW startup{}; startup.cb = sizeof(startup); From 470356ced9a829cf5d614a4e6497cd15826dfba7 Mon Sep 17 00:00:00 2001 From: Daniel Horn <73882221+danielhorngt@users.noreply.github.com> Date: Sun, 1 Nov 2026 16:00:18 +0000 Subject: [PATCH 200/612] fix(agc): clamp a guest LOD bias to the device limit The S# field reaches -32 to +31.99, but Vulkan refuses a mipLodBias beyond maxSamplerLodBias, so the sampler threw on a bias the guest may set. Co-Authored-By: Claude Opus 5.5 --- .../libSceAgcDriver/Graphics/src/Sampler.cpp | 3 +-- .../prx/libSceAgcDriver/tests/Graphics.cpp | 27 ++++++++++++++++--- docs/dev/debt/libSceAgcDriver.md | 1 + 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp index d7b1fb0056..1d9e58c143 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp @@ -9,7 +9,6 @@ namespace AgcDriver::Graphics { void Sampler::Validate(const Context& context, const GuestSamplerResource& descriptor) { Require(!descriptor.anisotropyEnable || context.samplerAnisotropy, "guest sampler descriptor requests anisotropic filtering which the device does not support"); Require(descriptor.maxAnisotropy <= context.limits.maxSamplerAnisotropy, "guest sampler descriptor requests an anisotropy ratio beyond the device limit"); - Require(descriptor.lodBias >= -context.limits.maxSamplerLodBias && descriptor.lodBias <= context.limits.maxSamplerLodBias, "guest sampler descriptor requests a LOD bias beyond the device limit"); Require(!(descriptor.unnormalizedCoordinates && descriptor.compareEnable), "guest sampler descriptor with unnormalized coordinates enables depth comparison, which is not implemented"); if (descriptor.reductionMode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT) { Require(context.samplerFilterMinmax, "guest sampler descriptor uses a min or max reduction which the device does not support"); @@ -33,7 +32,7 @@ namespace AgcDriver::Graphics { info.addressModeU = descriptor.addressModeU; info.addressModeV = descriptor.addressModeV; info.addressModeW = descriptor.addressModeW; - info.mipLodBias = descriptor.lodBias; + info.mipLodBias = std::clamp(descriptor.lodBias, -context.limits.maxSamplerLodBias, context.limits.maxSamplerLodBias); info.anisotropyEnable = descriptor.anisotropyEnable ? VK_TRUE : VK_FALSE; info.maxAnisotropy = descriptor.maxAnisotropy; info.compareEnable = descriptor.compareEnable ? VK_TRUE : VK_FALSE; diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 7298b9edbf..d148e28910 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -5423,6 +5423,7 @@ struct MockVulkan { std::vector writes; std::map samplerFlags; std::uint32_t samplerCreateCount = 0; + float samplerLodBias = 0.0f; std::vector copies; bool allowCopies = false; std::uint32_t boundSets = 0; @@ -5606,6 +5607,7 @@ VKAPI_ATTR VkResult VKAPI_CALL mockFreeDescriptorSets(VkDevice, VkDescriptorPool VKAPI_ATTR VkResult VKAPI_CALL mockCreateSampler(VkDevice, const VkSamplerCreateInfo* info, const VkAllocationCallbacks*, VkSampler* sampler) { *sampler = makeHandle(); mock.samplerFlags[*sampler] = info->flags; + mock.samplerLodBias = info->mipLodBias; ++mock.samplerCreateCount; ++mock.live; return VK_SUCCESS; @@ -7224,8 +7226,7 @@ void samplerTableDeviceLimitTests(std::optional selected = std::n namespace Abi = ShaderRecompiler::ImageTableAbi; constexpr auto count = 2u * Abi::SamplerEntryElements; const std::array valid{0x92u, (4u * 256u) << 12u, 0u, 0u}; - const std::array, 3> invalid{{ - {0x92u, valid[1], 256u, 0u}, + const std::array, 2> invalid{{ {0x92u | (2u << 9u), valid[1], (2u << 20u) | (2u << 22u), 0u}, {0x92u | (1u << 29u), valid[1], 0u, 0u} }}; @@ -7279,6 +7280,25 @@ void samplerTableDeviceLimitTests(std::optional selected = std::n } } +void samplerLodBiasTests() { + mock = MockVulkan{}; + auto context = mockContext(); + context.samplerAnisotropy = true; + context.limits.maxSamplerAnisotropy = 2.0f; + context.limits.maxSamplerLodBias = 15.0f; + const auto hostBias = [&](std::uint32_t field) { + const std::array words{0x92u, (4u * 256u) << 12u, field, 0u}; + AgcDriver::Graphics::Sampler sampler(context, AgcDriver::Graphics::DecodeSamplerResource(words)); + return mock.samplerLodBias; + }; + Require(hostBias(256u) == 1.0f, "a positive LOD bias within the device limit changed"); + Require(hostBias(0x3f00u) == -1.0f, "a negative LOD bias within the device limit changed"); + Require(hostBias(0xf00u) == 15.0f, "a LOD bias at the device limit changed"); + Require(hostBias(0x1facu) == 15.0f, "a LOD bias above the device limit was not clamped to it"); + Require(hostBias(0x2000u) == -15.0f, "a LOD bias below the device limit was not clamped to it"); + Require(mock.live == 0 && mock.samplerFlags.empty(), "the clamped samplers leaked Vulkan objects"); +} + void hostImportBudgetTests() { using AgcDriver::Graphics::HostImportBudget; constexpr std::uint64_t MiB = 1ull << 20u; @@ -13039,7 +13059,7 @@ int main(int argc, char** argv) { imageTableSnapshotAllocationTests(); return 0; } - const std::array samplerCases{"--sampler-table-lod-bias", "--sampler-table-anisotropy", "--sampler-table-reduction-compare"}; + const std::array samplerCases{"--sampler-table-anisotropy", "--sampler-table-reduction-compare"}; for (std::uint32_t index = 0; index < samplerCases.size(); ++index) { if (argc == 2 && std::string_view(argv[1]) == samplerCases[index]) { samplerTableDeviceLimitTests(index); @@ -13205,6 +13225,7 @@ int main(int argc, char** argv) { imageTableAllocationTests(); imageTableSnapshotAllocationTests(); samplerTableNonSeamlessCubeTests(); + samplerLodBiasTests(); samplerTableDeviceLimitTests(); tableUpdateAfterBindTests(); misalignedShaderDataTests(); diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index fbbeb61e6a..65d50bb1f9 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -189,6 +189,7 @@ - [Presentation under the GPU mutex](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) (libSceAgcDriver) - on a device whose presentation family has no queue left after the main and settle queues, such as RADV, lavapipe and Intel's Windows driver (Arc Graphics on Meteor Lake, 101.9150), which expose a single graphics queue, `vkQueuePresentKHR` still runs on the main queue under `GuestMemory::GpuMutex`, so the draw committer and the queue workers wait for as long as the present blocks. How long that is on those drivers was not measured; on NVIDIA in FIFO mode it is about 6 ms per present. AMD's Windows driver (Radeon 860M) has one too: a Quake II port's trace had 0.3 ms presents (max 3.5), no `GpuMutex.wait.*` behind them. A device with a free queue in that family presents on it under its own mutex, after the GPU mutex is released. Closes when: traces on RADV, lavapipe and Intel's Windows driver also show no `GpuMutex.wait.*` behind `Vulkan.QueuePresent`. - [Unnormalized S# coordinates](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) - `FORCE_UNNORMALIZED` uses a Vulkan `unnormalizedCoordinates` sampler, so the S# LOD range, LOD bias and mip filter are not applied, which is exact only on a one-level view. - An [unnormalized S#](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) maps clamp mode 0 (repeat) on X and Y to clamp-to-edge as required by Vulkan unnormalized coordinates, and assigns the magnification filter when minification and magnification filters differ (since unnormalized coordinates sample at LOD 0). Anisotropy, MC_COORD_TRUNC and other clamp modes still throw. Secondary LOD bias (LOD_BIAS_SEC, bits [19:14]) is accepted and ignored, matching RDNA2 hardware measurements across explicit and implicit samples. High on Life (PPSA15388, #4736) uses secondary LOD bias and unnormalized coordinates with clamp mode 0 and differing min/mag filters during home base and city rendering. +- [S# LOD bias](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp) (libSceAgcDriver) beyond `maxSamplerLodBias` is clamped to it. The S# field reaches -32 to +31.99, while Vulkan refuses a larger `mipLodBias` (15 on an RTX 4080, 15.99 on Intel UHD 770) and clamps a larger sum with a shader bias. The sampled level differs from the console only where the implicit LOD plus the clamped bias does not reach the level the guest bias selects: at a limit of 15, a positive bias on a view of L levels reads a lower level than the console when the texture is magnified more than 2^(16 - L) times, and a negative bias reads a higher level only when it is minified more than 2^15 times. - [TRUNC_COORD](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) is not applied, with normalized or unnormalized coordinates: the texel a point sample reads and the bilinear weights follow the host sampler's rounding. On RDNA 2 (RX 6700 XT), an S# with TRUNC_COORD 0 rounds the exact scaled coordinate to 8 fractional bits, ties to even, and one with TRUNC_COORD 1 truncates it, for point and bilinear filters. AMD's Windows driver (32.0.21045.5002) truncates for nearest samplers and rounds for linear ones. Under it, a TRUNC_COORD 0 point sample at most 1/512 texel below a texel edge reads the texel below the edge where the console reads the one above, and `k / width` computed in f32 lands there for about half of all k. A TRUNC_COORD 1 bilinear weight can be 1/256 above the console's. Other host drivers are not measured. - [Background link-time-optimized relink of library pipelines](../../../core/libs/prx/libSceAgcDriver/Execution/include/PipelineLinkOptimization.hpp) (libSceAgcDriver) is skipped on every GPU under NVIDIA's proprietary Vulkan driver (`VK_DRIVER_ID_NVIDIA_PROPRIETARY`), so those pipelines keep their fast-linked form. The optimized link loses the device when bound on an RTX 3080 (Ampere, #2866) and an RTX 4060 (Ada, #3434); an RTX 5060 and an RTX 5080 (Blackwell) on the same driver do not, so the scope is wider than the evidence. No standard Vulkan query tells the architecture apart, and the driver version that fixes it is unknown. `APS5_NO_PIPELINE_LTO` disables the relink on every driver. - [8_SRGB and 8_8_SRGB textures](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureFormat.cpp) (libSceAgcDriver) that the device samples as R8_SRGB or R8G8_SRGB use its own decode, which need not match gfx1035: NVIDIA's R8_SRGB is one bf16 step off in 29 of 256 codes. A device that cannot sample them gets an RGBA8 sRGB copy instead (a blit of the R8 or RG8 texels, refreshed with the storage image's version), whose decode is likewise the device's; 3D textures and storage images that are not single-depth and format-matched are copied from their snapshot instead. From 62cb63e7a7bb6f72456fd6248cefe3c4e6f96a0b Mon Sep 17 00:00:00 2001 From: fogydes Date: Sun, 1 Nov 2026 22:02:03 +0530 Subject: [PATCH 201/612] fix(libSceVideodec): validate caller pointer arguments in decoder queries and operations --- core/libs/prx/libSceVideodec/Export.cpp | 151 ++++++++++++++++-------- core/libs/tests/GuestVideodec.cpp | 21 ++++ docs/dev/debt/libSceVideodec.md | 1 + 3 files changed, 126 insertions(+), 47 deletions(-) diff --git a/core/libs/prx/libSceVideodec/Export.cpp b/core/libs/prx/libSceVideodec/Export.cpp index b8a4b3cbc8..052fbf7f4f 100644 --- a/core/libs/prx/libSceVideodec/Export.cpp +++ b/core/libs/prx/libSceVideodec/Export.cpp @@ -118,12 +118,18 @@ int CopyPicture(const SceVideodec2::OutputInfo& output, const Geometry& geometry const auto sourcePitch = static_cast(output.framePitchInBytes); const auto* source = static_cast(output.frameBuffer); auto* destination = static_cast(frameBuffer->frameBuffer); - for (std::uint32_t row = 0; row < geometry.height; ++row) - std::memcpy(destination + static_cast(row) * geometry.pitch, source + static_cast(row) * sourcePitch, geometry.width); + for (std::uint32_t row = 0; row < geometry.height; ++row) { + if (!GuestCopyOut_nid_no_patch(destination + static_cast(row) * geometry.pitch, source + static_cast(row) * sourcePitch, geometry.width)) { + return VideodecErrorFrameBufferPointer; + } + } const auto* sourceChroma = source + sourcePitch * geometry.height; auto* destinationChroma = destination + static_cast(geometry.pitch) * geometry.height; - for (std::uint32_t row = 0; row < geometry.height / 2; ++row) - std::memcpy(destinationChroma + static_cast(row) * geometry.pitch, sourceChroma + static_cast(row) * sourcePitch, geometry.width); + for (std::uint32_t row = 0; row < geometry.height / 2; ++row) { + if (!GuestCopyOut_nid_no_patch(destinationChroma + static_cast(row) * geometry.pitch, sourceChroma + static_cast(row) * sourcePitch, geometry.width)) { + return VideodecErrorFrameBufferPointer; + } + } return 0; } @@ -133,16 +139,29 @@ extern "C" { int APS5_VABI sceVideodecCreateDecoder(const ConfigInfo* config, const ResourceInfo* resource, Ctrl* ctrl) { if (config == nullptr || resource == nullptr || ctrl == nullptr) return VideodecErrorArgumentPointer; - if (config->thisSize < sizeof(ConfigInfo) || resource->thisSize < sizeof(ResourceInfo)) return VideodecErrorStructSize; - const auto decoderConfig = MapConfig(*config); + std::uint32_t configSize = 0; + if (!GuestCopyIn_nid_no_patch(&configSize, config, sizeof(configSize))) return VideodecErrorArgumentPointer; + std::uint32_t resourceSize = 0; + if (!GuestCopyIn_nid_no_patch(&resourceSize, resource, sizeof(resourceSize))) return VideodecErrorArgumentPointer; + if (configSize < sizeof(ConfigInfo) || resourceSize < sizeof(ResourceInfo)) return VideodecErrorStructSize; + ConfigInfo localConfig{}; + if (!GuestCopyIn_nid_no_patch(&localConfig, config, sizeof(localConfig))) return VideodecErrorArgumentPointer; + ResourceInfo localResource{}; + if (!GuestCopyIn_nid_no_patch(&localResource, resource, sizeof(localResource))) return VideodecErrorArgumentPointer; + Ctrl localCtrl{}; + localCtrl.thisSize = sizeof(Ctrl); + localCtrl.version = 1; + localCtrl.handle = nullptr; + if (!GuestCopyOut_nid_no_patch(ctrl, &localCtrl, sizeof(localCtrl))) return VideodecErrorArgumentPointer; + const auto decoderConfig = MapConfig(localConfig); SceVideodec2::DecoderMemoryInfo decoderMemory{}; decoderMemory.thisSize = sizeof(SceVideodec2::DecoderMemoryInfo); - decoderMemory.cpuMemorySize = resource->cpuMemorySize; - decoderMemory.cpuMemory = resource->cpuMemory; - decoderMemory.cpuGpuMemorySize = resource->cpuGpuMemorySize; - decoderMemory.cpuGpuMemory = resource->cpuGpuMemory; - decoderMemory.maxFrameBufferSize = resource->maxFrameBufferSize; - decoderMemory.frameBufferAlignment = resource->frameBufferAlignment; + decoderMemory.cpuMemorySize = localResource.cpuMemorySize; + decoderMemory.cpuMemory = localResource.cpuMemory; + decoderMemory.cpuGpuMemorySize = localResource.cpuGpuMemorySize; + decoderMemory.cpuGpuMemory = localResource.cpuGpuMemory; + decoderMemory.maxFrameBufferSize = localResource.maxFrameBufferSize; + decoderMemory.frameBufferAlignment = localResource.frameBufferAlignment; try { sceVideodec2QueryDecoderMemoryInfo_nid_postfix(&decoderConfig, &decoderMemory); @@ -158,31 +177,46 @@ int APS5_VABI sceVideodecCreateDecoder(const ConfigInfo* config, const ResourceI std::lock_guard lock(decodersMutex); decoders.emplace(decoder, decoder); } - ctrl->thisSize = sizeof(Ctrl); - ctrl->handle = decoder; - ctrl->version = 1; + localCtrl.handle = decoder; + if (!GuestCopyOut_nid_no_patch(ctrl, &localCtrl, sizeof(localCtrl))) { + { + std::lock_guard lock(decodersMutex); + decoders.erase(decoder); + } + delete decoder; + sceVideodec2DeleteDecoder_nid_postfix(handle); + return VideodecErrorArgumentPointer; + } return 0; } int APS5_VABI sceVideodecDecode(Ctrl* ctrl, const SceVideodec2::InputData* input, FrameBuffer* frameBuffer, PictureInfo* picture) { if (ctrl == nullptr || input == nullptr || picture == nullptr) return VideodecErrorArgumentPointer; if (frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; - if (ctrl->thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; - if (input->thisSize < sizeof(SceVideodec2::InputData)) return VideodecErrorStructSize; - if (picture->thisSize < sizeof(PictureInfo)) return VideodecErrorStructSize; - if (frameBuffer->thisSize < sizeof(FrameBuffer)) return VideodecErrorStructSize; - auto* decoder = FindDecoder(ctrl->handle); + Ctrl localCtrl{}; + if (!GuestCopyIn_nid_no_patch(&localCtrl, ctrl, sizeof(localCtrl))) return VideodecErrorArgumentPointer; + SceVideodec2::InputData localInput{}; + if (!GuestCopyIn_nid_no_patch(&localInput, input, sizeof(localInput))) return VideodecErrorArgumentPointer; + PictureInfo localPicture{}; + if (!GuestCopyIn_nid_no_patch(&localPicture, picture, sizeof(localPicture))) return VideodecErrorArgumentPointer; + FrameBuffer localFrameBuffer{}; + if (!GuestCopyIn_nid_no_patch(&localFrameBuffer, frameBuffer, sizeof(localFrameBuffer))) return VideodecErrorFrameBufferPointer; + if (localCtrl.thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; + if (localInput.thisSize < sizeof(SceVideodec2::InputData)) return VideodecErrorStructSize; + if (localPicture.thisSize < sizeof(PictureInfo)) return VideodecErrorStructSize; + if (localFrameBuffer.thisSize < sizeof(FrameBuffer)) return VideodecErrorStructSize; + auto* decoder = FindDecoder(localCtrl.handle); if (decoder == nullptr) return VideodecErrorHandle; - if (input->auData == nullptr) return VideodecErrorAuPointer; - if (input->auSize == 0) return VideodecErrorAuSize; - if (frameBuffer->frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; + if (localInput.auData == nullptr) return VideodecErrorAuPointer; + if (localInput.auSize == 0) return VideodecErrorAuSize; + if (localFrameBuffer.frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; SceVideodec2::FrameBuffer target{}; target.thisSize = sizeof(SceVideodec2::FrameBuffer); target.frameBuffer = decoder->picture.data(); target.frameBufferSize = decoder->picture.size(); SceVideodec2::OutputInfo output{sizeof(SceVideodec2::OutputInfo)}; try { - sceVideodec2Decode_nid_postfix(decoder->handle, input, &target, &output); + sceVideodec2Decode_nid_postfix(decoder->handle, &localInput, &target, &output); } catch (const std::exception&) { return VideodecErrorDecodeAu; @@ -190,20 +224,23 @@ int APS5_VABI sceVideodecDecode(Ctrl* ctrl, const SceVideodec2::InputData* input SceVideodec2::Internal::PictureHeader header{}; if (output.isValid) Videodec2PictureHeader_nid_no_patch(decoder->handle, output.frameBuffer, &header); const auto geometry = Measure(output, header); - FillPictureInfo(geometry, output, picture); - picture->ptsData = header.pts; - picture->attachedData = header.attached; + FillPictureInfo(geometry, output, &localPicture); + localPicture.ptsData = header.pts; + localPicture.attachedData = header.attached; + if (!GuestCopyOut_nid_no_patch(picture, &localPicture, sizeof(localPicture))) return VideodecErrorArgumentPointer; if (!output.isValid) return 0; - return CopyPicture(output, geometry, frameBuffer); + return CopyPicture(output, geometry, &localFrameBuffer); } int APS5_VABI sceVideodecDeleteDecoder(Ctrl* ctrl) { if (ctrl == nullptr) return VideodecErrorArgumentPointer; - if (ctrl->thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; + Ctrl localCtrl{}; + if (!GuestCopyIn_nid_no_patch(&localCtrl, ctrl, sizeof(localCtrl))) return VideodecErrorArgumentPointer; + if (localCtrl.thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; Decoder* decoder = nullptr; { std::lock_guard lock(decodersMutex); - const auto found = decoders.find(ctrl->handle); + const auto found = decoders.find(localCtrl.handle); if (found == decoders.end()) return VideodecErrorHandle; decoder = found->second; decoders.erase(found); @@ -222,12 +259,18 @@ int APS5_VABI sceVideodecDeleteDecoder(Ctrl* ctrl) { int APS5_VABI sceVideodecFlush(Ctrl* ctrl, FrameBuffer* frameBuffer, PictureInfo* picture) { if (ctrl == nullptr || picture == nullptr) return VideodecErrorArgumentPointer; if (frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; - if (ctrl->thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; - if (picture->thisSize < sizeof(PictureInfo)) return VideodecErrorStructSize; - if (frameBuffer->thisSize < sizeof(FrameBuffer)) return VideodecErrorStructSize; - auto* decoder = FindDecoder(ctrl->handle); + Ctrl localCtrl{}; + if (!GuestCopyIn_nid_no_patch(&localCtrl, ctrl, sizeof(localCtrl))) return VideodecErrorArgumentPointer; + PictureInfo localPicture{}; + if (!GuestCopyIn_nid_no_patch(&localPicture, picture, sizeof(localPicture))) return VideodecErrorArgumentPointer; + FrameBuffer localFrameBuffer{}; + if (!GuestCopyIn_nid_no_patch(&localFrameBuffer, frameBuffer, sizeof(localFrameBuffer))) return VideodecErrorFrameBufferPointer; + if (localCtrl.thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; + if (localPicture.thisSize < sizeof(PictureInfo)) return VideodecErrorStructSize; + if (localFrameBuffer.thisSize < sizeof(FrameBuffer)) return VideodecErrorStructSize; + auto* decoder = FindDecoder(localCtrl.handle); if (decoder == nullptr) return VideodecErrorHandle; - if (frameBuffer->frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; + if (localFrameBuffer.frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; SceVideodec2::FrameBuffer target{}; target.thisSize = sizeof(SceVideodec2::FrameBuffer); target.frameBuffer = decoder->picture.data(); @@ -242,17 +285,20 @@ int APS5_VABI sceVideodecFlush(Ctrl* ctrl, FrameBuffer* frameBuffer, PictureInfo SceVideodec2::Internal::PictureHeader header{}; if (output.isValid) Videodec2PictureHeader_nid_no_patch(decoder->handle, output.frameBuffer, &header); const auto geometry = Measure(output, header); - FillPictureInfo(geometry, output, picture); - picture->ptsData = header.pts; - picture->attachedData = header.attached; + FillPictureInfo(geometry, output, &localPicture); + localPicture.ptsData = header.pts; + localPicture.attachedData = header.attached; + if (!GuestCopyOut_nid_no_patch(picture, &localPicture, sizeof(localPicture))) return VideodecErrorArgumentPointer; if (!output.isValid) return 0; - return CopyPicture(output, geometry, frameBuffer); + return CopyPicture(output, geometry, &localFrameBuffer); } int APS5_VABI sceVideodecReset(Ctrl* ctrl) { if (ctrl == nullptr) return VideodecErrorArgumentPointer; - if (ctrl->thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; - auto* decoder = FindDecoder(ctrl->handle); + Ctrl localCtrl{}; + if (!GuestCopyIn_nid_no_patch(&localCtrl, ctrl, sizeof(localCtrl))) return VideodecErrorArgumentPointer; + if (localCtrl.thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; + auto* decoder = FindDecoder(localCtrl.handle); if (decoder == nullptr) return VideodecErrorHandle; try { sceVideodec2Reset_nid_postfix(decoder->handle); @@ -269,8 +315,16 @@ int APS5_VABI sceVideodecMapMemory(void) { int APS5_VABI sceVideodecQueryResourceInfo(const ConfigInfo* config, ResourceInfo* resource) { if (config == nullptr || resource == nullptr) return VideodecErrorArgumentPointer; - if (config->thisSize < sizeof(ConfigInfo) || resource->thisSize < sizeof(ResourceInfo)) return VideodecErrorStructSize; - const auto decoderConfig = MapConfig(*config); + std::uint32_t configSize = 0; + if (!GuestCopyIn_nid_no_patch(&configSize, config, sizeof(configSize))) return VideodecErrorArgumentPointer; + std::uint32_t resourceSize = 0; + if (!GuestCopyIn_nid_no_patch(&resourceSize, resource, sizeof(resourceSize))) return VideodecErrorArgumentPointer; + if (configSize < sizeof(ConfigInfo) || resourceSize < sizeof(ResourceInfo)) return VideodecErrorStructSize; + ConfigInfo localConfig{}; + if (!GuestCopyIn_nid_no_patch(&localConfig, config, sizeof(localConfig))) return VideodecErrorArgumentPointer; + ResourceInfo localResource{}; + if (!GuestCopyIn_nid_no_patch(&localResource, resource, sizeof(localResource))) return VideodecErrorArgumentPointer; + const auto decoderConfig = MapConfig(localConfig); SceVideodec2::DecoderMemoryInfo decoderMemory{}; decoderMemory.thisSize = sizeof(SceVideodec2::DecoderMemoryInfo); try { @@ -279,10 +333,13 @@ int APS5_VABI sceVideodecQueryResourceInfo(const ConfigInfo* config, ResourceInf } catch (const std::exception&) { return VideodecErrorConfigInfo; } - resource->cpuMemorySize = decoderMemory.cpuMemorySize; - resource->cpuGpuMemorySize = decoderMemory.cpuGpuMemorySize; - resource->maxFrameBufferSize = decoderMemory.maxFrameBufferSize; - resource->frameBufferAlignment = decoderMemory.frameBufferAlignment; + localResource.cpuMemorySize = decoderMemory.cpuMemorySize; + localResource.cpuGpuMemorySize = decoderMemory.cpuGpuMemorySize; + localResource.maxFrameBufferSize = decoderMemory.maxFrameBufferSize; + localResource.frameBufferAlignment = decoderMemory.frameBufferAlignment; + if (!GuestCopyOut_nid_no_patch(resource, &localResource, sizeof(localResource))) { + return VideodecErrorArgumentPointer; + } return 0; } diff --git a/core/libs/tests/GuestVideodec.cpp b/core/libs/tests/GuestVideodec.cpp index 68b3d8493e..3f18fea093 100644 --- a/core/libs/tests/GuestVideodec.cpp +++ b/core/libs/tests/GuestVideodec.cpp @@ -127,6 +127,17 @@ void testErrors() { const SceVideodec2::InputData empty{sizeof(SceVideodec2::InputData), nullptr, 0, 0, 0, 0}; FrameBuffer frame{sizeof(FrameBuffer), nullptr, 0}; PictureInfo picture{sizeof(PictureInfo)}; + auto* const badCtrl = reinterpret_cast(std::uintptr_t{0x10}); + auto* const badInput = reinterpret_cast(std::uintptr_t{0x10}); + auto* const badFrame = reinterpret_cast(std::uintptr_t{0x10}); + auto* const badPicture = reinterpret_cast(std::uintptr_t{0x10}); + check(sceVideodecDecode(badCtrl, &empty, &frame, &picture) == ErrorArgumentPointer, "an unreadable control block was accepted by decode"); + check(sceVideodecDecode(&ctrl, badInput, &frame, &picture) == ErrorArgumentPointer, "an unreadable input block was accepted by decode"); + check(sceVideodecDecode(&ctrl, &empty, badFrame, &picture) == ErrorFrameBufferPointer, "an unreadable frame buffer was accepted by decode"); + check(sceVideodecDecode(&ctrl, &empty, &frame, badPicture) == ErrorArgumentPointer, "an unreadable picture info was accepted by decode"); + check(sceVideodecFlush(badCtrl, &frame, &picture) == ErrorArgumentPointer, "an unreadable control block was accepted by flush"); + check(sceVideodecFlush(&ctrl, badFrame, &picture) == ErrorFrameBufferPointer, "an unreadable frame buffer was accepted by flush"); + check(sceVideodecFlush(&ctrl, &frame, badPicture) == ErrorArgumentPointer, "an unreadable picture info was accepted by flush"); check(sceVideodecDecode(nullptr, &empty, &frame, &picture) == ErrorArgumentPointer, "a null control block was accepted"); check(sceVideodecDecode(&ctrl, &empty, &frame, &picture) == ErrorAuPointer, "a null access unit was accepted"); const SceVideodec2::InputData sized{sizeof(SceVideodec2::InputData), reinterpret_cast(&config), 0, 0, 0, 0}; @@ -247,8 +258,18 @@ void testQueryResourceInfo() { Ctrl ctrl{sizeof(Ctrl)}; check(sceVideodecCreateDecoder(&config, &resource, &ctrl) == 0, "the queried resources were refused"); check(sceVideodecDeleteDecoder(&ctrl) == 0, "the decoder was not deleted"); + auto* const badConfig = reinterpret_cast(std::uintptr_t{0x10}); + auto* const badResource = reinterpret_cast(std::uintptr_t{0x10}); + auto* const badCtrl = reinterpret_cast(std::uintptr_t{0x10}); check(sceVideodecQueryResourceInfo(nullptr, &resource) == ErrorArgumentPointer, "a null configuration was accepted"); check(sceVideodecQueryResourceInfo(&config, nullptr) == ErrorArgumentPointer, "null resource information was accepted"); + check(sceVideodecQueryResourceInfo(badConfig, &resource) == ErrorArgumentPointer, "an unreadable configuration was accepted"); + check(sceVideodecQueryResourceInfo(&config, badResource) == ErrorArgumentPointer, "an unwritable resource information pointer was accepted"); + check(sceVideodecCreateDecoder(badConfig, &resource, &ctrl) == ErrorArgumentPointer, "an unreadable configuration was accepted by create"); + check(sceVideodecCreateDecoder(&config, badResource, &ctrl) == ErrorArgumentPointer, "an unreadable resource was accepted by create"); + check(sceVideodecCreateDecoder(&config, &resource, badCtrl) == ErrorArgumentPointer, "an unwritable control block was accepted by create"); + check(sceVideodecDeleteDecoder(badCtrl) == ErrorArgumentPointer, "an unreadable control block was accepted by delete"); + check(sceVideodecReset(badCtrl) == ErrorArgumentPointer, "an unreadable control block was accepted by reset"); const ResourceInfo sentinel{sizeof(ResourceInfo), 7, nullptr, 7, nullptr, 7, 7}; auto refused = sentinel; auto shortConfig = config; diff --git a/docs/dev/debt/libSceVideodec.md b/docs/dev/debt/libSceVideodec.md index c22b5e1589..8b6fa3aa29 100644 --- a/docs/dev/debt/libSceVideodec.md +++ b/docs/dev/debt/libSceVideodec.md @@ -13,3 +13,4 @@ - [sceVideodecDecode and sceVideodecFlush](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - the picture information's video usability information (the tick rate, the sample aspect ratio and the colour description) stays zero, as the decoder does not report it, and the crop offsets are the decoded picture's crop halved, the unit the H.264 offsets use. The frame buffer holds the coded picture the decoder reports, so the rows the crop covers are in it and the caller crops them. A picture deeper than 8 bits, such as one of a High 10 stream, throws, as what the console writes for it is not known; when its rows do not fit the staging buffer sized for 8-bit pictures of the configured size, the decode fails with the decode error before that - [sceVideodecMapMemory](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - its name is listed in the PS5 3.20 export table and cross-checked against PS5 13.60. Its signature and behaviour are unverified; this no-parameter declaration only resolves the import and throws through NotImplemented_nid_no_patch. +- [sceVideodec pointer safety](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - `sceVideodecCreateDecoder`, `sceVideodecDecode`, `sceVideodecDeleteDecoder`, `sceVideodecFlush`, `sceVideodecReset`, and `sceVideodecQueryResourceInfo` validate caller input and output pointers using `GuestCopyIn_nid_no_patch` and `GuestCopyOut_nid_no_patch`, returning `VideodecErrorArgumentPointer` or `VideodecErrorFrameBufferPointer` on invalid or unwritable memory rather than causing host access violations. On native console firmware, memory fault handling is managed by the kernel or hardware protection; this project returns the library pointer error. From 03f2eb905b61663f3ddc1ca9e7001eb7b3cae9b5 Mon Sep 17 00:00:00 2001 From: Ahmad Mujahid Date: Sun, 1 Nov 2026 20:30:14 +0400 Subject: [PATCH 202/612] feat(libSceJxr): implement WMPAlloc, WMPFree and the memory stream --- core/libs/prx/libSceJxr/Export.cpp | 84 +++++++++++--- core/libs/prx/libSceJxr/include/Api.hpp | 16 +-- core/libs/tests/JxrStream.cpp | 145 ++++++++++++++++++++++++ docs/dev/debt/libSceJxr.md | 4 +- 4 files changed, 224 insertions(+), 25 deletions(-) diff --git a/core/libs/prx/libSceJxr/Export.cpp b/core/libs/prx/libSceJxr/Export.cpp index 50f91d2edc..092b0b64df 100644 --- a/core/libs/prx/libSceJxr/Export.cpp +++ b/core/libs/prx/libSceJxr/Export.cpp @@ -10,6 +10,15 @@ namespace { constexpr std::int64_t WmpErrSuccess = 0; +constexpr std::int64_t WmpErrOutOfMemory = -101; +constexpr std::int64_t WmpErrBufferOverflow = -103; +constexpr std::size_t MemoryBuffer = 0; +constexpr std::size_t MemorySize = 1; +constexpr std::size_t MemoryPosition = 2; + +std::int64_t APS5_VABI CloseWS_Memory_nid_no_patch(Jxr::Stream** stream) { + return WMPFree_nid_postfix(reinterpret_cast(stream)); +} } extern "C" { @@ -72,16 +81,30 @@ int APS5_VABI CreateWS_List_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI CreateWS_Memory_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI CreateWS_Memory_nid_postfix(Jxr::Stream** stream, void* buffer, std::size_t size) { + if (!stream) APS5_INVALID_ARG_EX; + const auto result = WMPAlloc_nid_postfix(reinterpret_cast(stream), sizeof(Jxr::Stream)); + if (result < 0) return result; + auto* created = *stream; + created->state[MemoryBuffer] = reinterpret_cast(buffer); + created->state[MemorySize] = size; + created->state[MemoryPosition] = 0; + created->close = CloseWS_Memory_nid_no_patch; + created->end = EOSWS_Memory_nid_postfix; + created->read = ReadWS_Memory_nid_postfix; + created->write = WriteWS_Memory_nid_postfix; + created->setPosition = SetPosWS_Memory_nid_postfix; + created->getPosition = GetPosWS_Memory_nid_postfix; + return WmpErrSuccess; } int APS5_VABI EOSWS_File_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI EOSWS_Memory_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int32_t APS5_VABI EOSWS_Memory_nid_postfix(Jxr::Stream* stream) { + if (!stream) APS5_INVALID_ARG_EX; + return stream->state[MemorySize] <= stream->state[MemoryPosition]; } int APS5_VABI FreeDescMetadata_nid_postfix() { @@ -108,8 +131,10 @@ int APS5_VABI GetPosWS_List_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI GetPosWS_Memory_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI GetPosWS_Memory_nid_postfix(Jxr::Stream* stream, std::size_t* position) { + if (!stream || !position) APS5_INVALID_ARG_EX; + *position = stream->state[MemoryPosition]; + return WmpErrSuccess; } int APS5_VABI IDPEmpty_nid_postfix() { @@ -336,8 +361,18 @@ int APS5_VABI ReadWS_List_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI ReadWS_Memory_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI ReadWS_Memory_nid_postfix(Jxr::Stream* stream, void* buffer, std::size_t size) { + if (!stream) APS5_INVALID_ARG_EX; + auto& state = stream->state; + if (state[MemorySize] < state[MemoryPosition]) return WmpErrSuccess; + if (state[MemoryPosition] + size < state[MemoryPosition]) return WmpErrBufferOverflow; + if (state[MemorySize] < state[MemoryPosition] + size) size = state[MemorySize] - state[MemoryPosition]; + if (size != 0) { + if (!buffer || !state[MemoryBuffer]) APS5_INVALID_ARG_EX; + std::memcpy(buffer, reinterpret_cast(state[MemoryBuffer]) + state[MemoryPosition], size); + } + state[MemoryPosition] += size; + return WmpErrSuccess; } int APS5_VABI ResetCodingContext_nid_postfix() { @@ -352,16 +387,25 @@ int APS5_VABI SetPosWS_List_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI SetPosWS_Memory_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI SetPosWS_Memory_nid_postfix(Jxr::Stream* stream, std::size_t position) { + if (!stream) APS5_INVALID_ARG_EX; + stream->state[MemoryPosition] = position; + return WmpErrSuccess; } -int APS5_VABI WMPAlloc_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI WMPAlloc_nid_postfix(void** memory, std::size_t size) { + if (!memory) APS5_INVALID_ARG_EX; + *memory = calloc_nid_postfix(1, size); + return *memory ? WmpErrSuccess : WmpErrOutOfMemory; } -int APS5_VABI WMPFree_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI WMPFree_nid_postfix(void** memory) { + if (!memory) APS5_INVALID_ARG_EX; + if (*memory) { + free_nid_postfix(*memory); + *memory = nullptr; + } + return WmpErrSuccess; } int APS5_VABI WriteWS_File_nid_postfix() { @@ -372,8 +416,16 @@ int APS5_VABI WriteWS_List_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WriteWS_Memory_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI WriteWS_Memory_nid_postfix(Jxr::Stream* stream, const void* buffer, std::size_t size) { + if (!stream) APS5_INVALID_ARG_EX; + auto& state = stream->state; + if (state[MemoryPosition] + size < state[MemoryPosition] || state[MemorySize] < state[MemoryPosition] + size) return WmpErrBufferOverflow; + if (size != 0) { + if (!buffer || !state[MemoryBuffer]) APS5_INVALID_ARG_EX; + std::memcpy(reinterpret_cast(state[MemoryBuffer]) + state[MemoryPosition], buffer, size); + } + state[MemoryPosition] += size; + return WmpErrSuccess; } int APS5_VABI advanceMRPtr_nid_postfix() { diff --git a/core/libs/prx/libSceJxr/include/Api.hpp b/core/libs/prx/libSceJxr/include/Api.hpp index b57921294e..9aae808b9c 100644 --- a/core/libs/prx/libSceJxr/include/Api.hpp +++ b/core/libs/prx/libSceJxr/include/Api.hpp @@ -14,16 +14,16 @@ std::int64_t APS5_VABI writeIS_nid_postfix(void* codec, Jxr::BitIo* io); int APS5_VABI AllocateCodingContextDec_nid_postfix(); int APS5_VABI CloseWS_List_nid_postfix(); int APS5_VABI CreateWS_List_nid_postfix(); -int APS5_VABI CreateWS_Memory_nid_postfix(); +std::int64_t APS5_VABI CreateWS_Memory_nid_postfix(Jxr::Stream** stream, void* buffer, std::size_t size); int APS5_VABI EOSWS_File_nid_postfix(); -int APS5_VABI EOSWS_Memory_nid_postfix(); +std::int32_t APS5_VABI EOSWS_Memory_nid_postfix(Jxr::Stream* stream); int APS5_VABI FreeDescMetadata_nid_postfix(); int APS5_VABI GetImageDecodeIID_nid_postfix(); int APS5_VABI GetImageEncodeIID_nid_postfix(); int APS5_VABI GetPixelFormatFromHash_nid_postfix(); int APS5_VABI GetPosWS_File_nid_postfix(); int APS5_VABI GetPosWS_List_nid_postfix(); -int APS5_VABI GetPosWS_Memory_nid_postfix(); +std::int64_t APS5_VABI GetPosWS_Memory_nid_postfix(Jxr::Stream* stream, std::size_t* position); int APS5_VABI IDPEmpty_nid_postfix(); int APS5_VABI InitZigzagScan_nid_postfix(); int APS5_VABI PKAllocAligned_nid_postfix(); @@ -80,16 +80,16 @@ int APS5_VABI PKImageEncode_WritePixels_nid_postfix(); int APS5_VABI PKImageEncode_WriteSource_nid_postfix(); int APS5_VABI ReadWS_File_nid_postfix(); int APS5_VABI ReadWS_List_nid_postfix(); -int APS5_VABI ReadWS_Memory_nid_postfix(); +std::int64_t APS5_VABI ReadWS_Memory_nid_postfix(Jxr::Stream* stream, void* buffer, std::size_t size); int APS5_VABI ResetCodingContext_nid_postfix(); int APS5_VABI SetPosWS_File_nid_postfix(); int APS5_VABI SetPosWS_List_nid_postfix(); -int APS5_VABI SetPosWS_Memory_nid_postfix(); -int APS5_VABI WMPAlloc_nid_postfix(); -int APS5_VABI WMPFree_nid_postfix(); +std::int64_t APS5_VABI SetPosWS_Memory_nid_postfix(Jxr::Stream* stream, std::size_t position); +std::int64_t APS5_VABI WMPAlloc_nid_postfix(void** memory, std::size_t size); +std::int64_t APS5_VABI WMPFree_nid_postfix(void** memory); int APS5_VABI WriteWS_File_nid_postfix(); int APS5_VABI WriteWS_List_nid_postfix(); -int APS5_VABI WriteWS_Memory_nid_postfix(); +std::int64_t APS5_VABI WriteWS_Memory_nid_postfix(Jxr::Stream* stream, const void* buffer, std::size_t size); int APS5_VABI advanceMRPtr_nid_postfix(); int APS5_VABI advanceOneMBRow_nid_postfix(); int APS5_VABI allocateBitIOInfo_nid_postfix(); diff --git a/core/libs/tests/JxrStream.cpp b/core/libs/tests/JxrStream.cpp index 066f187541..73bde91b1f 100644 --- a/core/libs/tests/JxrStream.cpp +++ b/core/libs/tests/JxrStream.cpp @@ -1,4 +1,5 @@ #include "prx/libSceJxr/libSceJxr.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/include/Api.hpp" #include @@ -289,9 +290,147 @@ void GuestMapping(Jxr::Stream& stream) { Require(sceKernelReleaseDirectMemory(physical, mappingSize) == 0, "release guest packet backing"); } +constexpr std::size_t Max = std::numeric_limits::max(); + +bool Filled(const std::uint8_t* begin, const std::uint8_t* end, std::uint8_t value) { + return std::all_of(begin, end, [value](std::uint8_t byte) { return byte == value; }); +} + +Jxr::Stream* MemoryStream(void* buffer, std::size_t size) { + Jxr::Stream* stream = nullptr; + Require(CreateWS_Memory_nid_postfix(&stream, buffer, size) == 0 && stream, "memory stream creation failed"); + return stream; +} + +void CloseMemory(Jxr::Stream* stream) { + const auto close = stream->close; + Require(close(&stream) == 0 && stream == nullptr, "memory stream close did not free and clear the pointer"); + Require(close(&stream) == 0 && stream == nullptr, "memory stream close of a null pointer failed"); +} + +void Allocation() { + void* memory = &calls; + Require(WMPAlloc_nid_postfix(&memory, 24) == 0 && memory && memory != &calls, "allocation of 24 bytes failed"); + auto* bytes = static_cast(memory); + Require(Filled(bytes, bytes + 24, 0), "allocation is not zeroed"); + Require(WMPFree_nid_postfix(&memory) == 0 && memory == nullptr, "free did not release and clear the pointer"); + Require(WMPFree_nid_postfix(&memory) == 0 && memory == nullptr, "free of a null pointer failed"); + memory = &calls; + Require(WMPAlloc_nid_postfix(&memory, Max) == -101 && memory == nullptr, "failed allocation did not report out of memory"); + RequireInvalidArgument([] { WMPAlloc_nid_postfix(nullptr, 24); }, "allocation accepted a null pointer"); + RequireInvalidArgument([] { WMPFree_nid_postfix(nullptr); }, "free accepted a null pointer"); +} + +void MemoryCreate() { + std::array bytes{}; + for (void* buffer : {static_cast(bytes.data()), static_cast(nullptr)}) { + auto* stream = reinterpret_cast(&calls); + Require(CreateWS_Memory_nid_postfix(&stream, buffer, bytes.size()) == 0 && stream && stream != reinterpret_cast(&calls), "memory stream creation failed"); + Require(stream->state == std::array{reinterpret_cast(buffer), bytes.size(), 0, 0} && stream->memory == 0, "memory stream state differs"); + Require(stream->end && stream->read && stream->write && stream->setPosition && stream->getPosition && stream->close, "memory stream callbacks are missing"); + if (buffer) { + const std::array source{9, 8, 7, 6}; + std::array target{}; + std::size_t position = 99; + Require(stream->write(stream, source.data(), source.size()) == 0 && std::equal(source.begin(), source.end(), bytes.begin()), "stored write callback differs"); + Require(stream->setPosition(stream, 1) == 0 && stream->read(stream, target.data(), 3) == 0 && std::equal(target.begin(), target.begin() + 3, source.begin() + 1), "stored read callback differs"); + Require(stream->getPosition(stream, &position) == 0 && position == 4 && stream->end(stream) == 0, "stored position callbacks differ"); + Require(stream->setPosition(stream, bytes.size()) == 0 && stream->end(stream) == 1, "stored end callback differs"); + } + CloseMemory(stream); + } + RequireInvalidArgument([&] { CreateWS_Memory_nid_postfix(nullptr, bytes.data(), bytes.size()); }, "memory stream creation accepted a null stream pointer"); +} + +void MemoryRead() { + const std::array source{1, 2, 3, 4, 5, 6, 7, 8}; + auto* stream = MemoryStream(const_cast(source.data()), source.size()); + std::array target; + target.fill(0xcc); + Require(ReadWS_Memory_nid_postfix(stream, target.data(), 3) == 0 && stream->state[2] == 3, "in-bounds read failed or did not advance"); + Require(std::equal(source.begin(), source.begin() + 3, target.begin()) && Filled(target.data() + 3, target.data() + 8, 0xcc), "in-bounds read data differs"); + target.fill(0xcc); + Require(ReadWS_Memory_nid_postfix(stream, target.data(), 8) == 0 && stream->state[2] == 8, "read across the end did not clamp"); + Require(std::equal(source.begin() + 3, source.end(), target.begin()) && Filled(target.data() + 5, target.data() + 8, 0xcc), "clamped read data differs"); + target.fill(0xcc); + Require(ReadWS_Memory_nid_postfix(stream, target.data(), 1) == 0 && stream->state[2] == 8 && Filled(target.data(), target.data() + 8, 0xcc), "read at the end copied or moved"); + Require(SetPosWS_Memory_nid_postfix(stream, 20) == 0 && ReadWS_Memory_nid_postfix(stream, nullptr, 4) == 0 && stream->state[2] == 20, "read past the end failed or moved"); + Require(ReadWS_Memory_nid_postfix(stream, nullptr, Max) == 0 && stream->state[2] == 20, "read past the end checked for overflow first"); + Require(SetPosWS_Memory_nid_postfix(stream, 1) == 0 && ReadWS_Memory_nid_postfix(stream, target.data(), Max) == -103, "wrapping read did not overflow"); + Require(stream->state[2] == 1 && Filled(target.data(), target.data() + 8, 0xcc), "wrapping read copied or moved"); + RequireInvalidArgument([&] { ReadWS_Memory_nid_postfix(stream, nullptr, 1); }, "read accepted a null destination"); + Require(stream->state[2] == 1, "rejected read moved"); + CloseMemory(stream); +} + +void MemoryWrite() { + const std::array source{1, 2, 3, 4, 5, 6, 7, 8}; + std::array target; + target.fill(0xcc); + auto* stream = MemoryStream(target.data(), target.size()); + Require(WriteWS_Memory_nid_postfix(stream, source.data(), 5) == 0 && stream->state[2] == 5, "in-bounds write failed or did not advance"); + Require(std::equal(source.begin(), source.begin() + 5, target.begin()) && Filled(target.data() + 5, target.data() + 8, 0xcc), "in-bounds write data differs"); + Require(WriteWS_Memory_nid_postfix(stream, source.data(), 4) == -103 && stream->state[2] == 5 && Filled(target.data() + 5, target.data() + 8, 0xcc), "write past the end did not overflow cleanly"); + Require(WriteWS_Memory_nid_postfix(stream, source.data(), 3) == 0 && stream->state[2] == 8 && std::equal(source.begin(), source.begin() + 3, target.begin() + 5), "write up to the end failed"); + Require(SetPosWS_Memory_nid_postfix(stream, 1) == 0 && WriteWS_Memory_nid_postfix(stream, source.data(), Max) == -103 && stream->state[2] == 1, "wrapping write did not overflow"); + Require(WriteWS_Memory_nid_postfix(stream, nullptr, 0) == 0 && stream->state[2] == 1, "empty in-bounds write failed"); + RequireInvalidArgument([&] { WriteWS_Memory_nid_postfix(stream, nullptr, 1); }, "write accepted a null source"); + Require(SetPosWS_Memory_nid_postfix(stream, 9) == 0 && WriteWS_Memory_nid_postfix(stream, source.data(), 0) == -103 && stream->state[2] == 9, "empty write past the end did not overflow"); + Require(std::equal(source.begin(), source.begin() + 5, target.begin()) && std::equal(source.begin(), source.begin() + 3, target.begin() + 5), "failed writes changed the buffer"); + CloseMemory(stream); +} + +void MemoryPosition() { + std::array bytes{}; + auto* stream = MemoryStream(bytes.data(), bytes.size()); + std::size_t position = 99; + for (std::size_t offset : {std::size_t{0}, std::size_t{7}, std::size_t{8}, std::size_t{9}, Max}) { + Require(SetPosWS_Memory_nid_postfix(stream, offset) == 0 && stream->state[2] == offset, "set position failed"); + Require(EOSWS_Memory_nid_postfix(stream) == (offset >= 8 ? 1 : 0), "end of stream differs"); + Require(GetPosWS_Memory_nid_postfix(stream, &position) == 0 && position == offset, "get position differs"); + } + Require(stream->state[0] == reinterpret_cast(bytes.data()) && stream->state[1] == 8, "position calls changed the buffer"); + RequireInvalidArgument([] { EOSWS_Memory_nid_postfix(nullptr); }, "end of stream accepted a null stream"); + RequireInvalidArgument([&] { ReadWS_Memory_nid_postfix(nullptr, bytes.data(), 1); }, "read accepted a null stream"); + RequireInvalidArgument([&] { WriteWS_Memory_nid_postfix(nullptr, bytes.data(), 1); }, "write accepted a null stream"); + RequireInvalidArgument([] { SetPosWS_Memory_nid_postfix(nullptr, 0); }, "set position accepted a null stream"); + RequireInvalidArgument([&] { GetPosWS_Memory_nid_postfix(nullptr, &position); }, "get position accepted a null stream"); + RequireInvalidArgument([&] { GetPosWS_Memory_nid_postfix(stream, nullptr); }, "get position accepted a null result"); + CloseMemory(stream); +} + +void MemoryBitIo() { + std::vector source(2 * PacketSize + 100); + for (std::size_t i = 0; i < source.size(); ++i) source[i] = static_cast(i * 31 + (i >> 8) * 13); + auto* stream = MemoryStream(source.data(), source.size()); + std::fill(ring.begin(), ring.end(), 0xcc); + Jxr::BitIo io{7, 19, 13, ~8192, ring.data(), ring.data() + PacketSize, stream, PacketSize}; + Require(readIS_nid_postfix(nullptr, &io) == 0 && io.referenceOffset == 2 * PacketSize && io.start == ring.data() + PacketSize, "memory packet read did not advance"); + Require(stream->state[2] == 2 * PacketSize && std::equal(ring.begin(), ring.begin() + PacketSize, source.begin() + PacketSize), "memory packet read data differs"); + Require(Filled(ring.data() + PacketSize, ring.data() + ring.size(), 0xcc) && std::memcmp(&io.shadow, source.data() + PacketSize, sizeof(io.shadow)) == 0, "memory packet read changed the other half or shadow"); + io.current = ring.data(); + Require(readIS_nid_postfix(nullptr, &io) == 0 && io.referenceOffset == 3 * PacketSize && io.start == ring.data(), "clamped memory packet read did not advance"); + Require(stream->state[2] == 2 * PacketSize + 100 && std::equal(ring.begin() + PacketSize, ring.begin() + PacketSize + 100, source.begin() + 2 * PacketSize), "clamped memory packet read data differs"); + Require(Filled(ring.data() + PacketSize + 100, ring.data() + ring.size(), 0xcc) && std::equal(ring.begin(), ring.begin() + PacketSize, source.begin() + PacketSize), "clamped memory packet read changed bytes past the clamp"); + CloseMemory(stream); + + for (std::size_t i = 0; i < ring.size(); ++i) ring[i] = static_cast(i * 23 + (i >> 8) * 5); + std::vector target(PacketSize + 100, 0xcc); + stream = MemoryStream(target.data(), target.size()); + io = {7, 19, 13, ~8192, ring.data(), ring.data() + PacketSize, stream, 0}; + Require(writeIS_nid_postfix(nullptr, &io) == 0 && io.start == ring.data() + PacketSize && stream->state[2] == PacketSize, "memory packet write did not advance"); + Require(std::equal(ring.begin(), ring.begin() + PacketSize, target.begin()) && Filled(target.data() + PacketSize, target.data() + target.size(), 0xcc), "memory packet write data differs"); + io.current = ring.data(); + Require(writeIS_nid_postfix(nullptr, &io) == -103 && io.start == ring.data() + PacketSize && stream->state[2] == PacketSize, "overflowing memory packet write advanced"); + Require(std::equal(ring.begin(), ring.begin() + PacketSize, target.begin()) && Filled(target.data() + PacketSize, target.data() + target.size(), 0xcc), "overflowing memory packet write changed the buffer"); + CloseMemory(stream); +} + } int main() { + void* api[10]{}; + ApplicationHeapRegister_nid_no_patch(api); Jxr::Stream stream{}; stream.read = Read; stream.write = Write; @@ -302,5 +441,11 @@ int main() { CallbackMutation(stream); NullState(stream); GuestMapping(stream); + Allocation(); + MemoryCreate(); + MemoryRead(); + MemoryWrite(); + MemoryPosition(); + MemoryBitIo(); return 0; } diff --git a/docs/dev/debt/libSceJxr.md b/docs/dev/debt/libSceJxr.md index 249c82055a..ca4d62cde0 100644 --- a/docs/dev/debt/libSceJxr.md +++ b/docs/dev/debt/libSceJxr.md @@ -10,6 +10,8 @@ - [PKFree](../../../core/libs/prx/libSceJxr/Export.cpp) (libSceJxr) follows Microsoft's jxrlib [JXRGlue.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/jxrgluelib/JXRGlue.c#L53-L62), whose four exported names the module shares: a non-null `ppv` has `*ppv` released with the guest `free` and set to null, and the result is always `WMP_errSuccess` (0) as a 64-bit `ERR` (`long`). That the console's module is built from jxrlib, and that its `PKFree` releases through libc's `free`, is inferred from the export names and not checked against the console's code. Closes when: the console's `PKFree` is disassembled or measured on a PS5 +- [WMPAlloc, WMPFree and the memory stream](../../../core/libs/prx/libSceJxr/Export.cpp) (`CreateWS_Memory`, `EOSWS_Memory`, `ReadWS_Memory`, `WriteWS_Memory`, `SetPosWS_Memory`, `GetPosWS_Memory`): console code and layouts are unverified. They follow pinned jxrlib [strcodec.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L246-L447) on the LP64 `WMPStream` layout, allocating with guest `calloc(1, cb)` and `free`; that the module is built from jxrlib is inferred from the export names. The `Close` callback is jxrlib's `CloseWS_Memory`, which the export inventory lacks. Where jxrlib dereferences null, `std::invalid_argument` is thrown: a null `ppv`, stream or `poffPos`, and a null buffer when a read or write copies at least one byte. Closes when: the console's functions are disassembled or measured on a PS5 + - [readIS and writeIS](../../../core/libs/prx/libSceJxr/Export.cpp) use the non-ARM, LP64 `BitIOInfo` and `WMPStream` ABI and packet algorithm from [Microsoft's JPEG XR reference implementation](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L1090-L1177). Console structure layouts and execution remain unverified. Profiling callbacks are omitted as in the reference Unix build with `DISABLE_PERF_MEASUREMENT`; the codec argument is unused. Where the reference uses an invalid pointer, `std::invalid_argument` is thrown instead: a null bit state, stream or callback, a read-only read packet, and a null packet when a read copies its shadow or a successful write offsets it; a null packet still reaches the callback first. No codec is instantiated by these helpers; callers must supply the reference ring buffer and guest-ABI stream callbacks. -- The 132 additional exports in [Export.cpp](../../../core/libs/prx/libSceJxr/Export.cpp) are present in the pinned firmware 13.60 `libSceJxr` export inventory. Their parameter lists, return types and behavior are unverified. Parameterless `int` declarations are import-resolution placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. The `_nid_postfix` aliases keep the guest bindings distinct from private native helpers. No runtime functionality is implemented. +- The 124 additional exports in [Export.cpp](../../../core/libs/prx/libSceJxr/Export.cpp) are present in the pinned firmware 13.60 `libSceJxr` export inventory. Their parameter lists, return types and behavior are unverified. Parameterless `int` declarations are import-resolution placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. The `_nid_postfix` aliases keep the guest bindings distinct from private native helpers. No runtime functionality is implemented. From ecc801b18f20ec840c547fd78d7879559bc1b95e Mon Sep 17 00:00:00 2001 From: slopgobblercobbler Date: Sun, 1 Nov 2026 17:45:27 +0100 Subject: [PATCH 203/612] test(libkernel): cover null pointer in nanotime --- core/libs/tests/GuestFreebsdClocks.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/libs/tests/GuestFreebsdClocks.cpp b/core/libs/tests/GuestFreebsdClocks.cpp index 3ec397c5c3..aea6f837b2 100644 --- a/core/libs/tests/GuestFreebsdClocks.cpp +++ b/core/libs/tests/GuestFreebsdClocks.cpp @@ -70,10 +70,12 @@ static void UnsupportedResolutionStillThrows() { } static void OmittedTimeIsFaultAfterClockValidation() { + constexpr int fault = static_cast(0x8002000eu); for (const int clockId : {GUEST_CLOCK_REALTIME, GUEST_CLOCK_VIRTUAL, GUEST_CLOCK_PROF, GUEST_CLOCK_MONOTONIC, GUEST_CLOCK_REALTIME_PRECISE, GUEST_CLOCK_SECOND, GUEST_CLOCK_THREAD_CPUTIME_ID, GUEST_CLOCK_PROCESS_CPUTIME_ID}) { *__error_nid_postfix() = 0; Require(clock_gettime_nid_postfix(clockId, nullptr) == -1 && *__error_nid_postfix() == 14); + Require(sceKernelClockGettime(clockId, nullptr) == fault); } bool rejected = false; try { @@ -83,6 +85,7 @@ static void OmittedTimeIsFaultAfterClockValidation() { rejected = true; } Require(rejected); + Require(sceKernelClockGettime(3, nullptr) == fault); } static KernelTimespec Read(int clockId) { From 6834476a85042d915ec184e5e2ecf763901c48fa Mon Sep 17 00:00:00 2001 From: Daniel Horn <73882221+danielhorngt@users.noreply.github.com> Date: Sun, 1 Nov 2026 16:46:46 +0000 Subject: [PATCH 204/612] docs(libSceAgcDriver): record LOD bias cancellation with a shader bias Vulkan clamps the sum of sampler and shader bias, so a shader bias of the opposite sign cancels against the clamped S# bias, not the guest one. Co-Authored-By: Claude Opus 5.5 --- docs/dev/debt/libSceAgcDriver.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index 65d50bb1f9..89ce75aa43 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -189,7 +189,7 @@ - [Presentation under the GPU mutex](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) (libSceAgcDriver) - on a device whose presentation family has no queue left after the main and settle queues, such as RADV, lavapipe and Intel's Windows driver (Arc Graphics on Meteor Lake, 101.9150), which expose a single graphics queue, `vkQueuePresentKHR` still runs on the main queue under `GuestMemory::GpuMutex`, so the draw committer and the queue workers wait for as long as the present blocks. How long that is on those drivers was not measured; on NVIDIA in FIFO mode it is about 6 ms per present. AMD's Windows driver (Radeon 860M) has one too: a Quake II port's trace had 0.3 ms presents (max 3.5), no `GpuMutex.wait.*` behind them. A device with a free queue in that family presents on it under its own mutex, after the GPU mutex is released. Closes when: traces on RADV, lavapipe and Intel's Windows driver also show no `GpuMutex.wait.*` behind `Vulkan.QueuePresent`. - [Unnormalized S# coordinates](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) - `FORCE_UNNORMALIZED` uses a Vulkan `unnormalizedCoordinates` sampler, so the S# LOD range, LOD bias and mip filter are not applied, which is exact only on a one-level view. - An [unnormalized S#](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) maps clamp mode 0 (repeat) on X and Y to clamp-to-edge as required by Vulkan unnormalized coordinates, and assigns the magnification filter when minification and magnification filters differ (since unnormalized coordinates sample at LOD 0). Anisotropy, MC_COORD_TRUNC and other clamp modes still throw. Secondary LOD bias (LOD_BIAS_SEC, bits [19:14]) is accepted and ignored, matching RDNA2 hardware measurements across explicit and implicit samples. High on Life (PPSA15388, #4736) uses secondary LOD bias and unnormalized coordinates with clamp mode 0 and differing min/mag filters during home base and city rendering. -- [S# LOD bias](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp) (libSceAgcDriver) beyond `maxSamplerLodBias` is clamped to it. The S# field reaches -32 to +31.99, while Vulkan refuses a larger `mipLodBias` (15 on an RTX 4080, 15.99 on Intel UHD 770) and clamps a larger sum with a shader bias. The sampled level differs from the console only where the implicit LOD plus the clamped bias does not reach the level the guest bias selects: at a limit of 15, a positive bias on a view of L levels reads a lower level than the console when the texture is magnified more than 2^(16 - L) times, and a negative bias reads a higher level only when it is minified more than 2^15 times. +- [S# LOD bias](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp) (libSceAgcDriver) beyond `maxSamplerLodBias` is clamped to it when the sampler is created, and the [shader bias](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) stays unchanged. The S# field reaches -32 to +31.99, while Vulkan refuses a larger `mipLodBias` (15 on an RTX 4080, 15.99 on Intel UHD 770) and clamps the sum of sampler and shader bias to the same limit. A shader bias of the opposite sign therefore cancels against the clamped value, not the guest one: at an implicit LOD near 2.1 on an RTX 4080, S# +16 with shader bias -14 reads level 3 where the sum selects level 4, and S# +31.67 with shader bias -29 reads level 0 where the sum selects level 5. Not measured on a console. Without a shader bias, a positive S# bias reads a lower level than the sum selects when the texture on a view of L levels is magnified more than 2^(16 - L) times at a limit of 15, and a negative one reads a higher level only when it is minified more than 2^15 times. - [TRUNC_COORD](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) is not applied, with normalized or unnormalized coordinates: the texel a point sample reads and the bilinear weights follow the host sampler's rounding. On RDNA 2 (RX 6700 XT), an S# with TRUNC_COORD 0 rounds the exact scaled coordinate to 8 fractional bits, ties to even, and one with TRUNC_COORD 1 truncates it, for point and bilinear filters. AMD's Windows driver (32.0.21045.5002) truncates for nearest samplers and rounds for linear ones. Under it, a TRUNC_COORD 0 point sample at most 1/512 texel below a texel edge reads the texel below the edge where the console reads the one above, and `k / width` computed in f32 lands there for about half of all k. A TRUNC_COORD 1 bilinear weight can be 1/256 above the console's. Other host drivers are not measured. - [Background link-time-optimized relink of library pipelines](../../../core/libs/prx/libSceAgcDriver/Execution/include/PipelineLinkOptimization.hpp) (libSceAgcDriver) is skipped on every GPU under NVIDIA's proprietary Vulkan driver (`VK_DRIVER_ID_NVIDIA_PROPRIETARY`), so those pipelines keep their fast-linked form. The optimized link loses the device when bound on an RTX 3080 (Ampere, #2866) and an RTX 4060 (Ada, #3434); an RTX 5060 and an RTX 5080 (Blackwell) on the same driver do not, so the scope is wider than the evidence. No standard Vulkan query tells the architecture apart, and the driver version that fixes it is unknown. `APS5_NO_PIPELINE_LTO` disables the relink on every driver. - [8_SRGB and 8_8_SRGB textures](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureFormat.cpp) (libSceAgcDriver) that the device samples as R8_SRGB or R8G8_SRGB use its own decode, which need not match gfx1035: NVIDIA's R8_SRGB is one bf16 step off in 29 of 256 codes. A device that cannot sample them gets an RGBA8 sRGB copy instead (a blit of the R8 or RG8 texels, refreshed with the storage image's version), whose decode is likewise the device's; 3D textures and storage images that are not single-depth and format-matched are copied from their snapshot instead. From b2d817a3d37662db052dae1253ce5d16c0c9de29 Mon Sep 17 00:00:00 2001 From: hyvvs Date: Sun, 1 Nov 2026 19:32:04 +0100 Subject: [PATCH 205/612] fix(libcairo): keep font options storage private --- core/libs/prx/libcairo/FontOptions.cpp | 34 ++++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/core/libs/prx/libcairo/FontOptions.cpp b/core/libs/prx/libcairo/FontOptions.cpp index b3c1428ef3..825808f949 100644 --- a/core/libs/prx/libcairo/FontOptions.cpp +++ b/core/libs/prx/libcairo/FontOptions.cpp @@ -1,23 +1,31 @@ #include "prx/libcairo/include/InventoryExports.hpp" #include -struct cairo_font_options_t { +namespace { + +struct CairoFontOptionsStorage { cairo_antialias_t antialias; cairo_subpixel_order_t subpixel_order; cairo_hint_style_t hint_style; cairo_hint_metrics_t hint_metrics; }; -namespace { +const CairoFontOptionsStorage nilOptions{}; -const cairo_font_options_t nilOptions{}; +CairoFontOptionsStorage* Storage(cairo_font_options_t* options) { + return reinterpret_cast(options); +} + +const CairoFontOptionsStorage* Storage(const cairo_font_options_t* options) { + return reinterpret_cast(options); +} bool Invalid(const cairo_font_options_t* options) { - return options == nullptr || options == &nilOptions; + return options == nullptr || Storage(options) == &nilOptions; } cairo_font_options_t* Nil() { - return const_cast(&nilOptions); + return reinterpret_cast(const_cast(&nilOptions)); } } @@ -25,14 +33,14 @@ cairo_font_options_t* Nil() { extern "C" { cairo_font_options_t* APS5_VABI cairo_font_options_create() { - auto* options = static_cast(std::calloc(1, sizeof(cairo_font_options_t))); - return options != nullptr ? options : Nil(); + auto* options = std::calloc(1, sizeof(CairoFontOptionsStorage)); + return options != nullptr ? static_cast(options) : Nil(); } cairo_font_options_t* APS5_VABI cairo_font_options_copy(const cairo_font_options_t* original) { if (Invalid(original)) return Nil(); auto* options = cairo_font_options_create(); - if (options != Nil()) *options = *original; + if (options != Nil()) *Storage(options) = *Storage(original); return options; } @@ -41,23 +49,23 @@ void APS5_VABI cairo_font_options_destroy(cairo_font_options_t* options) { } void APS5_VABI cairo_font_options_set_antialias(cairo_font_options_t* options, cairo_antialias_t antialias) { - if (!Invalid(options)) options->antialias = antialias; + if (!Invalid(options)) Storage(options)->antialias = antialias; } cairo_antialias_t APS5_VABI cairo_font_options_get_antialias(const cairo_font_options_t* options) { - return Invalid(options) ? CAIRO_ANTIALIAS_DEFAULT : options->antialias; + return Invalid(options) ? CAIRO_ANTIALIAS_DEFAULT : Storage(options)->antialias; } void APS5_VABI cairo_font_options_set_hint_style(cairo_font_options_t* options, cairo_hint_style_t hint_style) { - if (!Invalid(options)) options->hint_style = hint_style; + if (!Invalid(options)) Storage(options)->hint_style = hint_style; } void APS5_VABI cairo_font_options_set_hint_metrics(cairo_font_options_t* options, cairo_hint_metrics_t hint_metrics) { - if (!Invalid(options)) options->hint_metrics = hint_metrics; + if (!Invalid(options)) Storage(options)->hint_metrics = hint_metrics; } void APS5_VABI cairo_font_options_set_subpixel_order(cairo_font_options_t* options, cairo_subpixel_order_t subpixel_order) { - if (!Invalid(options)) options->subpixel_order = subpixel_order; + if (!Invalid(options)) Storage(options)->subpixel_order = subpixel_order; } } From 5cc848799976f01d8eeaa94a0fe93415428cb252 Mon Sep 17 00:00:00 2001 From: xdCloudy <52116030+xdCloudy@users.noreply.github.com> Date: Sun, 1 Nov 2026 19:42:51 +0000 Subject: [PATCH 206/612] fix(libSceUserService): validate group and home queries --- core/libs/prx/libSceUserService/Export.cpp | 224 +++++++++++++++--- .../libSceUserService/InventoryExports.cpp | 13 +- .../prx/libSceUserService/UserService.hpp | 22 +- core/libs/tests/GuestUserService.cpp | 20 ++ docs/dev/debt/libSceUserService.md | 16 +- 5 files changed, 245 insertions(+), 50 deletions(-) diff --git a/core/libs/prx/libSceUserService/Export.cpp b/core/libs/prx/libSceUserService/Export.cpp index 6c1318958d..69ca83f105 100644 --- a/core/libs/prx/libSceUserService/Export.cpp +++ b/core/libs/prx/libSceUserService/Export.cpp @@ -1,3 +1,7 @@ +#include +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/include/Api.hpp" #include "prx/libSceSystemService/include/SystemService.hpp" #include #include @@ -95,6 +99,12 @@ static int ReportKeyboardSetting(int user_id, int32_t* type, int32_t (*value)()) *type = value(); return USER_SERVICE_OK; } +bool UserGroupSlotExists(int index) { + char path[64]; + std::snprintf(path, sizeof(path), "/system_data/priv/home/user_list.dat.%d", index); + FileStat status{}; + return sceKernelStat(path, &status) == 0; +} } extern "C" { @@ -429,9 +439,31 @@ int APS5_VABI sceUserServiceGetIsHrtfEnabled(void) { return 0; } -int APS5_VABI sceUserServiceGetCurrentUserGroupIndex(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetCurrentUserGroupIndex(std::int32_t* index) { + if (index == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + *index = 0; + const int descriptor = sceKernelOpen("/system_data/priv/home/user_list.dat", 0, 0); + if (descriptor < 0) { + return descriptor; + } + const std::int64_t seekResult = sceKernelLseek(descriptor, 0x44, 0); + if (seekResult != 0x44) { + sceKernelClose(descriptor); + return static_cast(seekResult); + } + std::int32_t storedIndex{}; + const std::int64_t bytesRead = sceKernelRead(descriptor, &storedIndex, sizeof(storedIndex)); + sceKernelClose(descriptor); + if (bytesRead < 0) { + return static_cast(bytesRead); + } + if (bytesRead != sizeof(storedIndex)) { + return SCE_KERNEL_ERROR_EIO; + } + *index = storedIndex; + return USER_SERVICE_OK; } int APS5_VABI sceUserServiceGetPbtcThursdayHoursEnd(void) { @@ -449,9 +481,23 @@ int APS5_VABI sceUserServiceSetSystemLoggerHashedAccountIdTtl(void) { return 0; } -int APS5_VABI sceUserServiceGetRegisteredHomeUserIdList(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetRegisteredHomeUserIdList(std::int32_t* userIds, std::int32_t* count) { + if (userIds == nullptr || count == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + UserServiceRegisteredUserIdList registeredUsers{}; + const int result = sceUserServiceGetRegisteredUserIdList(®isteredUsers); + if (result != USER_SERVICE_OK) { + return result; + } + *count = 0; + for (std::size_t index = 0; index < sizeof(registeredUsers.user_id) / sizeof(registeredUsers.user_id[0]); ++index) { + userIds[index] = registeredUsers.user_id[index]; + if (userIds[index] != USER_SERVICE_USER_ID_INVALID) { + ++*count; + } + } + return USER_SERVICE_OK; } int APS5_VABI sceUserServiceSetVolumeForGenericUSB(void) { @@ -619,9 +665,35 @@ int APS5_VABI sceUserServiceSetKeyRepeatSpeed(void) { return 0; } -int APS5_VABI sceUserServiceSetUserGroupName(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceSetUserGroupName(int groupIndex, const char* name) { + if (name == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + if (!UserGroupSlotExists(groupIndex)) { + int nextGroupIndex = 0; + while (nextGroupIndex < 128 && UserGroupSlotExists(nextGroupIndex)) { + ++nextGroupIndex; + } + if (groupIndex != nextGroupIndex) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + } + const std::size_t nameSize = std::strlen(name); + if (nameSize > 128) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + char path[64]; + std::snprintf(path, sizeof(path), "/system_data/priv/home/user_group_name.dat.%d", groupIndex); + const int descriptor = sceKernelOpen(path, 0x601, 0x180); + if (descriptor < 0) { + return descriptor; + } + const std::int64_t bytesWritten = sceKernelWrite(descriptor, name, nameSize); + sceKernelClose(descriptor); + if (bytesWritten < 0) { + return static_cast(bytesWritten); + } + return USER_SERVICE_OK; } int APS5_VABI sceUserServiceGetParentalDvdRegion(void) { @@ -754,9 +826,29 @@ int APS5_VABI sceUserServiceSetHearingAssistUsePreset(void) { return 0; } -int APS5_VABI sceUserServiceGetUserGroupName(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetUserGroupName(int groupIndex, char* name, std::size_t size) { + if (name == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + if (size < 129) { + return USER_SERVICE_ERROR_BUFFER_TOO_SHORT; + } + if (size != 129) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + std::memset(name, 0, size); + char path[64]; + std::snprintf(path, sizeof(path), "/system_data/priv/home/user_group_name.dat.%d", groupIndex); + const int descriptor = sceKernelOpen(path, 0, 0); + if (descriptor >= 0) { + const std::int64_t bytesRead = sceKernelRead(descriptor, name, 128); + sceKernelClose(descriptor); + if (bytesRead > 0) { + return USER_SERVICE_OK; + } + } + std::snprintf(name, size, "User Group %d", groupIndex + 1); + return USER_SERVICE_OK; } int APS5_VABI sceUserServiceSetAutoLoginEnabled(void) { @@ -954,9 +1046,12 @@ int APS5_VABI sceUserServiceSetNotificationSettings_2(void) { return 0; } -int APS5_VABI sceUserServiceGetHomeDirectory(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetHomeDirectory(std::uint32_t user_id, char* path, std::size_t size) { + if (path == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + const int result = std::snprintf(path, size, "/user/home/%08x", user_id); + return result >= 0 && static_cast(result) < size ? USER_SERVICE_OK : USER_SERVICE_ERROR_BUFFER_TOO_SHORT; } int APS5_VABI sceUserServiceSetPbtcSundayDuration(void) { @@ -1959,9 +2054,21 @@ int APS5_VABI sceUserServiceSetPsCloudFeedbackTime(void) { return 0; } -int APS5_VABI sceUserServiceSetUserGroupIndex(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceSetUserGroupIndex(int index) { + if (index != -1 && (index < 0 || index > 128 || !UserGroupSlotExists(index))) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + const int descriptor = sceKernelOpen("/system_data/priv/home/user_list.dat", 2, 0); + if (descriptor < 0) { + return descriptor; + } + const std::int32_t storedIndex = index == -1 ? 0x7fffffff : index; + const std::int64_t bytesWritten = sceKernelPwrite(descriptor, &storedIndex, sizeof(storedIndex), 0x44); + sceKernelClose(descriptor); + if (bytesWritten < 0) { + return static_cast(bytesWritten); + } + return bytesWritten == sizeof(storedIndex) ? USER_SERVICE_OK : SCE_KERNEL_ERROR_EIO; } int APS5_VABI sceUserServiceSetPartyMuteListA(void) { @@ -2229,9 +2336,19 @@ int APS5_VABI sceUserServiceGetSaveDataAutoSync(void) { return 0; } -int APS5_VABI sceUserServiceGetDefaultNewUserGroupName(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetDefaultNewUserGroupName(char* name, std::size_t size) { + if (name == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + if (size < 129) { + return USER_SERVICE_ERROR_BUFFER_TOO_SHORT; + } + int groupIndex = 0; + while (groupIndex < 128 && UserGroupSlotExists(groupIndex)) { + ++groupIndex; + } + std::snprintf(name, size, "User Group %d", groupIndex + 1); + return USER_SERVICE_OK; } int APS5_VABI sceUserServiceGetHmd2Brightness(void) { @@ -2344,9 +2461,12 @@ int APS5_VABI sceUserServiceGetDeletedUserInfo(void) { return 0; } -int APS5_VABI sceUserServiceGetSecureHomeDirectory(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetSecureHomeDirectory(std::uint32_t user_id, char* path, std::size_t size) { + if (path == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + const int result = std::snprintf(path, size, "/%s/priv/home/%08x", sceKernelGetFsSandboxRandomWord(), user_id); + return result >= 0 && static_cast(result) < size ? USER_SERVICE_OK : USER_SERVICE_ERROR_BUFFER_TOO_SHORT; } int APS5_VABI sceUserServiceGetHearingAssistAdjustmentLevelRight(void) { @@ -2559,9 +2679,46 @@ int APS5_VABI sceUserServiceSetGlsMessageFilterLevel(void) { return 0; } -int APS5_VABI sceUserServiceGetUserGroupNameList(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetUserGroupNameList(int groupIndex, char* nameList, std::size_t size, + std::int32_t* count) { + if (nameList == nullptr || count == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + if (size < 0x110) { + return USER_SERVICE_ERROR_BUFFER_TOO_SHORT; + } + if (size != 0x110 || groupIndex < -1 || groupIndex > 128 || + (groupIndex >= 0 && !UserGroupSlotExists(groupIndex))) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + char path[64]; + if (groupIndex < 0) { + std::snprintf(path, sizeof(path), "/system_data/priv/home/user_list.dat"); + } else { + std::snprintf(path, sizeof(path), "/system_data/priv/home/user_list.dat.%d", groupIndex); + } + std::array userIds; + userIds.fill(USER_SERVICE_USER_ID_INVALID); + const int descriptor = sceKernelOpen(path, 0, 0); + if (descriptor < 0) { + return descriptor; + } + const std::int64_t bytesRead = sceKernelRead(descriptor, userIds.data(), 0x40); + sceKernelClose(descriptor); + if (bytesRead < 0) { + return static_cast(bytesRead); + } + *count = 0; + for (std::size_t index = 0; index < userIds.size() && userIds[index] != USER_SERVICE_USER_ID_INVALID; ++index) { + char userName[USER_SERVICE_MAX_USER_NAME_LENGTH + 1]{}; + const int result = sceUserServiceGetUserName(userIds[index], userName, sizeof(userName)); + if (result != USER_SERVICE_OK) { + return result; + } + std::memcpy(nameList + index * sizeof(userName), userName, sizeof(userName)); + ++*count; + } + return USER_SERVICE_OK; } int APS5_VABI sceUserServiceSetVoiceAgentEnableFeedback(void) { @@ -2914,9 +3071,16 @@ int APS5_VABI sceUserServiceSetNotificationBehavior(void) { return 0; } -int APS5_VABI sceUserServiceGetUserGroupNum(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetUserGroupNum(std::int32_t* count) { + if (count == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + int groupCount = 0; + while (groupCount < 128 && UserGroupSlotExists(groupCount)) { + ++groupCount; + } + *count = groupCount; + return USER_SERVICE_OK; } int APS5_VABI sceUserServiceSetPartySettingFlags(void) { diff --git a/core/libs/prx/libSceUserService/InventoryExports.cpp b/core/libs/prx/libSceUserService/InventoryExports.cpp index 0a45386094..5ce4fb1426 100644 --- a/core/libs/prx/libSceUserService/InventoryExports.cpp +++ b/core/libs/prx/libSceUserService/InventoryExports.cpp @@ -1,4 +1,6 @@ #include "SceTypes.hpp" +#include "prx/libSceUserService/UserService.hpp" +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/general/ExportMacros.hpp" @@ -84,8 +86,15 @@ int APS5_VABI libSceUserServiceUnknown_DL6cKVeISqs_nid_no_patch() { } APS5_EXPORT("DL6cKVeISqs", libSceUserServiceUnknown_DL6cKVeISqs_nid_no_patch); -int APS5_VABI libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(std::uint32_t userId, char* path, std::size_t size) { + if (path == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + if (size < 0x25) { + return USER_SERVICE_ERROR_BUFFER_TOO_SHORT; + } + std::snprintf(path, size, "/%s/eap/rodata/home/%08x", sceKernelGetFsSandboxRandomWord(), userId); + return USER_SERVICE_OK; } APS5_EXPORT("ErTMkmSho4E", libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch); diff --git a/core/libs/prx/libSceUserService/UserService.hpp b/core/libs/prx/libSceUserService/UserService.hpp index 5a0f74d2cb..dae01264ca 100644 --- a/core/libs/prx/libSceUserService/UserService.hpp +++ b/core/libs/prx/libSceUserService/UserService.hpp @@ -74,11 +74,11 @@ int APS5_VABI sceUserServiceSetJapaneseInputType(void); int APS5_VABI sceUserServiceSetGlsBroadcastersComment(void); int APS5_VABI sceUserServiceSetGlsCameraTransparency(void); int APS5_VABI sceUserServiceGetIsHrtfEnabled(void); -int APS5_VABI sceUserServiceGetCurrentUserGroupIndex(void); +int APS5_VABI sceUserServiceGetCurrentUserGroupIndex(std::int32_t* index); int APS5_VABI sceUserServiceGetPbtcThursdayHoursEnd(void); int APS5_VABI sceUserServiceGetPbtcFridayHoursStart(void); int APS5_VABI sceUserServiceSetSystemLoggerHashedAccountIdTtl(void); -int APS5_VABI sceUserServiceGetRegisteredHomeUserIdList(void); +int APS5_VABI sceUserServiceGetRegisteredHomeUserIdList(std::int32_t* userIds, std::int32_t* count); int APS5_VABI sceUserServiceSetVolumeForGenericUSB(void); int APS5_VABI sceUserServiceSetControlCenterFunctionControlSettings(void); int APS5_VABI sceUserServiceGetAudioFocus(void); @@ -112,7 +112,7 @@ int APS5_VABI sceUserServiceGetGlsHintFlag(void); int APS5_VABI sceUserServiceSetIsFakePremium(void); int APS5_VABI sceUserServiceGetAccessibilityKeyremapDataAston(void); int APS5_VABI sceUserServiceSetKeyRepeatSpeed(void); -int APS5_VABI sceUserServiceSetUserGroupName(void); +int APS5_VABI sceUserServiceSetUserGroupName(int groupIndex, const char* name); int APS5_VABI sceUserServiceGetParentalDvdRegion(void); int APS5_VABI sceUserServiceSetGlsCameraDepthLevel(void); int APS5_VABI sceUserServiceGetParentalContentControl(void); @@ -139,7 +139,7 @@ int APS5_VABI sceUserServiceGetEventCalendarType(void); int APS5_VABI sceUserServiceSetPbtcSaturdayHoursEnd(void); int APS5_VABI sceUserServiceSetSystemLoggerHashedAccountIdParam(void); int APS5_VABI sceUserServiceSetHearingAssistUsePreset(void); -int APS5_VABI sceUserServiceGetUserGroupName(void); +int APS5_VABI sceUserServiceGetUserGroupName(int groupIndex, char* name, std::size_t size); int APS5_VABI sceUserServiceSetAutoLoginEnabled(void); int APS5_VABI sceUserServiceGetIPDLeft(void); int APS5_VABI sceUserServiceSetHintShowFlag(void); @@ -179,7 +179,7 @@ int APS5_VABI sceUserServiceSetHmd2Vibration(void); int APS5_VABI sceUserServiceGetGlsLiveQuality2(void); int APS5_VABI sceUserServiceSetIsQuickSignup(void); int APS5_VABI sceUserServiceSetNotificationSettings_2(void); -int APS5_VABI sceUserServiceGetHomeDirectory(void); +int APS5_VABI sceUserServiceGetHomeDirectory(std::uint32_t userId, char* path, std::size_t size); int APS5_VABI sceUserServiceSetPbtcSundayDuration(void); int APS5_VABI sceUserServiceSetIPDLeft(void); int APS5_VABI sceUserServiceGetKratosPrimaryUser(void); @@ -380,7 +380,7 @@ int APS5_VABI sceUserServiceSetGlsIsCameraHidden(void); int APS5_VABI sceUserServiceSetUserStatus(void); int APS5_VABI sceUserServiceGetEventFilterTeamEvent(void); int APS5_VABI sceUserServiceSetPsCloudFeedbackTime(void); -int APS5_VABI sceUserServiceSetUserGroupIndex(void); +int APS5_VABI sceUserServiceSetUserGroupIndex(int index); int APS5_VABI sceUserServiceSetPartyMuteListA(void); int APS5_VABI sceUserServiceSetNpLanguageCode2(void); int APS5_VABI sceUserServiceSetShareStatus2(void); @@ -434,7 +434,7 @@ int APS5_VABI sceUserServiceSetEventSortTitle(void); int APS5_VABI sceUserServiceSetNpOnlineId(void); int APS5_VABI sceUserServiceSetA11yControllerBName(void); int APS5_VABI sceUserServiceGetSaveDataAutoSync(void); -int APS5_VABI sceUserServiceGetDefaultNewUserGroupName(void); +int APS5_VABI sceUserServiceGetDefaultNewUserGroupName(char* name, std::size_t size); int APS5_VABI sceUserServiceGetHmd2Brightness(void); int APS5_VABI sceUserServiceGetHoldAudioOutDevice(void); int APS5_VABI sceUserServiceSetVibrationStrength(void); @@ -457,7 +457,7 @@ int APS5_VABI sceUserServiceSetGlsCameraEliminationLevel(void); int APS5_VABI sceUserServiceGetVibrationStrength(void); int APS5_VABI sceUserServiceGetNpAccountUpgradeFlag(void); int APS5_VABI sceUserServiceGetDeletedUserInfo(void); -int APS5_VABI sceUserServiceGetSecureHomeDirectory(void); +int APS5_VABI sceUserServiceGetSecureHomeDirectory(std::uint32_t userId, char* path, std::size_t size); int APS5_VABI sceUserServiceGetHearingAssistAdjustmentLevelRight(void); int APS5_VABI sceUserServiceGetPlatformPrivacyWs1Internal(void); int APS5_VABI sceUserServiceGetGlsIsRecDisabled(void); @@ -500,7 +500,8 @@ int APS5_VABI sceUserServiceSetGlsLiveQuality2(void); int APS5_VABI sceUserServiceGetHmd2GazeActivate(void); int APS5_VABI sceUserServiceSetGlsCameraPosition(void); int APS5_VABI sceUserServiceSetGlsMessageFilterLevel(void); -int APS5_VABI sceUserServiceGetUserGroupNameList(void); +int APS5_VABI sceUserServiceGetUserGroupNameList(int groupIndex, char* nameList, std::size_t size, + std::int32_t* count); int APS5_VABI sceUserServiceSetVoiceAgentEnableFeedback(void); int APS5_VABI sceUserServiceGetGazeActivate(void); int APS5_VABI sceUserServiceGetGlsIsTwitterEnabled(void); @@ -571,11 +572,12 @@ int APS5_VABI sceUserServiceSetPbtcThursdayHoursStart(void); int APS5_VABI sceUserServiceSetIsHrtfEnabled(void); int APS5_VABI sceUserServiceGetParentalGameAgeLevel(void); int APS5_VABI sceUserServiceSetNotificationBehavior(void); -int APS5_VABI sceUserServiceGetUserGroupNum(void); +int APS5_VABI sceUserServiceGetUserGroupNum(std::int32_t* count); int APS5_VABI sceUserServiceSetPartySettingFlags(void); int APS5_VABI sceUserServiceGetGlsCameraEffect(void); int APS5_VABI sceUserServiceGetPbtcSundayHoursEnd(void); int APS5_VABI sceUserServiceGetIsSpartacusEnabled(void); +int APS5_VABI libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(std::uint32_t userId, char* path, std::size_t size); } #endif diff --git a/core/libs/tests/GuestUserService.cpp b/core/libs/tests/GuestUserService.cpp index 852b8d6da0..6801293672 100644 --- a/core/libs/tests/GuestUserService.cpp +++ b/core/libs/tests/GuestUserService.cpp @@ -193,6 +193,26 @@ int main() { Require(sceUserServiceGetUserColor(USER_SERVICE_INITIAL_USER_ID + 1, &color) == USER_SERVICE_OK); Require(color == 1); + char homePath[64]{}; + Require(sceUserServiceGetHomeDirectory(USER_SERVICE_INITIAL_USER_ID, nullptr, sizeof(homePath)) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetHomeDirectory(USER_SERVICE_INITIAL_USER_ID, homePath, 1) == USER_SERVICE_ERROR_BUFFER_TOO_SHORT); + Require(sceUserServiceGetHomeDirectory(USER_SERVICE_INITIAL_USER_ID, homePath, sizeof(homePath)) == USER_SERVICE_OK); + Require(std::strcmp(homePath, "/user/home/10000000") == 0); + Require(sceUserServiceGetSecureHomeDirectory(USER_SERVICE_INITIAL_USER_ID, nullptr, sizeof(homePath)) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetSecureHomeDirectory(USER_SERVICE_INITIAL_USER_ID, homePath, 1) == USER_SERVICE_ERROR_BUFFER_TOO_SHORT); + Require(sceUserServiceGetSecureHomeDirectory(USER_SERVICE_INITIAL_USER_ID, homePath, sizeof(homePath)) == USER_SERVICE_OK); + Require(std::strstr(homePath, "/priv/home/10000000") != nullptr); + Require(libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(USER_SERVICE_INITIAL_USER_ID, nullptr, 37) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(USER_SERVICE_INITIAL_USER_ID, homePath, 36) == USER_SERVICE_ERROR_BUFFER_TOO_SHORT); + Require(libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(USER_SERVICE_INITIAL_USER_ID, homePath, 37) == USER_SERVICE_OK); + Require(std::strlen(homePath) == 36); + Require(sceUserServiceGetCurrentUserGroupIndex(nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceSetUserGroupIndex(-2) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceSetUserGroupIndex(129) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserGroupName(0, nullptr, 129) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserGroupName(0, homePath, 128) == USER_SERVICE_ERROR_BUFFER_TOO_SHORT); + Require(sceUserServiceGetUserGroupName(0, homePath, sizeof(homePath)) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetAgeLevel(USER_SERVICE_INITIAL_USER_ID + 1, &age_level) == USER_SERVICE_ERROR_NOT_LOGGED_IN); Require(sceUserServiceGetAgeLevel(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_NOT_LOGGED_IN); privacy = 0x5a5a; diff --git a/docs/dev/debt/libSceUserService.md b/docs/dev/debt/libSceUserService.md index 599a16dc66..2d3cbbf299 100644 --- a/docs/dev/debt/libSceUserService.md +++ b/docs/dev/debt/libSceUserService.md @@ -69,11 +69,11 @@ - [sceUserServiceSetGlsBroadcastersComment](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetGlsCameraTransparency](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetIsHrtfEnabled](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceGetCurrentUserGroupIndex](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature +- [sceUserServiceGetCurrentUserGroupIndex](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int32_t* index)` and dispatches user group query `0x30018`; AnyPS5 reads the current index from offset `0x44` in `/system_data/priv/home/user_list.dat`, matching the SceShellCore 13.60 handler. - [sceUserServiceGetPbtcThursdayHoursEnd](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetPbtcFridayHoursStart](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetSystemLoggerHashedAccountIdTtl](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceGetRegisteredHomeUserIdList](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature +- [sceUserServiceGetRegisteredHomeUserIdList](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - the 13.60 managed wrapper recovers `(int32_t* userIds, int32_t* count)` and returns up to 16 IDs; AnyPS5 fills the list from its existing connected-user model, matching `sceUserServiceGetRegisteredUserIdList`. - [sceUserServiceSetVolumeForGenericUSB](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetControlCenterFunctionControlSettings](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetAudioFocus](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature @@ -106,7 +106,7 @@ - [sceUserServiceSetIsFakePremium](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetAccessibilityKeyremapDataAston](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetKeyRepeatSpeed](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceSetUserGroupName](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature +- [sceUserServiceSetUserGroupName](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int groupIndex, const char* name)` and dispatches update `0x3001c`; AnyPS5 writes names up to 128 bytes to `/system_data/priv/home/user_group_name.dat.` with the recovered create, truncate, and mode flags. Missing slots are writable only when they are the next group slot. - [sceUserServiceGetParentalDvdRegion](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetGlsCameraDepthLevel](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetParentalContentControl](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature @@ -133,7 +133,7 @@ - [sceUserServiceSetPbtcSaturdayHoursEnd](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetSystemLoggerHashedAccountIdParam](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetHearingAssistUsePreset](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceGetUserGroupName](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature +- [sceUserServiceGetUserGroupName](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int groupIndex, char* name, size_t size)` and dispatches query `0x3001b`; AnyPS5 reads up to 128 bytes from `/system_data/priv/home/user_group_name.dat.`, using `User Group ` when the file is absent or empty. - [sceUserServiceSetAutoLoginEnabled](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetIPDLeft](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetHintShowFlag](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature @@ -366,7 +366,7 @@ - [sceUserServiceSetUserStatus](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetEventFilterTeamEvent](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetPsCloudFeedbackTime](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceSetUserGroupIndex](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature +- [sceUserServiceSetUserGroupIndex](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - firmware 13.60 validates the group slot, maps `-1` to `INT_MAX`, and writes Registry key `0x7410000`; AnyPS5 validates against `/system_data/priv/home/user_list.dat.` and stores the selected index at offset `0x44` in `/system_data/priv/home/user_list.dat`. - [sceUserServiceSetPartyMuteListA](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetNpLanguageCode2](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetShareStatus2](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature @@ -418,7 +418,7 @@ - [sceUserServiceSetNpOnlineId](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetA11yControllerBName](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetSaveDataAutoSync](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceGetDefaultNewUserGroupName](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature +- [sceUserServiceGetDefaultNewUserGroupName](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(char* name, size_t size)` and dispatches query `0x3001d`; AnyPS5 selects the first missing group slot among 128 using `/system_data/priv/home/user_list.dat.` and formats `User Group `. - [sceUserServiceGetHmd2Brightness](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetHoldAudioOutDevice](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetVibrationStrength](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature @@ -482,7 +482,7 @@ - [sceUserServiceGetHmd2GazeActivate](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetGlsCameraPosition](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetGlsMessageFilterLevel](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceGetUserGroupNameList](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature +- [sceUserServiceGetUserGroupNameList](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int groupIndex, char* nameList, size_t size, int32_t* count)` and dispatches query `0x3001a`; it reads up to 16 IDs from the group file (or the global list for index `-1`) and writes fixed 17-byte name slots. AnyPS5 resolves IDs supported by its user-name model; unsupported IDs return `USER_SERVICE_ERROR_UNKNOWN_USER` because shell user configuration is not modeled. - [sceUserServiceSetVoiceAgentEnableFeedback](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetGazeActivate](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetGlsIsTwitterEnabled](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature @@ -551,7 +551,7 @@ - [sceUserServiceSetIsHrtfEnabled](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetParentalGameAgeLevel](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetNotificationBehavior](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceGetUserGroupNum](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature +- [sceUserServiceGetUserGroupNum](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int32_t* count)` and dispatches query `0x30017`; AnyPS5 scans `/system_data/priv/home/user_list.dat.` from slot 0 and counts consecutive existing slots up to 128, matching the SceShellCore 13.60 handler. - [sceUserServiceSetPartySettingFlags](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetGlsCameraEffect](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetPbtcSundayHoursEnd](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature From 94dbf7f88107a70c477aa697379c1383e2a0b629 Mon Sep 17 00:00:00 2001 From: xdCloudy <52116030+xdCloudy@users.noreply.github.com> Date: Sun, 1 Nov 2026 20:09:16 +0000 Subject: [PATCH 207/612] test(libSceUserService): use a full group name buffer --- core/libs/tests/GuestUserService.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/libs/tests/GuestUserService.cpp b/core/libs/tests/GuestUserService.cpp index 6801293672..b6b4964f22 100644 --- a/core/libs/tests/GuestUserService.cpp +++ b/core/libs/tests/GuestUserService.cpp @@ -211,7 +211,8 @@ int main() { Require(sceUserServiceSetUserGroupIndex(129) == USER_SERVICE_ERROR_INVALID_ARGUMENT); Require(sceUserServiceGetUserGroupName(0, nullptr, 129) == USER_SERVICE_ERROR_INVALID_ARGUMENT); Require(sceUserServiceGetUserGroupName(0, homePath, 128) == USER_SERVICE_ERROR_BUFFER_TOO_SHORT); - Require(sceUserServiceGetUserGroupName(0, homePath, sizeof(homePath)) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + char oversizedGroupName[256]{}; + Require(sceUserServiceGetUserGroupName(0, oversizedGroupName, sizeof(oversizedGroupName)) == USER_SERVICE_ERROR_INVALID_ARGUMENT); Require(sceUserServiceGetAgeLevel(USER_SERVICE_INITIAL_USER_ID + 1, &age_level) == USER_SERVICE_ERROR_NOT_LOGGED_IN); Require(sceUserServiceGetAgeLevel(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_NOT_LOGGED_IN); From 93f88669b4e25368a6ff6996463e6a2c840da246 Mon Sep 17 00:00:00 2001 From: xdCloudy <52116030+xdCloudy@users.noreply.github.com> Date: Sun, 1 Nov 2026 20:20:28 +0000 Subject: [PATCH 208/612] fix(libSceUserService): recover user state queries --- core/libs/prx/libSceUserService/Export.cpp | 32 +++++++++---------- .../prx/libSceUserService/UserService.hpp | 12 +++---- docs/dev/debt/libSceUserService.md | 9 ++++-- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/core/libs/prx/libSceUserService/Export.cpp b/core/libs/prx/libSceUserService/Export.cpp index 69ca83f105..b3c0e23b3b 100644 --- a/core/libs/prx/libSceUserService/Export.cpp +++ b/core/libs/prx/libSceUserService/Export.cpp @@ -907,7 +907,6 @@ int APS5_VABI sceUserServiceGetVoiceAgentEnableFeedback(void) { } int APS5_VABI sceUserServiceIsKratosUser(void) { - NotImplemented_nid_no_patch(__func__); return 0; } @@ -1064,9 +1063,8 @@ int APS5_VABI sceUserServiceSetIPDLeft(void) { return 0; } -int APS5_VABI sceUserServiceGetKratosPrimaryUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetKratosPrimaryUser(int*) { + return USER_SERVICE_ERROR_NOT_SUPPORTED; } int APS5_VABI sceUserServiceLogout(void) { @@ -1390,7 +1388,6 @@ int APS5_VABI sceUserServiceSetHearingAssistSeparateLR(void) { } int APS5_VABI sceUserServiceIsUserStorageAccountBound(void) { - NotImplemented_nid_no_patch(__func__); return 0; } @@ -1499,9 +1496,11 @@ int APS5_VABI sceUserServiceGetHmd2Vibration(void) { return 0; } -int APS5_VABI sceUserServiceIsLoggedIn(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceIsLoggedIn(int userId) { + if (!initialized.load()) { + return USER_SERVICE_ERROR_NOT_INITIALIZED; + } + return IsUserLoggedIn(userId) ? 1 : 0; } int APS5_VABI sceUserServiceGetPbtcMondayHoursStart(void) { @@ -1524,9 +1523,8 @@ int APS5_VABI sceUserServiceGetIsQuickSignup(void) { return 0; } -int APS5_VABI sceUserServiceIsKratosPrimaryUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI sceUserServiceIsKratosPrimaryUser(int) { + return false; } int APS5_VABI sceUserServiceSetVoiceAgentEnableWup(void) { @@ -1549,9 +1547,11 @@ int APS5_VABI sceUserServiceSetGlsHintFlag(void) { return 0; } -int APS5_VABI sceUserServiceIsLoggedInWithoutLock(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceIsLoggedInWithoutLock(int userId) { + if (!initialized.load()) { + return USER_SERVICE_ERROR_NOT_INITIALIZED; + } + return IsUserLoggedIn(userId) ? 1 : 0; } int APS5_VABI sceUserServiceSetShareStatus3(void) { @@ -2086,7 +2086,7 @@ int APS5_VABI sceUserServiceSetShareStatus2(void) { return 0; } -int APS5_VABI sceUserServiceSetForegroundUser(void) { +int APS5_VABI sceUserServiceSetForegroundUser(int userId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2196,7 +2196,7 @@ int APS5_VABI sceUserServiceGetPbtcTuesdayDuration(void) { return 0; } -int APS5_VABI sceUserServiceGetUserWithHearingAssist(void) { +int APS5_VABI sceUserServiceGetUserWithHearingAssist(std::int32_t* userId) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceUserService/UserService.hpp b/core/libs/prx/libSceUserService/UserService.hpp index dae01264ca..0d5fcc7a1d 100644 --- a/core/libs/prx/libSceUserService/UserService.hpp +++ b/core/libs/prx/libSceUserService/UserService.hpp @@ -182,7 +182,7 @@ int APS5_VABI sceUserServiceSetNotificationSettings_2(void); int APS5_VABI sceUserServiceGetHomeDirectory(std::uint32_t userId, char* path, std::size_t size); int APS5_VABI sceUserServiceSetPbtcSundayDuration(void); int APS5_VABI sceUserServiceSetIPDLeft(void); -int APS5_VABI sceUserServiceGetKratosPrimaryUser(void); +int APS5_VABI sceUserServiceGetKratosPrimaryUser(int* userId); int APS5_VABI sceUserServiceLogout(void); int APS5_VABI sceUserServiceSetNpDateOfBirth(void); int APS5_VABI sceUserServiceSetPbtcWednesdayHoursStart(void); @@ -269,17 +269,17 @@ int APS5_VABI sceUserServiceGetGlsIsRecievedMessageHidden(void); int APS5_VABI sceUserServiceSetVoiceAgentSessionTimeout(void); int APS5_VABI sceUserServiceSetGlsLanguageFilter(void); int APS5_VABI sceUserServiceGetHmd2Vibration(void); -int APS5_VABI sceUserServiceIsLoggedIn(void); +int APS5_VABI sceUserServiceIsLoggedIn(int userId); int APS5_VABI sceUserServiceGetPbtcMondayHoursStart(void); int APS5_VABI sceUserServiceSetAppSortOrder(void); int APS5_VABI sceUserServiceSetMouseHandType(void); int APS5_VABI sceUserServiceGetIsQuickSignup(void); -int APS5_VABI sceUserServiceIsKratosPrimaryUser(void); +bool APS5_VABI sceUserServiceIsKratosPrimaryUser(int userId); int APS5_VABI sceUserServiceSetVoiceAgentEnableWup(void); int APS5_VABI sceUserServiceGetGlsCameraDepthLevel(void); int APS5_VABI sceUserServiceGetHmuBrightness(void); int APS5_VABI sceUserServiceSetGlsHintFlag(void); -int APS5_VABI sceUserServiceIsLoggedInWithoutLock(void); +int APS5_VABI sceUserServiceIsLoggedInWithoutLock(int userId); int APS5_VABI sceUserServiceSetShareStatus3(void); int APS5_VABI sceUserServiceGetGlsTtsVolume(void); int APS5_VABI sceUserServiceGetMicLevel(void); @@ -384,7 +384,7 @@ int APS5_VABI sceUserServiceSetUserGroupIndex(int index); int APS5_VABI sceUserServiceSetPartyMuteListA(void); int APS5_VABI sceUserServiceSetNpLanguageCode2(void); int APS5_VABI sceUserServiceSetShareStatus2(void); -int APS5_VABI sceUserServiceSetForegroundUser(void); +int APS5_VABI sceUserServiceSetForegroundUser(int userId); int APS5_VABI sceUserServiceSetGlsAccessTokenTwitch(void); int APS5_VABI sceUserServiceGetNpOfflineAccountAdult(void); int APS5_VABI sceUserServiceSetGamePresets(void); @@ -406,7 +406,7 @@ int APS5_VABI sceUserServiceSetGlsCameraEdgeLevel(void); int APS5_VABI sceUserServiceGetPartyChatTranscriptionLanguageType(void); int APS5_VABI sceUserServiceSetVoiceAgentTtsVolume(void); int APS5_VABI sceUserServiceGetPbtcTuesdayDuration(void); -int APS5_VABI sceUserServiceGetUserWithHearingAssist(void); +int APS5_VABI sceUserServiceGetUserWithHearingAssist(std::int32_t* userId); int APS5_VABI sceUserServiceGetNpOnlineId(void); int APS5_VABI sceUserServiceGetHintShowFlag3(void); int APS5_VABI sceUserServiceSetNotificationSettings_3(void); diff --git a/docs/dev/debt/libSceUserService.md b/docs/dev/debt/libSceUserService.md index 2d3cbbf299..30a58ff3f7 100644 --- a/docs/dev/debt/libSceUserService.md +++ b/docs/dev/debt/libSceUserService.md @@ -45,6 +45,10 @@ ### Unknown function info +- [sceUserServiceGetKratosPrimaryUser, sceUserServiceIsKratosPrimaryUser and sceUserServiceIsKratosUser](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - firmware supports Kratos user queries only when the kernel reports Cronos. AnyPS5's current host model is non-Cronos, so GetKratosPrimaryUser returns `0x80960006` and both Boolean queries return false. Cronos-mode behavior remains unmodelled. +- [sceUserServiceIsLoggedIn and sceUserServiceIsLoggedInWithoutLock](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - firmware 13.60 checks membership in its logged-in user list and returns 1 or 0 after checking initialization. AnyPS5 uses its connected-user list for the same query. +- [sceUserServiceIsUserStorageAccountBound](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - AnyPS5 has no linked PSN or online-storage account state; the export returns false, matching its existing zero account-ID model. Bound-account behavior is not modeled. +- [sceUserServiceIsKratosPrimaryUser](../../../core/libs/prx/libSceUserService/Export.cpp) - returns false for AnyPS5's non-Cronos host model; Cronos-mode behavior is unavailable. - [sceUserServiceGetLoginUserIdList and sceUserServiceGetEvent for user 0](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - under ANYPS5_PAD_PORT0_STATE=controller user 0 logs out when its controller is removed and logs in when it returns, after a hold of 500 ms that AnyPS5 chooses. Whether the console logs the initial user out on controller removal, how long it takes and which event it sends are not measured. The default mode keeps user 0 logged in as before. - [sceUserServiceGetNpAccountId](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - the account id for a logged-in user with no linked PSN account is assumed to be 0 with success; libSceNpManager's `sceNpGetAccountIdA` answers the same question with `SCE_NP_ERROR_SIGNED_OUT`. Console measurement on PS4 Pro firmware 10.71 confirms a user that is not logged in returns `0x80960105` (`USER_SERVICE_ERROR_UNKNOWN_USER`) and leaves the output untouched, differing from `sceUserServiceGetUserName`. - [Older settings exports](../../../core/libs/prx/libSceUserService/LegacyExports.cpp) - Forty callable NIDs are declared as throwing placeholders for older software. Their signatures and behavior are unverified; parameterless `int` declarations are placeholders, not verified ABI contracts. The corresponding NIDs are absent from the later version-labelled inventory. Representative later settings functions use a related dispatch pattern, which does not establish compatibility for these settings. No functionality is implemented. @@ -175,7 +179,6 @@ - [sceUserServiceGetHomeDirectory](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetPbtcSundayDuration](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetIPDLeft](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceGetKratosPrimaryUser](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetNpDateOfBirth](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetPbtcWednesdayHoursStart](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetSparkEqParam](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature @@ -370,7 +373,7 @@ - [sceUserServiceSetPartyMuteListA](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetNpLanguageCode2](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetShareStatus2](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceSetForegroundUser](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature +- [sceUserServiceSetForegroundUser](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - the 13.60 managed wrapper recovers `(int userId)`; foreground-user state is not modeled. - [sceUserServiceSetGlsAccessTokenTwitch](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetNpOfflineAccountAdult](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetGamePresets](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature @@ -391,7 +394,7 @@ - [sceUserServiceGetPartyChatTranscriptionLanguageType](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetVoiceAgentTtsVolume](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetPbtcTuesdayDuration](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceGetUserWithHearingAssist](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature +- [sceUserServiceGetUserWithHearingAssist](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int32_t* userId)` and dispatches query `0x3002b`; the result comes from the unavailable UserService backend, so the export still throws. - [sceUserServiceGetNpOnlineId](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetHintShowFlag3](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetNotificationSettings_3](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature From f957fd1f17da465d3422c798c6a1afef866ce120 Mon Sep 17 00:00:00 2001 From: xdCloudy <52116030+xdCloudy@users.noreply.github.com> Date: Sun, 1 Nov 2026 20:22:37 +0000 Subject: [PATCH 209/612] docs(libSceUserService): list modeled identity stubs --- core/libs/prx/libSceUserService/UserService.hpp | 1 + docs/dev/debt/libSceUserService.md | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/core/libs/prx/libSceUserService/UserService.hpp b/core/libs/prx/libSceUserService/UserService.hpp index 0d5fcc7a1d..661e78f6a7 100644 --- a/core/libs/prx/libSceUserService/UserService.hpp +++ b/core/libs/prx/libSceUserService/UserService.hpp @@ -23,6 +23,7 @@ constexpr int USER_SERVICE_THREAD_PRIORITY_MIN = 0x100; constexpr int USER_SERVICE_THREAD_PRIORITY_MAX = 0x2FF; constexpr int USER_SERVICE_ERROR_BUFFER_TOO_SHORT = -2137653238; constexpr int USER_SERVICE_ERROR_UNKNOWN_USER = static_cast(0x80960105u); +constexpr int USER_SERVICE_ERROR_NOT_SUPPORTED = static_cast(0x80960006u); constexpr std::size_t USER_SERVICE_MAX_USER_NAME_LENGTH = 16; constexpr const char* USER_SERVICE_INITIAL_USER_NAME = "Player"; constexpr int USER_SERVICE_MAX_USERS = 4; diff --git a/docs/dev/debt/libSceUserService.md b/docs/dev/debt/libSceUserService.md index 30a58ff3f7..bc84969422 100644 --- a/docs/dev/debt/libSceUserService.md +++ b/docs/dev/debt/libSceUserService.md @@ -1,5 +1,10 @@ # libSceUserService technical debt +### Silent stubs + +- [sceUserServiceIsKratosUser and sceUserServiceIsKratosPrimaryUser](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) return false for AnyPS5's non-Cronos host model; Cronos behavior is unavailable. +- [sceUserServiceIsUserStorageAccountBound](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) returns false because AnyPS5 does not model linked PSN or online-storage accounts. Bound-account behavior is not verified. + ### Additional callable exports - [Additional callable exports](../../../core/libs/prx/libSceUserService/InventoryExports.cpp) - 36 entries present in both version-labelled images are import-resolution declarations only. Neither label independently authenticates retail firmware provenance. None of these NIDs appears in the pinned public 3.20 catalogue for this library. Parameter lists, return types and behavior are unverified; parameterless `int` declarations are placeholders, not verified ABI contracts. Every entry throws through `NotImplemented_nid_no_patch`. Unidentified names use literal NID aliases. No runtime functionality is implemented. Data exports require separate layout and ownership analysis. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). From f288a56c5bbacbfbac87ee3ae62d8364478f60c8 Mon Sep 17 00:00:00 2001 From: unknown <52116030+xdCloudy@users.noreply.github.com> Date: Sun, 1 Nov 2026 20:36:09 +0000 Subject: [PATCH 210/612] docs(libSceFreeTypeOt): qualify trace query ABI evidence --- docs/dev/debt/libSceFreeTypeOt.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/dev/debt/libSceFreeTypeOt.md b/docs/dev/debt/libSceFreeTypeOt.md index c4f84057f0..bef3c4c4a3 100644 --- a/docs/dev/debt/libSceFreeTypeOt.md +++ b/docs/dev/debt/libSceFreeTypeOt.md @@ -4,6 +4,8 @@ - The 71 `sceFreeTypeOtUnknown_*_nid_no_patch` exports in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) are present in the PS5 3.20 export inventory from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). Their prototypes and behavior are unverified. Each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. - Console runtime and other firmware versions are unverified for [FT_Trace_Get_Count and FT_Trace_Get_Name](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp). The [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFreeTypeOt.c#L2708) identifies NIDs `08-mhD-AAds` and `64XMq6sDxUo`; FreeType declares an integer count and a `const char*` name lookup taking an integer index. Firmware 13.60 shows `xor eax,eax; ret` at `0x10410` and `0x10420`, matching the disabled-tracing return values 0 and null. Both implementations are fixed constants independent of host tracing. +The two return-only bodies do not establish parameter counts or parameter widths: neither reads an argument. The no-argument count declaration and integer name index follow the public FreeType API, not an independently recovered console prototype. The constant results are supported by static inspection, not measured on a console. + ### Unknown function info - [FT_Outline_GetInsideBorder, FT_Outline_GetOutsideBorder](../../../core/libs/prx/libSceFreeTypeOt/CMakeLists.txt) (libSceFreeTypeOl, libSceFreeTypeOt) - console prototypes, enum/layout equivalence, FreeType configuration and module/library versions are unverified. The existing LP64 outline layout and unsigned 16-bit counts follow bundled FreeType 2.13.3; border selection follows [ftstroke.c](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftstroke.c), with the shifted polygon area from [ftoutln.c](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftoutln.c) accumulated in 64 bits. Null positive-count arrays and out-of-range or non-increasing contour endpoints throw when reached; their console behavior is unknown. Tags and flags are not read, and input arrays remain caller-owned. From 53de6f2339088af8f17d670044ce631fa2aac9ba Mon Sep 17 00:00:00 2001 From: unknown <52116030+xdCloudy@users.noreply.github.com> Date: Sun, 1 Nov 2026 20:38:47 +0000 Subject: [PATCH 211/612] fix(libSceUserService): validate group slots and group name writes --- .../libs/prx/libSceUserService/CMakeLists.txt | 11 ++++ core/libs/prx/libSceUserService/Export.cpp | 8 ++- .../libSceUserService/InventoryExports.cpp | 2 +- core/libs/tests/GuestUserService.cpp | 3 +- core/libs/tests/GuestUserServiceGroupIo.cpp | 59 +++++++++++++++++++ docs/dev/debt/libSceUserService.md | 21 ++++--- 6 files changed, 92 insertions(+), 12 deletions(-) create mode 100644 core/libs/tests/GuestUserServiceGroupIo.cpp diff --git a/core/libs/prx/libSceUserService/CMakeLists.txt b/core/libs/prx/libSceUserService/CMakeLists.txt index ee3f746eb4..4002dbd68f 100644 --- a/core/libs/prx/libSceUserService/CMakeLists.txt +++ b/core/libs/prx/libSceUserService/CMakeLists.txt @@ -5,6 +5,17 @@ configure_windows_unwind(libSceUserService) target_sources(libSceUserService PRIVATE LegacyExports.cpp) if(BUILD_TESTING) + add_test_executable(guest_user_service_group_io_tests ${LIBS_INCLUDE_DIR}/tests/GuestUserServiceGroupIo.cpp Export.cpp) + target_include_directories(guest_user_service_group_io_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_user_service_group_io_tests PRIVATE libSceSystemService libc libkernel) + configure_windows_unwind(guest_user_service_group_io_tests) + add_test(NAME guest_user_service_group_io COMMAND guest_user_service_group_io_tests) + set_tests_properties(guest_user_service_group_io PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_user_service_group_io PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_user_service_tests ${LIBS_INCLUDE_DIR}/tests/GuestUserService.cpp) target_include_directories(guest_user_service_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_user_service_tests PRIVATE libSceUserService libSceSystemService libc) diff --git a/core/libs/prx/libSceUserService/Export.cpp b/core/libs/prx/libSceUserService/Export.cpp index b3c0e23b3b..de6b96326a 100644 --- a/core/libs/prx/libSceUserService/Export.cpp +++ b/core/libs/prx/libSceUserService/Export.cpp @@ -100,6 +100,9 @@ static int ReportKeyboardSetting(int user_id, int32_t* type, int32_t (*value)()) return USER_SERVICE_OK; } bool UserGroupSlotExists(int index) { + if (index < 0 || index > 128) { + return false; + } char path[64]; std::snprintf(path, sizeof(path), "/system_data/priv/home/user_list.dat.%d", index); FileStat status{}; @@ -693,7 +696,7 @@ int APS5_VABI sceUserServiceSetUserGroupName(int groupIndex, const char* name) { if (bytesWritten < 0) { return static_cast(bytesWritten); } - return USER_SERVICE_OK; + return bytesWritten == static_cast(nameSize) ? USER_SERVICE_OK : SCE_KERNEL_ERROR_EIO; } int APS5_VABI sceUserServiceGetParentalDvdRegion(void) { @@ -836,6 +839,9 @@ int APS5_VABI sceUserServiceGetUserGroupName(int groupIndex, char* name, std::si if (size != 129) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } + if (!UserGroupSlotExists(groupIndex)) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } std::memset(name, 0, size); char path[64]; std::snprintf(path, sizeof(path), "/system_data/priv/home/user_group_name.dat.%d", groupIndex); diff --git a/core/libs/prx/libSceUserService/InventoryExports.cpp b/core/libs/prx/libSceUserService/InventoryExports.cpp index 5ce4fb1426..4e91a3c0d1 100644 --- a/core/libs/prx/libSceUserService/InventoryExports.cpp +++ b/core/libs/prx/libSceUserService/InventoryExports.cpp @@ -90,7 +90,7 @@ int APS5_VABI libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(std::uint32_t us if (path == nullptr) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } - if (size < 0x25) { + if (size <= 0x25) { return USER_SERVICE_ERROR_BUFFER_TOO_SHORT; } std::snprintf(path, size, "/%s/eap/rodata/home/%08x", sceKernelGetFsSandboxRandomWord(), userId); diff --git a/core/libs/tests/GuestUserService.cpp b/core/libs/tests/GuestUserService.cpp index b6b4964f22..ad838f772f 100644 --- a/core/libs/tests/GuestUserService.cpp +++ b/core/libs/tests/GuestUserService.cpp @@ -204,7 +204,8 @@ int main() { Require(std::strstr(homePath, "/priv/home/10000000") != nullptr); Require(libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(USER_SERVICE_INITIAL_USER_ID, nullptr, 37) == USER_SERVICE_ERROR_INVALID_ARGUMENT); Require(libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(USER_SERVICE_INITIAL_USER_ID, homePath, 36) == USER_SERVICE_ERROR_BUFFER_TOO_SHORT); - Require(libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(USER_SERVICE_INITIAL_USER_ID, homePath, 37) == USER_SERVICE_OK); + Require(libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(USER_SERVICE_INITIAL_USER_ID, homePath, 37) == USER_SERVICE_ERROR_BUFFER_TOO_SHORT); + Require(libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(USER_SERVICE_INITIAL_USER_ID, homePath, 38) == USER_SERVICE_OK); Require(std::strlen(homePath) == 36); Require(sceUserServiceGetCurrentUserGroupIndex(nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); Require(sceUserServiceSetUserGroupIndex(-2) == USER_SERVICE_ERROR_INVALID_ARGUMENT); diff --git a/core/libs/tests/GuestUserServiceGroupIo.cpp b/core/libs/tests/GuestUserServiceGroupIo.cpp new file mode 100644 index 0000000000..83aedee935 --- /dev/null +++ b/core/libs/tests/GuestUserServiceGroupIo.cpp @@ -0,0 +1,59 @@ +#include "prx/libSceUserService/UserService.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include +#include +#include +#include +#include + +namespace { +int openResult = 7; +std::int64_t readResult = 4; +std::int64_t writeResult = 4; +int opens{}; +int closes{}; +std::string written; +void Require(bool value) { if (!value) std::abort(); } +} + +extern "C" { +int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t) { ++opens; return openResult; } +int APS5_VABI sceKernelClose(int) { ++closes; return 0; } +int APS5_VABI sceKernelStat(const char*, FileStat*) { return 0; } +std::int64_t APS5_VABI sceKernelRead(int, void* buffer, std::size_t size) { + if (readResult > 0) std::memcpy(buffer, "Team", std::min(size, static_cast(readResult))); + return readResult; +} +std::int64_t APS5_VABI sceKernelWrite(int, const void* buffer, std::size_t size) { + written.assign(static_cast(buffer), size); + return writeResult; +} +} + +int main() { + char name[129]{}; + Require(sceUserServiceGetUserGroupName(INT_MAX, name, sizeof(name)) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserGroupName(INT_MIN, name, sizeof(name)) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(opens == 0); + Require(sceUserServiceGetUserGroupName(0, name, sizeof(name)) == USER_SERVICE_OK); + Require(std::strcmp(name, "Team") == 0 && closes == 1); + readResult = 0; + Require(sceUserServiceGetUserGroupName(128, name, sizeof(name)) == USER_SERVICE_OK); + Require(std::strcmp(name, "User Group 129") == 0); + readResult = SCE_KERNEL_ERROR_EIO; + Require(sceUserServiceGetUserGroupName(0, name, sizeof(name)) == USER_SERVICE_OK); + Require(std::strcmp(name, "User Group 1") == 0); + openResult = SCE_KERNEL_ERROR_EIO; + Require(sceUserServiceGetUserGroupName(0, name, sizeof(name)) == USER_SERVICE_OK); + Require(std::strcmp(name, "User Group 1") == 0); + openResult = 7; + Require(sceUserServiceSetUserGroupName(0, "Team") == USER_SERVICE_OK); + Require(written == "Team"); + writeResult = 2; + Require(sceUserServiceSetUserGroupName(0, "Team") == SCE_KERNEL_ERROR_EIO); + writeResult = SCE_KERNEL_ERROR_EIO; + Require(sceUserServiceSetUserGroupName(0, "Team") == SCE_KERNEL_ERROR_EIO); + writeResult = 0; + Require(sceUserServiceSetUserGroupName(0, "") == USER_SERVICE_OK); +} diff --git a/docs/dev/debt/libSceUserService.md b/docs/dev/debt/libSceUserService.md index bc84969422..c0bb974594 100644 --- a/docs/dev/debt/libSceUserService.md +++ b/docs/dev/debt/libSceUserService.md @@ -7,7 +7,7 @@ ### Additional callable exports -- [Additional callable exports](../../../core/libs/prx/libSceUserService/InventoryExports.cpp) - 36 entries present in both version-labelled images are import-resolution declarations only. Neither label independently authenticates retail firmware provenance. None of these NIDs appears in the pinned public 3.20 catalogue for this library. Parameter lists, return types and behavior are unverified; parameterless `int` declarations are placeholders, not verified ABI contracts. Every entry throws through `NotImplemented_nid_no_patch`. Unidentified names use literal NID aliases. No runtime functionality is implemented. Data exports require separate layout and ownership analysis. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). +- [Additional callable exports](../../../core/libs/prx/libSceUserService/InventoryExports.cpp) - 35 entries present in both version-labelled images are import-resolution declarations only. Neither label independently authenticates retail firmware provenance. None of these NIDs appears in the pinned public 3.20 catalogue for this library. Parameter lists, return types and behavior are unverified; parameterless `int` declarations are placeholders, not verified ABI contracts. Every entry throws through `NotImplemented_nid_no_patch`. Unidentified names use literal NID aliases. No runtime functionality is implemented. Data exports require separate layout and ownership analysis. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). | Name | NID | | --- | --- | @@ -27,7 +27,6 @@ | `Unknown` | `Cqt7pJZRKLI` | | `Unknown` | `D5lYX9Wm7PI` | | `Unknown` | `DL6cKVeISqs` | -| `Unknown` | `ErTMkmSho4E` | | `Unknown` | `GbRykQBfmM8` | | `Unknown` | `JD766DzZIIU` | | `Unknown` | `L9NSPGwLZTQ` | @@ -78,7 +77,7 @@ - [sceUserServiceSetGlsBroadcastersComment](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetGlsCameraTransparency](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetIsHrtfEnabled](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceGetCurrentUserGroupIndex](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int32_t* index)` and dispatches user group query `0x30018`; AnyPS5 reads the current index from offset `0x44` in `/system_data/priv/home/user_list.dat`, matching the SceShellCore 13.60 handler. +- [sceUserServiceGetCurrentUserGroupIndex](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int32_t* index)` and dispatches user group query `0x30018`; AnyPS5 reads the current index from offset `0x44` in `/system_data/priv/home/user_list.dat`, matching the SceShellCore 13.60 handler logic. AnyPS5 does not create the global user list or numbered group files: on a fresh installation this call fails with the kernel open error or reports zero groups, until those files are provided. - [sceUserServiceGetPbtcThursdayHoursEnd](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetPbtcFridayHoursStart](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetSystemLoggerHashedAccountIdTtl](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature @@ -142,7 +141,7 @@ - [sceUserServiceSetPbtcSaturdayHoursEnd](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetSystemLoggerHashedAccountIdParam](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetHearingAssistUsePreset](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceGetUserGroupName](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int groupIndex, char* name, size_t size)` and dispatches query `0x3001b`; AnyPS5 reads up to 128 bytes from `/system_data/priv/home/user_group_name.dat.`, using `User Group ` when the file is absent or empty. +- [sceUserServiceGetUserGroupName](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int groupIndex, char* name, size_t size)` and dispatches query `0x3001b`; AnyPS5 reads up to 128 bytes from `/system_data/priv/home/user_group_name.dat.`, using `User Group ` after any name-file open error or nonpositive read. In SceShellCore 13.60, `0x9f5410` requires a 129-byte buffer and calls the slot validator before path formatting. Validator `0x9f45e0` rejects values outside unsigned 0..128 and checks the group file. The fallback at `0x9f55a1` handles open errors and nonpositive reads, including genuine I/O errors; these are observed native semantics, not just absent-file handling. A valid group slot must exist even when its name file is missing. The setter at `0x9f5670` establishes the 128-byte name limit and create/truncate flags. Short writes return EIO on the host to avoid reporting a truncated name as successfully stored; native handling of short nonnegative writes differs and is not claimed equivalent. - [sceUserServiceSetAutoLoginEnabled](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetIPDLeft](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetHintShowFlag](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature @@ -181,7 +180,7 @@ - [sceUserServiceGetGlsLiveQuality2](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetIsQuickSignup](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetNotificationSettings_2](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceGetHomeDirectory](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature +- [sceUserServiceGetHomeDirectory](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - the chosen ABI is `(uint32_t userId, char* path, size_t size)`. Firmware 13.60 `libSceUserService.sprx` at `0x3b20` forwards a 32-bit user value and the destination/capacity to the formatting import with `/user/home/%08x`. The host rejects null and truncated buffers; native error behavior for these inputs was not established. - [sceUserServiceSetPbtcSundayDuration](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetIPDLeft](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetNpDateOfBirth](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature @@ -374,7 +373,7 @@ - [sceUserServiceSetUserStatus](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetEventFilterTeamEvent](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetPsCloudFeedbackTime](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceSetUserGroupIndex](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - firmware 13.60 validates the group slot, maps `-1` to `INT_MAX`, and writes Registry key `0x7410000`; AnyPS5 validates against `/system_data/priv/home/user_list.dat.` and stores the selected index at offset `0x44` in `/system_data/priv/home/user_list.dat`. +- [sceUserServiceSetUserGroupIndex](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - firmware 13.60 validates the group slot, maps `-1` to `INT_MAX`, and writes Registry key `0x7410000`; AnyPS5 validates against `/system_data/priv/home/user_list.dat.` and stores the selected index at offset `0x44` in `/system_data/priv/home/user_list.dat`. AnyPS5 does not create this file, so a fresh installation returns the kernel open error. - [sceUserServiceSetPartyMuteListA](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetNpLanguageCode2](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetShareStatus2](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature @@ -448,7 +447,7 @@ - [sceUserServiceGetVibrationStrength](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetNpAccountUpgradeFlag](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetDeletedUserInfo](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceGetSecureHomeDirectory](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature +- [sceUserServiceGetSecureHomeDirectory](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - the chosen ABI is `(uint32_t userId, char* path, size_t size)`. Firmware 13.60 `libSceUserService.sprx` at `0x3b50` calls the sandbox-word import and formats `//priv/home/%08x`. The host rejects null and truncated buffers; native error behavior for these inputs was not established. - [sceUserServiceGetHearingAssistAdjustmentLevelRight](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetPlatformPrivacyWs1Internal](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetGlsIsRecDisabled](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature @@ -490,7 +489,7 @@ - [sceUserServiceGetHmd2GazeActivate](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetGlsCameraPosition](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetGlsMessageFilterLevel](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceGetUserGroupNameList](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int groupIndex, char* nameList, size_t size, int32_t* count)` and dispatches query `0x3001a`; it reads up to 16 IDs from the group file (or the global list for index `-1`) and writes fixed 17-byte name slots. AnyPS5 resolves IDs supported by its user-name model; unsupported IDs return `USER_SERVICE_ERROR_UNKNOWN_USER` because shell user configuration is not modeled. +- [sceUserServiceGetUserGroupNameList](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int groupIndex, char* nameList, size_t size, int32_t* count)` and dispatches query `0x3001a`; it reads up to 16 IDs from the group file (or the global list for index `-1`) and writes fixed 17-byte name slots. AnyPS5 resolves IDs supported by its user-name model; unsupported IDs return `USER_SERVICE_ERROR_UNKNOWN_USER` because shell user configuration is not modeled. The global and numbered user-list files are not created by AnyPS5; a fresh installation returns the kernel open error, including for index -1. - [sceUserServiceSetVoiceAgentEnableFeedback](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetGazeActivate](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetGlsIsTwitterEnabled](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature @@ -559,10 +558,14 @@ - [sceUserServiceSetIsHrtfEnabled](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetParentalGameAgeLevel](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetNotificationBehavior](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceGetUserGroupNum](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int32_t* count)` and dispatches query `0x30017`; AnyPS5 scans `/system_data/priv/home/user_list.dat.` from slot 0 and counts consecutive existing slots up to 128, matching the SceShellCore 13.60 handler. +- [sceUserServiceGetUserGroupNum](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int32_t* count)` and dispatches query `0x30017`; AnyPS5 scans `/system_data/priv/home/user_list.dat.` from slot 0 and counts consecutive existing slots up to 128, matching the SceShellCore 13.60 handler logic. AnyPS5 does not create the global user list or numbered group files: on a fresh installation this call fails with the kernel open error or reports zero groups, until those files are provided. - [sceUserServiceSetPartySettingFlags](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetGlsCameraEffect](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetPbtcSundayHoursEnd](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetIsSpartacusEnabled](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetKeyboardType, sceUserServiceGetJapaneseInputType, sceUserServiceGetTraditionalChineseInputType](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - AnyPS5 stores no per-user keyboard setting, so the initial user reports the keyboard type of the configured system language (`ANYPS5_LANGUAGE`, English US by default) and 0 for the two input variants, and any other user id returns `USER_SERVICE_ERROR_UNKNOWN_USER`. The language-to-keyboard-type table is the repository owner's (languages 0 to 29); a valid language outside it, such as Ukrainian (30), reports 20 (US English). A value that is not a console language number is rejected by `sceSystemServiceParamGetInt` with an error naming `ANYPS5_LANGUAGE`; the call throws it and leaves the output untouched. The input variant is 0 because the owner's table gives 0 for Japanese and Traditional Chinese, the only languages needing one. `sceConvertKeycodeGetImeKeyboardType` consumes these values. - [sceUserServiceGetPlatformPrivacyWs1](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - the platform privacy setting for an offline user without a linked PSN account is assumed to be 0 (not permitted) with success; actual console privacy querying is not modelled. Return of `0x80960009` (`USER_SERVICE_ERROR_NOT_LOGGED_IN`) with or without an output for a user who is not logged in is borrowed from `sceUserServiceGetAgeLevel` and is not measured for this call. + +### Group and home evidence limitations + +- [EAP home path](../../../core/libs/prx/libSceUserService/InventoryExports.cpp), NID `ErTMkmSho4E`, is implemented separately from the 35 throwing inventory entries. Firmware 13.60 `libSceUserService.sprx` at `0x3b90` rejects a null destination with `0x80960005`, rejects capacities through `0x25` with `0x8096000a`, and otherwise formats `//eap/rodata/home/%08x`. Capacity 38 succeeds even though the current host sandbox word produces a 36-character path fitting in 37 bytes; the stricter native check is retained. The three path routines establish a 32-bit user value, destination and capacity; exact source-language typedefs remain unverified. All cited sites were read from disassembly/decompilation, not measured on a console. Module SHA-256: `f56b87b4be88f988be50a358a76962ea8cedb9d858777c8737c801e7010beca1`; SceShellCore SHA-256: `15606a2cb87b73b091ed2121b37424ea8826d301ba6d07ce0a508da4979ea090`. From 0362cae88b39f953e39edaef69d0149d1ae0e2b1 Mon Sep 17 00:00:00 2001 From: unknown <52116030+xdCloudy@users.noreply.github.com> Date: Sun, 1 Nov 2026 20:50:17 +0000 Subject: [PATCH 212/612] fix(libSceUserService): preserve unsupported account state failures --- core/libs/prx/libSceUserService/Export.cpp | 8 ++++---- docs/dev/debt/libSceUserService.md | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceUserService/Export.cpp b/core/libs/prx/libSceUserService/Export.cpp index de6b96326a..9a752197e6 100644 --- a/core/libs/prx/libSceUserService/Export.cpp +++ b/core/libs/prx/libSceUserService/Export.cpp @@ -913,7 +913,7 @@ int APS5_VABI sceUserServiceGetVoiceAgentEnableFeedback(void) { } int APS5_VABI sceUserServiceIsKratosUser(void) { - return 0; + NotImplemented_nid_no_patch(__func__); } int APS5_VABI sceUserServiceSetCreatedVersion(void) { @@ -1070,7 +1070,7 @@ int APS5_VABI sceUserServiceSetIPDLeft(void) { } int APS5_VABI sceUserServiceGetKratosPrimaryUser(int*) { - return USER_SERVICE_ERROR_NOT_SUPPORTED; + NotImplemented_nid_no_patch(__func__); } int APS5_VABI sceUserServiceLogout(void) { @@ -1394,7 +1394,7 @@ int APS5_VABI sceUserServiceSetHearingAssistSeparateLR(void) { } int APS5_VABI sceUserServiceIsUserStorageAccountBound(void) { - return 0; + NotImplemented_nid_no_patch(__func__); } int APS5_VABI sceUserServiceSetHmd2Brightness(void) { @@ -1530,7 +1530,7 @@ int APS5_VABI sceUserServiceGetIsQuickSignup(void) { } bool APS5_VABI sceUserServiceIsKratosPrimaryUser(int) { - return false; + NotImplemented_nid_no_patch(__func__); } int APS5_VABI sceUserServiceSetVoiceAgentEnableWup(void) { diff --git a/docs/dev/debt/libSceUserService.md b/docs/dev/debt/libSceUserService.md index c0bb974594..5a8c20d5eb 100644 --- a/docs/dev/debt/libSceUserService.md +++ b/docs/dev/debt/libSceUserService.md @@ -1,9 +1,9 @@ # libSceUserService technical debt -### Silent stubs +### Unsupported account state -- [sceUserServiceIsKratosUser and sceUserServiceIsKratosPrimaryUser](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) return false for AnyPS5's non-Cronos host model; Cronos behavior is unavailable. -- [sceUserServiceIsUserStorageAccountBound](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) returns false because AnyPS5 does not model linked PSN or online-storage accounts. Bound-account behavior is not verified. +- [sceUserServiceIsKratosUser, sceUserServiceIsKratosPrimaryUser and sceUserServiceGetKratosPrimaryUser](../../../core/libs/prx/libSceUserService/Export.cpp) throw because the account-state backend and Cronos behavior are unavailable. A non-Cronos model alone does not establish the full export contract. +- [sceUserServiceIsUserStorageAccountBound](../../../core/libs/prx/libSceUserService/Export.cpp) throws because linked PSN and online-storage account state is unavailable; returning false would conceal the missing backend. ### Additional callable exports From 692d74fd42fdbd315b6011aa310c65e126ba099f Mon Sep 17 00:00:00 2001 From: unknown <52116030+xdCloudy@users.noreply.github.com> Date: Sun, 1 Nov 2026 21:00:52 +0000 Subject: [PATCH 213/612] fix(libSceLibreSsl): stage network dependencies for patched BIO tests --- core/libs/prx/libSceLibreSsl/MemoryBio.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceLibreSsl/MemoryBio.cmake b/core/libs/prx/libSceLibreSsl/MemoryBio.cmake index 86da987864..187cfe01c4 100644 --- a/core/libs/prx/libSceLibreSsl/MemoryBio.cmake +++ b/core/libs/prx/libSceLibreSsl/MemoryBio.cmake @@ -22,7 +22,8 @@ if(BUILD_TESTING) target_link_libraries(guest_libressl_bio_patched_tests PRIVATE libc) endif() add_custom_target(libressl_bio_patched_libraries DEPENDS - ${PATCHED_DIR}/libSceLibreSsl.prx ${PATCHED_DIR}/libc.prx ${PATCHED_DIR}/libkernel.prx) + ${PATCHED_DIR}/libSceLibreSsl.prx ${PATCHED_DIR}/libc.prx ${PATCHED_DIR}/libkernel.prx + ${PATCHED_DIR}/libSceNet.prx ${PATCHED_DIR}/libSceNetCtl.prx) add_dependencies(guest_libressl_bio_patched_tests libressl_bio_patched_libraries) add_test(NAME guest_libressl_bio_patched COMMAND guest_libressl_bio_patched_tests ${PATCHED_DIR}) set_tests_properties(guest_libressl_bio_patched PROPERTIES TIMEOUT 30) From 071c30c3e7b9bb66f75a1f9ff22602816b804d6c Mon Sep 17 00:00:00 2001 From: unknown <52116030+xdCloudy@users.noreply.github.com> Date: Sun, 1 Nov 2026 21:06:17 +0000 Subject: [PATCH 214/612] docs(libSceUserService): isolate short-write behavior caveat --- docs/dev/debt/libSceUserService.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceUserService.md b/docs/dev/debt/libSceUserService.md index 5a8c20d5eb..93885f0c51 100644 --- a/docs/dev/debt/libSceUserService.md +++ b/docs/dev/debt/libSceUserService.md @@ -141,7 +141,7 @@ - [sceUserServiceSetPbtcSaturdayHoursEnd](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetSystemLoggerHashedAccountIdParam](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetHearingAssistUsePreset](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature -- [sceUserServiceGetUserGroupName](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int groupIndex, char* name, size_t size)` and dispatches query `0x3001b`; AnyPS5 reads up to 128 bytes from `/system_data/priv/home/user_group_name.dat.`, using `User Group ` after any name-file open error or nonpositive read. In SceShellCore 13.60, `0x9f5410` requires a 129-byte buffer and calls the slot validator before path formatting. Validator `0x9f45e0` rejects values outside unsigned 0..128 and checks the group file. The fallback at `0x9f55a1` handles open errors and nonpositive reads, including genuine I/O errors; these are observed native semantics, not just absent-file handling. A valid group slot must exist even when its name file is missing. The setter at `0x9f5670` establishes the 128-byte name limit and create/truncate flags. Short writes return EIO on the host to avoid reporting a truncated name as successfully stored; native handling of short nonnegative writes differs and is not claimed equivalent. +- [sceUserServiceGetUserGroupName](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - 13.60 ABI is `(int groupIndex, char* name, size_t size)` and dispatches query `0x3001b`; AnyPS5 reads up to 128 bytes from `/system_data/priv/home/user_group_name.dat.`, using `User Group ` after any name-file open error or nonpositive read. In SceShellCore 13.60, `0x9f5410` requires a 129-byte buffer and calls the slot validator before path formatting. Validator `0x9f45e0` rejects values outside unsigned 0..128 and checks the group file. The fallback at `0x9f55a1` handles open errors and nonpositive reads, including genuine I/O errors; these are observed native semantics, not just absent-file handling. A valid group slot must exist even when its name file is missing. - [sceUserServiceSetAutoLoginEnabled](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceGetIPDLeft](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature - [sceUserServiceSetHintShowFlag](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - unknown signature @@ -569,3 +569,5 @@ ### Group and home evidence limitations - [EAP home path](../../../core/libs/prx/libSceUserService/InventoryExports.cpp), NID `ErTMkmSho4E`, is implemented separately from the 35 throwing inventory entries. Firmware 13.60 `libSceUserService.sprx` at `0x3b90` rejects a null destination with `0x80960005`, rejects capacities through `0x25` with `0x8096000a`, and otherwise formats `//eap/rodata/home/%08x`. Capacity 38 succeeds even though the current host sandbox word produces a 36-character path fitting in 37 bytes; the stricter native check is retained. The three path routines establish a 32-bit user value, destination and capacity; exact source-language typedefs remain unverified. All cited sites were read from disassembly/decompilation, not measured on a console. Module SHA-256: `f56b87b4be88f988be50a358a76962ea8cedb9d858777c8737c801e7010beca1`; SceShellCore SHA-256: `15606a2cb87b73b091ed2121b37424ea8826d301ba6d07ce0a508da4979ea090`. + +- [Group-name write limits](../../../core/libs/prx/libSceUserService/Export.cpp) differ for short writes: host writes return EIO rather than report a truncated name as successfully stored. SceShellCore 13.60 at 0x9f5670 establishes the 128-byte limit and create/truncate flags, but accepts short nonnegative writes. Host equivalence for that case is not claimed. From eeef26f744b20e834c2516746cb849cebd7b6f98 Mon Sep 17 00:00:00 2001 From: unknown <52116030+xdCloudy@users.noreply.github.com> Date: Sun, 1 Nov 2026 21:21:28 +0000 Subject: [PATCH 215/612] docs(libSceLibcInternal): pin showmanyc bodies and module identity --- docs/dev/debt/libSceLibcInternal.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceLibcInternal.md b/docs/dev/debt/libSceLibcInternal.md index af1ab2e59a..abb19d68aa 100644 --- a/docs/dev/debt/libSceLibcInternal.md +++ b/docs/dev/debt/libSceLibcInternal.md @@ -8,7 +8,7 @@ The named entries below use provider-specific NID aliases to avoid interposing h - [Additional callable exports](../../../core/libs/prx/libSceLibcInternal/InventoryExports.cpp) - 297 entries observed in a 3.20-labelled image and a later 13.60-labelled image; 291 match the public 3.20 catalogue and 6 do not. Apart from the two `showmanyc` exports below, these remain import-resolution declarations: signatures and behavior are unverified, parameterless `int` declarations are placeholders, and their bodies throw through `NotImplemented_nid_no_patch`. All declarations use literal NID aliases; the table records candidate names where known. No other runtime functionality is implemented; data exports need separate analysis. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). -`IAEl1Ta7yVU` and `olsoiZsezkk` are the narrow and wide `std::basic_streambuf::showmanyc` exports. Ghidra inspection of the locally held firmware 13.60 `libSceLibcInternal.sprx` shows `xor eax, eax; ret` at both NID-resolved export addresses. Their Itanium names match the standard-library declarations, whose return type is `std::streamsize`; the base `showmanyc` contract returns zero ([C++ draft](https://eel.is/c++draft/streambuf.virt.get)). The bodies do not access the stream buffer. Firmware identity and implementation were inspected locally; no firmware material is included here. +- [showmanyc](../../../core/libs/prx/libSceLibcInternal/InventoryExports.cpp) has no console-runtime validation. Firmware 13.60 libSceLibcInternal.sprx (SHA-256 a2d70cc64c3abc3927662cbe0c3765b74b2385b4b2ad80eb7b942a891f9259fb) contains bytes 31 c0 c3 (xor eax,eax; ret) at 0x92b30 for IAEl1Ta7yVU and 0xc2c80 for olsoiZsezkk. These static observations establish a zero result without buffer access. The Itanium names identify narrow/wide std::basic_streambuf specializations; std::streamsize follows the standard-library declaration and the [base showmanyc contract](https://eel.is/c++draft/streambuf.virt.get). No console measurement or other firmware equivalence is claimed. | Name | NID | | --- | --- | From 7b03f92ac928475407ff2483bc3dfb68468a4b4b Mon Sep 17 00:00:00 2001 From: Daniel Horn <73882221+danielhorngt@users.noreply.github.com> Date: Sun, 1 Nov 2026 21:27:59 +0000 Subject: [PATCH 216/612] fix(agc): apply a LOD bias beyond the sampler limit in the shader Clamping the S# bias changed the LOD when a shader bias of the opposite sign cancelled it. An S# bias beyond 15 now leaves the host sampler at 0 and reaches the shader through a specialization constant: implicit and gradient samples scale their derivatives by two to the summed bias, and explicit LOD samples add it to the LOD. Co-Authored-By: Claude Opus 5.5 --- .../Graphics/include/Sampler.hpp | 1 + .../libSceAgcDriver/Graphics/src/Sampler.cpp | 8 +- .../Graphics/src/ShaderResources.cpp | 1 + .../prx/libSceAgcDriver/tests/Graphics.cpp | 23 +- .../tests/execution/ImageSampleLodBiasSum.cpp | 196 ++++++++++++++++++ .../IrMetadata/Resources.hpp | 4 + .../src/DescriptorBindingBuilder.cpp | 8 + .../recompiler/PipelineSpecialization.hpp | 2 + .../SpirvBackend/src/SpirvImageEmitter.cpp | 56 ++++- docs/dev/debt/libSceAgcDriver.md | 2 +- 10 files changed, 286 insertions(+), 15 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleLodBiasSum.cpp diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp index 7212fb40c6..ff442adb95 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp @@ -26,6 +26,7 @@ public: bool ReadsOpaqueBlackBorder() const; bool RequiresNonSeamlessCube() const; static void Validate(const Context& context, const GuestSamplerResource& descriptor); + static bool ShaderAppliesLodBias(const GuestSamplerResource& descriptor); static bool ReadsOpaqueBlackBorder(const GuestSamplerResource& descriptor); private: diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp index 1d9e58c143..eb294c8507 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp @@ -1,14 +1,20 @@ #include "prx/libSceAgcDriver/Graphics/include/Sampler.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" +#include "PipelineSpecialization.hpp" #include #include #include namespace AgcDriver::Graphics { + bool Sampler::ShaderAppliesLodBias(const GuestSamplerResource& descriptor) { + return descriptor.lodBias < -ShaderRecompiler::PipelineSpecialization::SamplerLodBiasShaderThreshold || descriptor.lodBias > ShaderRecompiler::PipelineSpecialization::SamplerLodBiasShaderThreshold; + } + void Sampler::Validate(const Context& context, const GuestSamplerResource& descriptor) { Require(!descriptor.anisotropyEnable || context.samplerAnisotropy, "guest sampler descriptor requests anisotropic filtering which the device does not support"); Require(descriptor.maxAnisotropy <= context.limits.maxSamplerAnisotropy, "guest sampler descriptor requests an anisotropy ratio beyond the device limit"); + Require(ShaderAppliesLodBias(descriptor) || (descriptor.lodBias >= -context.limits.maxSamplerLodBias && descriptor.lodBias <= context.limits.maxSamplerLodBias), "guest sampler descriptor requests a LOD bias beyond the device limit"); Require(!(descriptor.unnormalizedCoordinates && descriptor.compareEnable), "guest sampler descriptor with unnormalized coordinates enables depth comparison, which is not implemented"); if (descriptor.reductionMode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT) { Require(context.samplerFilterMinmax, "guest sampler descriptor uses a min or max reduction which the device does not support"); @@ -32,7 +38,7 @@ namespace AgcDriver::Graphics { info.addressModeU = descriptor.addressModeU; info.addressModeV = descriptor.addressModeV; info.addressModeW = descriptor.addressModeW; - info.mipLodBias = std::clamp(descriptor.lodBias, -context.limits.maxSamplerLodBias, context.limits.maxSamplerLodBias); + info.mipLodBias = ShaderAppliesLodBias(descriptor) ? 0.0f : descriptor.lodBias; info.anisotropyEnable = descriptor.anisotropyEnable ? VK_TRUE : VK_FALSE; info.maxAnisotropy = descriptor.maxAnisotropy; info.compareEnable = descriptor.compareEnable ? VK_TRUE : VK_FALSE; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 78ec8ee522..2831cff680 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -5232,6 +5232,7 @@ void ShaderResources::addSamplerTable(const ShaderRecompiler::DescriptorBinding& auto resource = DecodeSamplerResource(elementWords); resource.compareEnable = binding.samplerDepthCompare.at(element); Sampler::Validate(context, resource); + Require(!Sampler::ShaderAppliesLodBias(resource), "a sampler table entry with a LOD bias beyond the sampler limit needs a per-entry shader bias, which is not implemented"); Require(!Sampler::ReadsOpaqueBlackBorder(resource), "a sampler table entry with an opaque black border needs dynamic texture BC-swizzle pairing, which is not implemented"); Require(resource.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER && resource.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER && resource.addressModeW != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, "a sampler table entry with a border needs dynamic texture numeric-class pairing, which is not implemented"); Require(!resource.nonSeamlessCube || context.nonSeamlessCubeMap, "a sampler table entry that disables seamless cube filtering needs VK_EXT_non_seamless_cube_map because dynamic texture/sampler pairing is not implemented"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index d148e28910..3ea39d9f9a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -7226,7 +7226,9 @@ void samplerTableDeviceLimitTests(std::optional selected = std::n namespace Abi = ShaderRecompiler::ImageTableAbi; constexpr auto count = 2u * Abi::SamplerEntryElements; const std::array valid{0x92u, (4u * 256u) << 12u, 0u, 0u}; - const std::array, 2> invalid{{ + const std::array, 4> invalid{{ + {0x92u, valid[1], 256u, 0u}, + {0x92u, valid[1], 0x1000u, 0u}, {0x92u | (2u << 9u), valid[1], (2u << 20u) | (2u << 22u), 0u}, {0x92u | (1u << 29u), valid[1], 0u, 0u} }}; @@ -7285,7 +7287,7 @@ void samplerLodBiasTests() { auto context = mockContext(); context.samplerAnisotropy = true; context.limits.maxSamplerAnisotropy = 2.0f; - context.limits.maxSamplerLodBias = 15.0f; + context.limits.maxSamplerLodBias = 10.0f; const auto hostBias = [&](std::uint32_t field) { const std::array words{0x92u, (4u * 256u) << 12u, field, 0u}; AgcDriver::Graphics::Sampler sampler(context, AgcDriver::Graphics::DecodeSamplerResource(words)); @@ -7293,10 +7295,17 @@ void samplerLodBiasTests() { }; Require(hostBias(256u) == 1.0f, "a positive LOD bias within the device limit changed"); Require(hostBias(0x3f00u) == -1.0f, "a negative LOD bias within the device limit changed"); - Require(hostBias(0xf00u) == 15.0f, "a LOD bias at the device limit changed"); - Require(hostBias(0x1facu) == 15.0f, "a LOD bias above the device limit was not clamped to it"); - Require(hostBias(0x2000u) == -15.0f, "a LOD bias below the device limit was not clamped to it"); - Require(mock.live == 0 && mock.samplerFlags.empty(), "the clamped samplers leaked Vulkan objects"); + Require(hostBias(0xa00u) == 10.0f, "a LOD bias at the device limit changed"); + Require(hostBias(0x1facu) == 0.0f, "a host sampler kept a positive LOD bias the shader applies"); + Require(hostBias(0x2000u) == 0.0f, "a host sampler kept a negative LOD bias the shader applies"); + bool rejected = false; + try { + hostBias(0xf00u); + } catch (const std::runtime_error&) { + rejected = true; + } + Require(rejected, "a LOD bias beyond the device limit that the shader does not apply was accepted"); + Require(mock.live == 0 && mock.samplerFlags.empty(), "the sampler LOD bias checks leaked Vulkan objects"); } void hostImportBudgetTests() { @@ -13059,7 +13068,7 @@ int main(int argc, char** argv) { imageTableSnapshotAllocationTests(); return 0; } - const std::array samplerCases{"--sampler-table-anisotropy", "--sampler-table-reduction-compare"}; + const std::array samplerCases{"--sampler-table-lod-bias", "--sampler-table-lod-bias-beyond-threshold", "--sampler-table-anisotropy", "--sampler-table-reduction-compare"}; for (std::uint32_t index = 0; index < samplerCases.size(); ++index) { if (argc == 2 && std::string_view(argv[1]) == samplerCases[index]) { samplerTableDeviceLimitTests(index); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleLodBiasSum.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleLodBiasSum.cpp new file mode 100644 index 0000000000..58d0a4f205 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleLodBiasSum.cpp @@ -0,0 +1,196 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 16; +constexpr std::uint32_t Size = 64; +constexpr std::uint32_t Mips = 7; +constexpr std::uint32_t Format8888UNorm = 56; +constexpr std::uint32_t Type2D = 9; +constexpr float CoordinateScale = 0.0473f; +constexpr std::uint32_t LevelStep = 20; +alignas(256) std::array Pixels{}; +alignas(256) std::array Texels{}; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +alignas(256) constexpr std::array BiasPixelCode{ + 0x10040010, 0x06060300, 0x10060611, 0xf0940f08, 0x00610401, 0xbf8c3f70, 0xf800180f, 0x07060504, 0xbf810000, +}; + +constexpr std::array, 3> Triangle{{{-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f}}}; + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +std::array TextureDescriptor() { + const auto address = static_cast(reinterpret_cast(Texels.data())); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (((Size - 1u) & 3u) << 30u), + ((Size - 1u) >> 2u) | ((Size - 1u) << 14u), + 0xfacu | ((Mips - 1u) << 16u) | (Type2D << 28u), + 0u, + (Mips - 1u) << 4u, + 0u, + 0u, + }; +} + +std::array SamplerDescriptor() { + return {0u, 0xfffu << 12u, 1u << 26u, 0u}; +} + +void FillTexture() { + const auto mips = AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, Format8888UNorm, Size, Size, Mips); + Require(AgcDriver::Graphics::ComputeSurfaceSize(mips, 1) <= Texels.size(), "image sample LOD bias sum: the mip chain does not fit the texel storage"); + std::fill(Texels.begin(), Texels.end(), 0xeeu); + for (std::uint32_t level = 0; level < Mips; ++level) { + const auto& mip = mips[level]; + for (std::uint32_t y = 0; y < mip.height; ++y) { + for (std::uint32_t x = 0; x < mip.width; ++x) { + auto* texel = &Texels[mip.tiledOffset + y * mip.pitchBytes + x * 4u]; + texel[0] = static_cast(level * LevelStep); + texel[1] = 0u; + texel[2] = 0u; + texel[3] = 255u; + } + } + } +} + +void Draw(AgcDriver::VulkanDevice& device, std::array texture, std::array sampler, float bias) { + const auto& target = device.Target(); + alignas(256) std::array code{}; + std::copy_n(BiasPixelCode.begin(), 3u, code.begin()); + code[3] = 0x7e020212u; + std::copy(BiasPixelCode.begin() + 3, BiasPixelCode.end(), code.begin() + 4); + const auto pixelCode = std::span(code).first(10u); + Pixels.fill(std::byte{0}); + + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(Triangle.data(), 16u, static_cast(Triangle.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {64, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.wave32 = false; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY); + pixel.posX = true; + pixel.posY = true; + pixel.targetOutputMode[0] = 9; + pixel.targetExportMapping.fill(0xe4u); + std::vector pixelUserData(24, 0u); + std::copy(texture.begin(), texture.end(), pixelUserData.begin() + 4); + std::copy(sampler.begin(), sampler.end(), pixelUserData.begin() + 12); + pixelUserData[16] = std::bit_cast(CoordinateScale); + pixelUserData[17] = std::bit_cast(CoordinateScale); + pixelUserData[18] = std::bit_cast(bias); + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(std::span(pixelCode))}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, + {64, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + Require(!target.registrationModules || pixelResult.registrationModule, "image sample LOD bias sum did not exercise its requested registration module"); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)} + }}; + + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 64u, 64u, std::nullopt, std::nullopt}; + state.color = {reinterpret_cast(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u}; + state.colors = {state.color}; + state.hasColorTarget = true; + state.renderExtent = {Width, Height}; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.viewport = {0, static_cast(Height), static_cast(Width), -static_cast(Height), 0, 1}; + state.negativeOneToOne = false; + state.scissor = {{0, 0}, {Width, Height}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15; + state.blends = {state.blend}; + state.blendConstants = {}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Triangle.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); + device.WaitIdle(); +} + +struct Case { + std::uint32_t field; + float shaderBias; + std::uint32_t level; +}; + +void Check(const Case& sample) { + for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) { + const auto red = std::to_integer(Pixels[pixel * 4u]); + Require(red == sample.level * LevelStep, "image sample LOD bias sum: S# field " + std::to_string(sample.field) + " with shader bias " + std::to_string(sample.shaderBias) + " read " + std::to_string(red) + ", expected level " + std::to_string(sample.level)); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + FillTexture(); + constexpr std::array cases{{ + {0u, 0.0f, 2u}, + {0x100u, 0.0f, 3u}, + {0u, 2.0f, 4u}, + {0x1000u, -14.0f, 4u}, + {0x1facu, -29.0f, 5u}, + {0x3000u, 14.0f, 0u}, + {0x2000u, 32.0f, 2u}, + {0x1facu, 0.0f, 6u}, + {0x2000u, 0.0f, 0u}, + }}; + for (const auto& sample : cases) { + auto sampler = SamplerDescriptor(); + sampler[2] |= sample.field; + Draw(*device, TextureDescriptor(), sampler, sample.shaderBias); + Check(sample); + } + std::puts("image sample LOD bias sum tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp index f371ef9407..b868f59cdc 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp @@ -153,6 +153,10 @@ inline bool SamplesBaseLevelIfUnnormalized(const SamplerResource& sampler) { return (sampler.uses & (SamplerUseImplicitLod | SamplerUseGradient)) != 0u && (sampler.uses & ~static_cast(SamplerUseExplicitLod | SamplerUseImplicitLod | SamplerUseGradient | SamplerUseNonZeroLod | SamplerUseOffset)) == 0u && !sampler.depthCompare; } +inline bool AppliesLodBiasInShader(const SamplerResource& sampler) { + return sampler.table == NoTable && (sampler.uses & (SamplerUseExplicitLod | SamplerUseImplicitLod | SamplerUseGradient)) != 0u; +} + struct SampledResourcePair { std::uint32_t image = 0; std::uint32_t sampler = 0; diff --git a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp index 50bb4afd71..fca5030ca5 100644 --- a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp +++ b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp @@ -351,6 +351,14 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l for (std::uint32_t index = 0; index < info.samplers.size(); ++index) { if (FoldsTexelOffsets(info.samplers[index])) plan.specialization.push_back({PipelineSpecialization::FoldOffsetBase + index, unnormalized.samplers[index] ? 1u : 0u}); if (SamplesBaseLevelIfUnnormalized(info.samplers[index])) plan.specialization.push_back({PipelineSpecialization::UnnormalizedBaseLevelBase + index, unnormalized.samplers[index] ? 1u : 0u}); + if (info.samplers[index].table != NoTable) continue; + const auto bias = unnormalized.samplers[index] ? 0.0f : static_cast(static_cast((snapshot.samplers.at(index).dwords[2] & 0x3fffu) ^ 0x2000u) - 0x2000) / 256.0f; + const bool beyondThreshold = bias < -PipelineSpecialization::SamplerLodBiasShaderThreshold || bias > PipelineSpecialization::SamplerLodBiasShaderThreshold; + if (beyondThreshold && (!AppliesLodBiasInShader(info.samplers[index]) || (info.samplers[index].uses & (SamplerUseQueryLod | SamplerUseAdjust)) != 0u)) fail("a guest sampler LOD bias beyond the sampler limit reaches a shader use that cannot apply it, which is not implemented"); + for (const auto& pair : info.sampledPairs) { + if (beyondThreshold && pair.sampler == index && info.images.at(pair.image).cube) fail("a guest sampler LOD bias beyond the sampler limit on a cube image is not implemented"); + } + if (AppliesLodBiasInShader(info.samplers[index])) plan.specialization.push_back({PipelineSpecialization::SamplerLodBiasBase + index, beyondThreshold ? std::bit_cast(bias) : 0u}); } std::vector compareStates(info.images.size()); for (std::uint32_t index = 0; index < info.images.size(); ++index) { diff --git a/core/shader/recompiler/PipelineSpecialization.hpp b/core/shader/recompiler/PipelineSpecialization.hpp index 12960b63df..6ec8c10db3 100644 --- a/core/shader/recompiler/PipelineSpecialization.hpp +++ b/core/shader/recompiler/PipelineSpecialization.hpp @@ -46,6 +46,8 @@ inline constexpr std::uint32_t DescriptorIndexStride = 128u; inline constexpr std::uint32_t FoldOffsetBase = 57344u; inline constexpr std::uint32_t UnnormalizedBaseLevelBase = 57408u; inline constexpr std::uint32_t FmaskFormatBase = 57472u; +inline constexpr std::uint32_t SamplerLodBiasBase = 57600u; +inline constexpr float SamplerLodBiasShaderThreshold = 15.0f; inline constexpr std::uint32_t DescriptorIndex(std::uint32_t binding, std::uint32_t element) { return DescriptorIndexBase + binding * DescriptorIndexStride + element; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp index 34ae6baea8..c0d38e1956 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp @@ -1595,10 +1595,12 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con operandMask |= spv::ImageOperandsBiasMask; operands.push_back(AddressF32(ctx, access, setup.layout.bias)); } + const auto unclampedLod = (operandMask & spv::ImageOperandsLodMask) != 0u ? operands.back() : 0u; + std::uint32_t lodClamp = 0; if (setup.layout.clamp != NoImageComponent) { const auto& capabilities = state.supportedCapabilities; if (std::find(capabilities.begin(), capabilities.end(), static_cast(spv::CapabilityMinLod)) == capabilities.end()) ctx.Fail(access.inst, "clamps its LOD, which needs the device's shaderResourceMinLod"); - const auto clamp = AddressF32(ctx, access, setup.layout.clamp); + const auto clamp = lodClamp = AddressF32(ctx, access, setup.layout.clamp); if ((operandMask & spv::ImageOperandsLodMask) != 0u) { const auto clamped = state.module.AllocateId(); state.module.AddFunction(spv::OpExtInst, TypeF32(state), clamped, GlslStd450(state), GLSLstd450FMax, operands.back(), clamp); @@ -1661,6 +1663,48 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con state.module.AddFunction(words); return sample; }; + const auto emitBiasedSample = [&](std::uint32_t coord, std::uint32_t mask, const std::vector& arguments, std::uint32_t sampleOpcode) { + if (!AppliesLodBiasInShader(state.program.Info().samplers.at(mem.sampler))) return emitSample(coord, mask, arguments, sampleOpcode); + const auto f32 = TypeF32(state); + const auto selector = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::SamplerLodBiasBase + mem.sampler, 0u); + const auto beyondLimit = Binary(state, spv::OpINotEqual, TypeBool(state), selector, ConstantU32(state, 0u)); + return EmitValueIfElse(state, beyondLimit, resultType, [&] { + const auto bias = Unary(state, spv::OpBitcast, f32, selector); + auto biased = arguments; + if ((mask & spv::ImageOperandsLodMask) != 0u) { + auto lod = Binary(state, spv::OpFAdd, f32, unclampedLod, bias); + if (lodClamp != 0u) { + const auto clamped = state.module.AllocateId(); + state.module.AddFunction(spv::OpExtInst, f32, clamped, GlslStd450(state), GLSLstd450FMax, lod, lodClamp); + lod = clamped; + } + biased.front() = lod; + return emitSample(coord, mask, biased, sampleOpcode); + } + auto gradMask = mask; + auto total = bias; + if ((mask & spv::ImageOperandsBiasMask) != 0u) { + total = Binary(state, spv::OpFAdd, f32, total, biased.front()); + biased.erase(biased.begin()); + gradMask &= ~static_cast(spv::ImageOperandsBiasMask); + } + const auto components = setup.dimensionInfo.spatialComponents; + const auto gradientType = components == 1u ? f32 : TypeF32Vector(state, components); + if ((mask & spv::ImageOperandsGradMask) == 0u) { + const auto spatial = CoordF32(ctx, access, setup.layout.coord, components, AddressDimension(access).spatialComponents); + biased.insert(biased.begin(), {Unary(state, spv::OpDPdx, gradientType, spatial), Unary(state, spv::OpDPdy, gradientType, spatial)}); + gradMask |= spv::ImageOperandsGradMask; + } + const auto scale = state.module.AllocateId(); + state.module.AddFunction(spv::OpExtInst, f32, scale, GlslStd450(state), GLSLstd450Pow, ConstantF32(state, 0x40000000u), total); + for (std::size_t index = 0; index < 2u; ++index) { + biased[index] = Binary(state, components == 1u ? spv::OpFMul : spv::OpVectorTimesScalar, gradientType, biased[index], scale); + } + return emitSample(coord, gradMask, biased, setup.dref ? spv::OpImageSampleDrefExplicitLod : spv::OpImageSampleExplicitLod); + }, [&] { + return emitSample(coord, mask, arguments, sampleOpcode); + }); + }; std::uint32_t sample = 0; const bool baseLevelIfUnnormalized = !setup.dref && SamplesBaseLevelIfUnnormalized(state.program.Info().samplers.at(mem.sampler)); if (baseLevelIfUnnormalized) { @@ -1669,12 +1713,12 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con sample = EmitValueIfElse(state, unnormalized, resultType, [&] { return emitSample(hasOffset ? FoldedOffsetCoord(ctx, access, setup) : setup.coord, spv::ImageOperandsLodMask, {ZeroF32(state)}, spv::OpImageSampleExplicitLod); }, [&] { - return emitSample(setup.coord, operandMask, operands, opcode); + return emitBiasedSample(setup.coord, operandMask, operands, opcode); }); } else if (!foldOffset) { - sample = emitSample(setup.coord, operandMask, operands, opcode); + sample = emitBiasedSample(setup.coord, operandMask, operands, opcode); } else if (!offsetOperand) { - sample = emitSample(FoldedOffsetCoord(ctx, access, setup), foldedMask, foldedOperands, opcode); + sample = emitBiasedSample(FoldedOffsetCoord(ctx, access, setup), foldedMask, foldedOperands, opcode); } else { const auto selector = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::FoldOffsetBase + mem.sampler, 0u); const auto folded = Binary(state, spv::OpINotEqual, TypeBool(state), selector, ConstantU32(state, 0u)); @@ -1684,11 +1728,11 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con state.module.AddFunction(spv::OpSelectionMerge, merge, spv::SelectionControlMaskNone); state.module.AddFunction(spv::OpBranchConditional, folded, foldLabel, operandLabel); EmitLabel(state, foldLabel); - const auto foldSample = emitSample(FoldedOffsetCoord(ctx, access, setup), foldedMask, foldedOperands, opcode); + const auto foldSample = emitBiasedSample(FoldedOffsetCoord(ctx, access, setup), foldedMask, foldedOperands, opcode); const auto foldEnd = state.currentLabel; state.module.AddFunction(spv::OpBranch, merge); EmitLabel(state, operandLabel); - const auto operandSample = emitSample(setup.coord, operandMask, operands, opcode); + const auto operandSample = emitBiasedSample(setup.coord, operandMask, operands, opcode); const auto operandEnd = state.currentLabel; state.module.AddFunction(spv::OpBranch, merge); EmitLabel(state, merge); diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index 89ce75aa43..65988298a2 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -189,7 +189,7 @@ - [Presentation under the GPU mutex](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) (libSceAgcDriver) - on a device whose presentation family has no queue left after the main and settle queues, such as RADV, lavapipe and Intel's Windows driver (Arc Graphics on Meteor Lake, 101.9150), which expose a single graphics queue, `vkQueuePresentKHR` still runs on the main queue under `GuestMemory::GpuMutex`, so the draw committer and the queue workers wait for as long as the present blocks. How long that is on those drivers was not measured; on NVIDIA in FIFO mode it is about 6 ms per present. AMD's Windows driver (Radeon 860M) has one too: a Quake II port's trace had 0.3 ms presents (max 3.5), no `GpuMutex.wait.*` behind them. A device with a free queue in that family presents on it under its own mutex, after the GPU mutex is released. Closes when: traces on RADV, lavapipe and Intel's Windows driver also show no `GpuMutex.wait.*` behind `Vulkan.QueuePresent`. - [Unnormalized S# coordinates](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) - `FORCE_UNNORMALIZED` uses a Vulkan `unnormalizedCoordinates` sampler, so the S# LOD range, LOD bias and mip filter are not applied, which is exact only on a one-level view. - An [unnormalized S#](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) maps clamp mode 0 (repeat) on X and Y to clamp-to-edge as required by Vulkan unnormalized coordinates, and assigns the magnification filter when minification and magnification filters differ (since unnormalized coordinates sample at LOD 0). Anisotropy, MC_COORD_TRUNC and other clamp modes still throw. Secondary LOD bias (LOD_BIAS_SEC, bits [19:14]) is accepted and ignored, matching RDNA2 hardware measurements across explicit and implicit samples. High on Life (PPSA15388, #4736) uses secondary LOD bias and unnormalized coordinates with clamp mode 0 and differing min/mag filters during home base and city rendering. -- [S# LOD bias](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp) (libSceAgcDriver) beyond `maxSamplerLodBias` is clamped to it when the sampler is created, and the [shader bias](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) stays unchanged. The S# field reaches -32 to +31.99, while Vulkan refuses a larger `mipLodBias` (15 on an RTX 4080, 15.99 on Intel UHD 770) and clamps the sum of sampler and shader bias to the same limit. A shader bias of the opposite sign therefore cancels against the clamped value, not the guest one: at an implicit LOD near 2.1 on an RTX 4080, S# +16 with shader bias -14 reads level 3 where the sum selects level 4, and S# +31.67 with shader bias -29 reads level 0 where the sum selects level 5. Not measured on a console. Without a shader bias, a positive S# bias reads a lower level than the sum selects when the texture on a view of L levels is magnified more than 2^(16 - L) times at a limit of 15, and a negative one reads a higher level only when it is minified more than 2^15 times. +- [S# LOD bias](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp) (libSceAgcDriver) beyond 15 is applied in the shader, not the host sampler, because Vulkan limits `mipLodBias` and the sum of sampler and shader bias to `maxSamplerLodBias`. Implicit and gradient samples scale their derivatives by two to the summed bias, and explicit LOD samples add it to the LOD. A sampler table entry beyond 15, a cube image, an LOD query or an adjusted sample with such a bias throws, as does a bias of 15 or less above the device's `maxSamplerLodBias`. Whether the console applies the S# bias to explicit LOD samples is not measured; this keeps the host sampler's behaviour. - [TRUNC_COORD](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) is not applied, with normalized or unnormalized coordinates: the texel a point sample reads and the bilinear weights follow the host sampler's rounding. On RDNA 2 (RX 6700 XT), an S# with TRUNC_COORD 0 rounds the exact scaled coordinate to 8 fractional bits, ties to even, and one with TRUNC_COORD 1 truncates it, for point and bilinear filters. AMD's Windows driver (32.0.21045.5002) truncates for nearest samplers and rounds for linear ones. Under it, a TRUNC_COORD 0 point sample at most 1/512 texel below a texel edge reads the texel below the edge where the console reads the one above, and `k / width` computed in f32 lands there for about half of all k. A TRUNC_COORD 1 bilinear weight can be 1/256 above the console's. Other host drivers are not measured. - [Background link-time-optimized relink of library pipelines](../../../core/libs/prx/libSceAgcDriver/Execution/include/PipelineLinkOptimization.hpp) (libSceAgcDriver) is skipped on every GPU under NVIDIA's proprietary Vulkan driver (`VK_DRIVER_ID_NVIDIA_PROPRIETARY`), so those pipelines keep their fast-linked form. The optimized link loses the device when bound on an RTX 3080 (Ampere, #2866) and an RTX 4060 (Ada, #3434); an RTX 5060 and an RTX 5080 (Blackwell) on the same driver do not, so the scope is wider than the evidence. No standard Vulkan query tells the architecture apart, and the driver version that fixes it is unknown. `APS5_NO_PIPELINE_LTO` disables the relink on every driver. - [8_SRGB and 8_8_SRGB textures](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureFormat.cpp) (libSceAgcDriver) that the device samples as R8_SRGB or R8G8_SRGB use its own decode, which need not match gfx1035: NVIDIA's R8_SRGB is one bf16 step off in 29 of 256 codes. A device that cannot sample them gets an RGBA8 sRGB copy instead (a blit of the R8 or RG8 texels, refreshed with the storage image's version), whose decode is likewise the device's; 3D textures and storage images that are not single-depth and format-matched are copied from their snapshot instead. From 645ff25ad9267fb38ef729de219c764231d46bc9 Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Thu, 29 Oct 2026 12:56:03 +0100 Subject: [PATCH 217/612] feat(libSceNpCppWebApi): answer the empty ResponseHeaderBase header query --- .../libs/prx/libSceNpCppWebApi/CMakeLists.txt | 9 ++ core/libs/prx/libSceNpCppWebApi/Export.cpp | 24 ++++- .../prx/libSceNpCppWebApi/include/Api.hpp | 11 +- .../tests/GuestNpCppWebApiResponseHeader.cpp | 102 ++++++++++++++++++ 4 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 core/libs/tests/GuestNpCppWebApiResponseHeader.cpp diff --git a/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt index 7079b95d84..d14cb7c075 100644 --- a/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt +++ b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt @@ -21,6 +21,15 @@ if(MINGW) endif() if(BUILD_TESTING) + add_test_executable(guest_np_cpp_web_api_response_header_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpCppWebApiResponseHeader.cpp) + target_include_directories(guest_np_cpp_web_api_response_header_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_cpp_web_api_response_header_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_response_header_tests) + add_test(NAME guest_np_cpp_web_api_response_header COMMAND guest_np_cpp_web_api_response_header_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_response_header + PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() add_test_executable(guest_np_cpp_web_api_string_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpCppWebApiString.cpp) target_include_directories(guest_np_cpp_web_api_string_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_np_cpp_web_api_string_tests PRIVATE libSceNpCppWebApi libc) diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp index 9f757f3fff..300b99e10f 100644 --- a/core/libs/prx/libSceNpCppWebApi/Export.cpp +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -3322,7 +3322,10 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS return TransactionNotStarted; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE() { +int APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE(const CommonResponseHeaderBase* self, const char* name, IntrusivePtr* value) { + (void)name; + (void)value; + if (self->headers == nullptr) return ParameterBodyMissing; NotImplemented_nid_no_patch(__func__); return 0; } @@ -3418,6 +3421,25 @@ void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderB return self->object; } +static_assert(sizeof(CommonResponseHeaderBase) == 0x10 && offsetof(CommonResponseHeaderBase, headers) == 0x08); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseC1Ev(CommonResponseHeaderBase* self) { + self->referenceCount = 0; + self->headers = nullptr; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseC2Ev(CommonResponseHeaderBase* self) { + _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseC1Ev(self); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseD1Ev(CommonResponseHeaderBase* self) { + (void)self; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseD2Ev(CommonResponseHeaderBase* self) { + (void)self; +} + void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEE3getEv(const IntrusivePtr* self) { return self->object; } diff --git a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp index c0479d51ef..11c9143d77 100644 --- a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp +++ b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp @@ -9,6 +9,11 @@ struct CommonString { void* libContext; }; +struct CommonResponseHeaderBase { + std::int32_t referenceCount; + void* headers; +}; + struct IntrusivePtr { void* object; void (APS5_VABI* deleter)(void*); @@ -659,7 +664,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption(); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE(const CommonResponseHeaderBase* self, const char* name, IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv(); int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE3endEv(); int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE5beginEv(); @@ -680,6 +685,10 @@ void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEptEv(const IntrusivePtr* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEE3getEv(const IntrusivePtr* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEEptEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseC1Ev(CommonResponseHeaderBase* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseC2Ev(CommonResponseHeaderBase* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseD1Ev(CommonResponseHeaderBase* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseD2Ev(CommonResponseHeaderBase* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEE3getEv(const IntrusivePtr* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEEptEv(const IntrusivePtr* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEdeEv(const IntrusivePtr* self); diff --git a/core/libs/tests/GuestNpCppWebApiResponseHeader.cpp b/core/libs/tests/GuestNpCppWebApiResponseHeader.cpp new file mode 100644 index 0000000000..9f0ab93da8 --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiResponseHeader.cpp @@ -0,0 +1,102 @@ +#include "prx/libSceNpCppWebApi/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool value, std::source_location at = std::source_location::current()) { + if (!value) { + std::fprintf(stderr, "requirement failed at %s:%u\n", at.file_name(), at.line()); + std::abort(); + } +} + +constexpr std::size_t kHeaderSize = 0x10; +constexpr std::size_t kGuardSize = 0x10; +constexpr std::size_t kReferenceCountOffset = 0x00; +constexpr std::size_t kHeadersOffset = 0x08; +constexpr int kBodyMissing = static_cast(0x80553512); +constexpr unsigned char kFill = 0xA5; + +struct Storage { + alignas(8) unsigned char bytes[kHeaderSize + kGuardSize]; +}; + +struct IntrusivePtrStorage { + alignas(8) unsigned char bytes[sizeof(IntrusivePtr) + kGuardSize]; +}; + +template +TValue Load(const Storage& storage, std::size_t offset) { + TValue value; + std::memcpy(&value, storage.bytes + offset, sizeof(value)); + return value; +} + +bool GuardIntact(const Storage& storage) { + for (std::size_t index = kHeaderSize; index < sizeof(storage.bytes); ++index) { + if (storage.bytes[index] != kFill) return false; + } + return true; +} + +void TestEmptyHeaderHasNoValue() { + Storage storage; + IntrusivePtrStorage value; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + std::memset(value.bytes, kFill, sizeof(value.bytes)); + + _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseC1Ev(reinterpret_cast(storage.bytes)); + Require(Load(storage, kReferenceCountOffset) == 0); + Require(Load(storage, kHeadersOffset) == nullptr); + Require(GuardIntact(storage)); + + Require(_ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE( + reinterpret_cast(storage.bytes), "Content-Type", + reinterpret_cast(value.bytes)) == kBodyMissing); + Require(value.bytes[0] == kFill && value.bytes[sizeof(IntrusivePtr)] == kFill); + + _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseD1Ev(reinterpret_cast(storage.bytes)); + Require(Load(storage, kReferenceCountOffset) == 0); + Require(Load(storage, kHeadersOffset) == nullptr); + Require(GuardIntact(storage)); +} + +void TestHeaderWithStorageIsNotImplementedOffline() { + Storage storage; + IntrusivePtrStorage value; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + std::memset(value.bytes, kFill, sizeof(value.bytes)); + + _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseC2Ev(reinterpret_cast(storage.bytes)); + *reinterpret_cast(storage.bytes + kHeadersOffset) = reinterpret_cast(1); + + bool threw = false; + try { + _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE( + reinterpret_cast(storage.bytes), "Content-Type", + reinterpret_cast(value.bytes)); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw); + Require(value.bytes[0] == kFill && value.bytes[sizeof(IntrusivePtr)] == kFill); + + *reinterpret_cast(storage.bytes + kHeadersOffset) = nullptr; + _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseD2Ev(reinterpret_cast(storage.bytes)); + Require(GuardIntact(storage)); +} + +} + +int main() { + TestEmptyHeaderHasNoValue(); + TestHeaderWithStorageIsNotImplementedOffline(); + return 0; +} From 1f86198fb7c6bcc1df6342db0c715859d20ddaba Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Thu, 29 Oct 2026 14:06:53 +0100 Subject: [PATCH 218/612] test(libSceNpCppWebApi): stop pinning the unimplemented header path --- .../tests/GuestNpCppWebApiResponseHeader.cpp | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/core/libs/tests/GuestNpCppWebApiResponseHeader.cpp b/core/libs/tests/GuestNpCppWebApiResponseHeader.cpp index 9f0ab93da8..10334207b5 100644 --- a/core/libs/tests/GuestNpCppWebApiResponseHeader.cpp +++ b/core/libs/tests/GuestNpCppWebApiResponseHeader.cpp @@ -6,7 +6,6 @@ #include #include #include -#include namespace { @@ -68,35 +67,9 @@ void TestEmptyHeaderHasNoValue() { Require(GuardIntact(storage)); } -void TestHeaderWithStorageIsNotImplementedOffline() { - Storage storage; - IntrusivePtrStorage value; - std::memset(storage.bytes, kFill, sizeof(storage.bytes)); - std::memset(value.bytes, kFill, sizeof(value.bytes)); - - _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseC2Ev(reinterpret_cast(storage.bytes)); - *reinterpret_cast(storage.bytes + kHeadersOffset) = reinterpret_cast(1); - - bool threw = false; - try { - _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE( - reinterpret_cast(storage.bytes), "Content-Type", - reinterpret_cast(value.bytes)); - } catch (const std::runtime_error&) { - threw = true; - } - Require(threw); - Require(value.bytes[0] == kFill && value.bytes[sizeof(IntrusivePtr)] == kFill); - - *reinterpret_cast(storage.bytes + kHeadersOffset) = nullptr; - _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseD2Ev(reinterpret_cast(storage.bytes)); - Require(GuardIntact(storage)); -} - } int main() { TestEmptyHeaderHasNoValue(); - TestHeaderWithStorageIsNotImplementedOffline(); return 0; } From dc4a192c44ef5257372e5749e29151ab66a1dceb Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Thu, 29 Oct 2026 14:06:53 +0100 Subject: [PATCH 219/612] docs(libSceNpCppWebApi): record the measured ResponseHeaderBase layout --- docs/dev/debt/libSceNpCppWebApi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceNpCppWebApi.md b/docs/dev/debt/libSceNpCppWebApi.md index d83b94f7d4..43682e4fe1 100644 --- a/docs/dev/debt/libSceNpCppWebApi.md +++ b/docs/dev/debt/libSceNpCppWebApi.md @@ -19,7 +19,7 @@ - [sce::Np::CppWebApi::Common::Vector](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671, whose dump ships no `libSceNpCppWebApi.prx`. A `Vector` handle is an `IntrusivePtr` over a shared body; bodies created by `pushBack` use a host-side layout with the reference count at 0x30, matching the adopted-body convention measured on PPSA21564. The pointer/deleter constructor adopts a title-provided body released through the stored deleter; `pushBack` on such a body throws, since its element layout is unknown. `begin`, `end`, iterators and `copyFrom` throw: the only `Vector`s the title reads back come from failed transactions, which never produce one. `empty`, `size` and `operator[]` read the host-created body honestly and throw on title-provided bodies - [sce::Np::CppWebApi::SessionManager2V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671. Offline, the factories, `PlayerSessionsApi` calls and transaction `start`/`getResponse`/`readData` fail with `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`; parameter setters, `initialize`, `terminate`, constructors and destructors succeed without keeping state, as no request is ever sent. Response-body accessors (`getSessionId`, `getPlayerSessions`, `getInvitations`, the `IsSet` queries) throw: a failed request never produces a response object - [sce::Np::CppWebApi::Matchmaking::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA21564 (86 of 92) and PPSA05510, PPSA25256, PPSA17226 and PPSA08395 for the other six; behaviour comes from the module bundled with PPSA21564/PPSA23732 (builds J03093737/J03341606), not from a console. Offline, the four `Api` calls and four factories return `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`. The four `ParameterTo*` objects keep only an initialized flag: `initialize` returns 0x80553503 for a null context or string, 0x80553502 for an unset context, 0x8055350f when already initialized and 0x80553512 for an empty `SubmitTicketRequestBody`; `terminate` returns 0x80553510. `ParameterToGetTicket::setview` returns 0x80553502 uninitialized and 0x80553503 for a null string. Other setters succeed without keeping state. Response accessors throw: a failed request produces no response. 0x80553504 is never returned; the stub stores a static pointer. -- [sce::Np::CppWebApi::UserProfile::V1, ProfanityFilter::V2, CommunicationRestrictionStatus::V3](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by an Epic Online Services module whose dump ships no `libSceNpCppWebApi.prx`. The reference-count offset 0x00 of `ResponseHeaderBase` and `WebApiFilterRequest` is assumed from the other objects, not measured. Offline, the five API calls, `WebApiFilterRequestFactory::create` and the transactions' `start`/`getResponseHeaders` fail with `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`. Response accessors, `ResponseHeaderBase::getHeaderValue` and the `Vector`/`Iterator` members over `IntrusivePtr` and `unsigned long` throw: a failed request never produces a response +- [sce::Np::CppWebApi::UserProfile::V1, ProfanityFilter::V2, CommunicationRestrictionStatus::V3](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by an Epic Online Services module whose dump ships no `libSceNpCppWebApi.prx`. The 0x10-byte `ResponseHeaderBase` (count at 0x00, header block at 0x08) comes from PPSA21564/PPSA23732 `J03093737`/`J03341606` (`0x14980`, `0x149a0`, `0x149b0`); `getHeaderValue` returns 0x80553512 when the block is null and throws when it is present. The `WebApiFilterRequest` offset 0x00 is assumed, not measured. Offline, the five API calls, `WebApiFilterRequestFactory::create` and the transactions' `start`/`getResponseHeaders` fail with `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`. Response accessors, `ResponseHeaderBase::getHeaderValue` and the `Vector`/`Iterator` members over `IntrusivePtr` and `unsigned long` throw: a failed request never produces a response - [libSceNpCppWebApi](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - unimplemented stubs have their parameter lists, const qualifiers and return types aligned with the demangled symbols confirmed by NID matching in [#8143](https://github.com/boykopovar/AnyPS5/issues/8143). They cover response-body accessors, iterators, and uninstantiated transactions in Leaderboards::V1, SessionManager::V1, InGameCatalog::V5, TitleCloudStorage::V1, and Common templates: each mangled name gives the method and parameter types, but the runtime layout of response objects and errors are unverified on console, so each call continues to throw NotImplemented_nid_no_patch. No call site is recorded for them - [sce::Np::CppWebApi::SessionManager2V1 and sce::Np::CppWebApi::InGameCatalog2V5](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - 198 further exports imported by PPSA20397, named from aerolib. API calls, factories and transaction `start`/`getResponse*` fail `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`; parameter bookkeeping succeeds without state; `IntrusivePtr`/`Vector` helpers follow the layouts above, with `Vector::empty`/`size`/`operator[]` reading host-created bodies and throwing on title-provided ones. Response accessors, iterators, `Vector::begin`/`end`/`copyFrom` and transaction `operator=` throw: a failed request produces no response object. As `start` never succeeds here, `TransactionBase::finish` and `abort` return 0x80553508 and `getId` returns 0, which is what the module bundled with PPSA21564 returns while the two words its `Transaction` constructor zeroes are still unset - [sce::Np::CppWebApi::UserProfile::V1, sce::Np::CppWebApi::CommunicationRestrictionStatus::V3 and the remaining sce::Np::CppWebApi::Common instantiations](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - 87 further exports imported by PPSA20397, plus the previously-throwing `ContainerApi::ParameterToGetContainer` lifecycle and `getContainer`. API verbs and transaction `start`/`getResponse` fail `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`; parameter and model bookkeeping (`initialize`, `terminate`, `set*`, constructors, destructors) succeeds without state. `BasicProfile`/`BasicPresence`/`PersonalDetail` response accessors, `*IsSet` queries, `Get*Response` getters, vector `begin`/`end`, iterator dereference/compare and transaction `abort`/`getId`/`operator=` throw: no response object exists offline and the model layouts are unverified From e89a05b55b7660c4d2717e4725c618a40684881d Mon Sep 17 00:00:00 2001 From: Noxy-HNR Date: Sun, 1 Nov 2026 23:17:50 +0000 Subject: [PATCH 220/612] feat(libSceJxr): implement the pixel format hash, image IID lookups, aligned allocation and IDPEmpty --- core/libs/prx/libSceJxr/CMakeLists.txt | 17 +- core/libs/prx/libSceJxr/Export.cpp | 24 --- core/libs/prx/libSceJxr/include/Api.hpp | 14 +- core/libs/prx/libSceJxr/src/GlueHelpers.cpp | 109 +++++++++++ core/libs/prx/libSceJxr/src/NativeHash.c | 6 + core/libs/tests/GuestJxrGlue.cpp | 199 ++++++++++++++++++++ docs/dev/debt/libSceJxr.md | 6 +- 7 files changed, 342 insertions(+), 33 deletions(-) create mode 100644 core/libs/prx/libSceJxr/src/GlueHelpers.cpp create mode 100644 core/libs/prx/libSceJxr/src/NativeHash.c create mode 100644 core/libs/tests/GuestJxrGlue.cpp diff --git a/core/libs/prx/libSceJxr/CMakeLists.txt b/core/libs/prx/libSceJxr/CMakeLists.txt index 5d76a6870f..d9eabc2728 100644 --- a/core/libs/prx/libSceJxr/CMakeLists.txt +++ b/core/libs/prx/libSceJxr/CMakeLists.txt @@ -1,8 +1,8 @@ -add_library(jxr_lookup_native STATIC src/NativeLookup.c) +add_library(jxr_lookup_native STATIC src/NativeLookup.c src/NativeHash.c) configure_jxr_native(jxr_lookup_native) target_include_directories(jxr_lookup_native PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(jxr_lookup_native PRIVATE jxr) -add_library(libSceJxr SHARED EXCLUDE_FROM_ALL Export.cpp src/PixelFormatLookup.cpp src/GuidExports.cpp) +add_library(libSceJxr SHARED EXCLUDE_FROM_ALL Export.cpp src/GlueHelpers.cpp src/PixelFormatLookup.cpp src/GuidExports.cpp) target_link_libraries(libSceJxr PRIVATE jxr_lookup_native) if(NOT APPLE) target_link_options(libSceJxr PRIVATE LINKER:--exclude-libs,ALL) @@ -69,3 +69,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_jxr_glue_tests ${LIBS_INCLUDE_DIR}/tests/GuestJxrGlue.cpp) + target_include_directories(guest_jxr_glue_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_jxr_glue_tests PRIVATE libSceJxr libc jxr) + configure_windows_unwind(guest_jxr_glue_tests) + add_test(NAME guest_jxr_glue COMMAND guest_jxr_glue_tests) + set_tests_properties(guest_jxr_glue PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_jxr_glue PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceJxr/Export.cpp b/core/libs/prx/libSceJxr/Export.cpp index 50f91d2edc..f18c21e59d 100644 --- a/core/libs/prx/libSceJxr/Export.cpp +++ b/core/libs/prx/libSceJxr/Export.cpp @@ -88,18 +88,6 @@ int APS5_VABI FreeDescMetadata_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI GetImageDecodeIID_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI GetImageEncodeIID_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI GetPixelFormatFromHash_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI GetPosWS_File_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -112,18 +100,10 @@ int APS5_VABI GetPosWS_Memory_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI IDPEmpty_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI InitZigzagScan_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI PKAllocAligned_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI PKAlloc_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -200,10 +180,6 @@ int APS5_VABI PKFormatConverter_Release_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI PKFreeAligned_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI PKImageDecode_Copy_nid_postfix() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceJxr/include/Api.hpp b/core/libs/prx/libSceJxr/include/Api.hpp index b57921294e..c8c78da72e 100644 --- a/core/libs/prx/libSceJxr/include/Api.hpp +++ b/core/libs/prx/libSceJxr/include/Api.hpp @@ -1,6 +1,8 @@ #pragma once #include "SceTypes.hpp" +#include +#include #include "prx/libSceJxr/GuestPixelInfo.h" #include "prx/libSceJxr/Stream.hpp" @@ -10,6 +12,12 @@ std::int64_t APS5_VABI PKFree_nid_postfix(void** memory); std::int64_t APS5_VABI PixelFormatLookup_nid_postfix(JxrPixelInfo* value, std::uint8_t mode); std::int64_t APS5_VABI readIS_nid_postfix(void* codec, Jxr::BitIo* io); std::int64_t APS5_VABI writeIS_nid_postfix(void* codec, Jxr::BitIo* io); +std::int64_t APS5_VABI GetImageDecodeIID_nid_postfix(const char* extension, const std::uint32_t** iid); +std::int64_t APS5_VABI GetImageEncodeIID_nid_postfix(const char* extension, const std::uint32_t** iid); +const void* APS5_VABI GetPixelFormatFromHash_nid_postfix(std::uint8_t hash); +std::int32_t APS5_VABI IDPEmpty_nid_postfix(void* codec); +std::int64_t APS5_VABI PKAllocAligned_nid_postfix(void** memory, std::size_t size, std::size_t alignment); +std::int64_t APS5_VABI PKFreeAligned_nid_postfix(void** memory); int APS5_VABI AllocateCodingContextDec_nid_postfix(); int APS5_VABI CloseWS_List_nid_postfix(); @@ -18,15 +26,10 @@ int APS5_VABI CreateWS_Memory_nid_postfix(); int APS5_VABI EOSWS_File_nid_postfix(); int APS5_VABI EOSWS_Memory_nid_postfix(); int APS5_VABI FreeDescMetadata_nid_postfix(); -int APS5_VABI GetImageDecodeIID_nid_postfix(); -int APS5_VABI GetImageEncodeIID_nid_postfix(); -int APS5_VABI GetPixelFormatFromHash_nid_postfix(); int APS5_VABI GetPosWS_File_nid_postfix(); int APS5_VABI GetPosWS_List_nid_postfix(); int APS5_VABI GetPosWS_Memory_nid_postfix(); -int APS5_VABI IDPEmpty_nid_postfix(); int APS5_VABI InitZigzagScan_nid_postfix(); -int APS5_VABI PKAllocAligned_nid_postfix(); int APS5_VABI PKAlloc_nid_postfix(); int APS5_VABI PKCodecFactory_CreateCodec_nid_postfix(); int APS5_VABI PKCodecFactory_CreateDecoderFromFile_nid_postfix(); @@ -46,7 +49,6 @@ int APS5_VABI PKFormatConverter_GetSourcePixelFormat_nid_postfix(); int APS5_VABI PKFormatConverter_InitializeConvert_nid_postfix(); int APS5_VABI PKFormatConverter_Initialize_nid_postfix(); int APS5_VABI PKFormatConverter_Release_nid_postfix(); -int APS5_VABI PKFreeAligned_nid_postfix(); int APS5_VABI PKImageDecode_Copy_nid_postfix(); int APS5_VABI PKImageDecode_Create_WMP_nid_postfix(); int APS5_VABI PKImageDecode_Create_nid_postfix(); diff --git a/core/libs/prx/libSceJxr/src/GlueHelpers.cpp b/core/libs/prx/libSceJxr/src/GlueHelpers.cpp new file mode 100644 index 0000000000..d360c499b8 --- /dev/null +++ b/core/libs/prx/libSceJxr/src/GlueHelpers.cpp @@ -0,0 +1,109 @@ +#include "prx/libSceJxr/libSceJxr.hpp" +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include + +extern "C" const void* JxrHashLookup_nid_no_patch(std::uint8_t hash); + +namespace { + +constexpr std::int64_t WmpErrSuccess = 0; +constexpr std::int64_t WmpErrOutOfMemory = -101; +constexpr std::int64_t WmpErrUnsupportedFormat = -106; +constexpr std::uint32_t ImageUnsupported = 100; + +struct IidInfo { + const char* extension; + const std::uint32_t* encode; + const std::uint32_t* decode; +}; + +int Lower(char value) { + const auto byte = static_cast(value); + return byte >= 'A' && byte <= 'Z' ? byte + ('a' - 'A') : byte; +} + +bool MatchesPrefix(const char* extension, const char* expected) { + auto count = std::strlen(expected); + for (; Lower(*extension) == Lower(*expected) && *extension && *expected && count; ++extension, ++expected, --count) {} + return count == 0; +} + +const IidInfo* FindIid(const char* extension, const char* function) { + if (!extension) throw std::invalid_argument(std::string(function) + " requires an extension"); + static const IidInfo table[] = { + {".jxr", &JxrGuest_IID_PKImageWmpEncode_nid_no_patch, &JxrGuest_IID_PKImageWmpDecode_nid_no_patch}, + {".wdp", &ImageUnsupported, &JxrGuest_IID_PKImageWmpDecode_nid_no_patch}, + {".hdp", &ImageUnsupported, &JxrGuest_IID_PKImageWmpDecode_nid_no_patch}, + }; + for (const auto& entry : table) + if (MatchesPrefix(extension, entry.extension)) return &entry; + return nullptr; +} + +std::int64_t StoreIid(const std::uint32_t* IidInfo::*field, const char* extension, const std::uint32_t** iid, + const char* function) { + const auto* info = FindIid(extension, function); + if (!info) return WmpErrUnsupportedFormat; + if (!iid) throw std::invalid_argument(std::string(function) + " requires an output for a known extension"); + *iid = info->*field; + return WmpErrSuccess; +} + +} // namespace + +extern "C" { + +const void* APS5_VABI GetPixelFormatFromHash_nid_postfix(std::uint8_t hash) { + return JxrHashLookup_nid_no_patch(hash); +} + +std::int64_t APS5_VABI GetImageEncodeIID_nid_postfix(const char* extension, const std::uint32_t** iid) { + return StoreIid(&IidInfo::encode, extension, iid, "GetImageEncodeIID"); +} + +std::int64_t APS5_VABI GetImageDecodeIID_nid_postfix(const char* extension, const std::uint32_t** iid) { + return StoreIid(&IidInfo::decode, extension, iid, "GetImageDecodeIID"); +} + +std::int64_t APS5_VABI PKAllocAligned_nid_postfix(void** memory, std::size_t size, std::size_t alignment) { + if (!memory) throw std::invalid_argument("PKAllocAligned requires an output pointer"); + *memory = nullptr; + if (alignment == 0) throw std::invalid_argument("PKAllocAligned: zero alignment divides by zero"); + constexpr auto maximum = std::numeric_limits::max(); + if (size > maximum - sizeof(void*) || alignment - 1 > maximum - sizeof(void*) - size) + throw std::length_error("PKAllocAligned: block size overflows size_t"); + const auto blockSize = size + sizeof(void*) + alignment - 1; + auto* original = static_cast(calloc_nid_postfix(1, blockSize)); + if (!original) return WmpErrOutOfMemory; + auto correction = alignment - reinterpret_cast(original) % alignment; + if (correction < sizeof(void*)) correction += alignment; + if (correction < sizeof(void*)) { + free_nid_postfix(original); + throw std::invalid_argument("PKAllocAligned: the alignment leaves no room for the original pointer"); + } + auto* aligned = original + correction; + std::memcpy(aligned - sizeof(void*), &original, sizeof(original)); + *memory = aligned; + return WmpErrSuccess; +} + +std::int64_t APS5_VABI PKFreeAligned_nid_postfix(void** memory) { + if (memory && *memory) { + void* original = nullptr; + std::memcpy(&original, static_cast(*memory) - sizeof(void*), sizeof(original)); + free_nid_postfix(original); + *memory = nullptr; + } + return WmpErrSuccess; +} + +std::int32_t APS5_VABI IDPEmpty_nid_postfix(void*) { + return 0; +} + +} diff --git a/core/libs/prx/libSceJxr/src/NativeHash.c b/core/libs/prx/libSceJxr/src/NativeHash.c new file mode 100644 index 0000000000..7ce57bf5a4 --- /dev/null +++ b/core/libs/prx/libSceJxr/src/NativeHash.c @@ -0,0 +1,6 @@ +#include "strcodec.h" +#include "JXRGlue.h" + +const void* JxrHashLookup_nid_no_patch(U8 hash) { + return GetPixelFormatFromHash(hash); +} diff --git a/core/libs/tests/GuestJxrGlue.cpp b/core/libs/tests/GuestJxrGlue.cpp new file mode 100644 index 0000000000..4b2961c1d5 --- /dev/null +++ b/core/libs/tests/GuestJxrGlue.cpp @@ -0,0 +1,199 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceJxr/include/Api.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" const void* GetPixelFormatFromHash(unsigned char hash); + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +void* lastCalloc = nullptr; +std::size_t lastCount = 0; +std::size_t lastSize = 0; +void* lastFreed = nullptr; +int frees = 0; +bool failCalloc = false; + +void* APS5_VABI Allocate(std::size_t size) { return std::malloc(size); } +void APS5_VABI Free(void* pointer) { + if (pointer != nullptr) { + lastFreed = pointer; + ++frees; + } + std::free(pointer); +} +void* APS5_VABI Reallocate(void* pointer, std::size_t size) { return std::realloc(pointer, size); } +void* APS5_VABI Calloc(std::size_t count, std::size_t size) { + lastCount = count; + lastSize = size; + lastCalloc = failCalloc ? nullptr : std::calloc(count, size); + return lastCalloc; +} +void* APS5_VABI Align(std::size_t, std::size_t size) { return std::malloc(size); } +void* APS5_VABI Realign(void* pointer, std::size_t size, std::size_t) { return std::realloc(pointer, size); } +int APS5_VABI PosixAlign(void** pointer, std::size_t, std::size_t size) { + *pointer = std::malloc(size); + return *pointer ? 0 : 12; +} + +void InitializeHeap() { + alignas(16) static std::array process{}; + alignas(16) static std::array libc{}; + alignas(16) static std::array replacement{}; + const auto write = [](auto& data, std::size_t offset, auto value) { + std::memcpy(data.data() + offset, &value, sizeof(value)); + }; + write(process, 0, std::uint64_t{0x40}); + write(process, 8, std::uint32_t{0x4942524f}); + write(process, 0x38, libc.data()); + write(libc, 0, std::uint64_t{0x38}); + write(libc, 0x30, replacement.data()); + write(replacement, 0, std::uint64_t{0x78}); + write(replacement, 8, std::uint64_t{2}); + write(replacement, 0x20, reinterpret_cast(&Allocate)); + write(replacement, 0x28, reinterpret_cast(&Free)); + write(replacement, 0x30, reinterpret_cast(&Calloc)); + write(replacement, 0x38, reinterpret_cast(&Reallocate)); + write(replacement, 0x40, reinterpret_cast(&Align)); + write(replacement, 0x48, reinterpret_cast(&Realign)); + write(replacement, 0x50, reinterpret_cast(&PosixAlign)); + ApplicationHeapInitialize_nid_no_patch(process.data()); +} + +void TestPixelFormatHash() { + int found = 0; + for (int hash = 0; hash < 256; ++hash) { + const void* expected = GetPixelFormatFromHash(static_cast(hash)); + const void* actual = GetPixelFormatFromHash_nid_postfix(static_cast(hash)); + Require((expected == nullptr) == (actual == nullptr)); + if (!actual) continue; + ++found; + Require(std::memcmp(expected, actual, 16) == 0); + Require(static_cast(actual)[15] == hash); + } + Require(found > 0 && found < 256); + const std::uint8_t gray8[16] = {0x24, 0xc3, 0xdd, 0x6f, 0x03, 0x4e, 0xfe, 0x4b, + 0xb1, 0x85, 0x3d, 0x77, 0x76, 0x8d, 0xc9, 0x08}; + const void* gray = GetPixelFormatFromHash_nid_postfix(0x08); + Require(gray && std::memcmp(gray, gray8, sizeof(gray8)) == 0); +} + +template +void RequireThrows(Function function) { + bool threw = false; + try { + function(); + } catch (const std::invalid_argument&) { + threw = true; + } catch (const std::length_error&) { + threw = true; + } + Require(threw); +} + +void TestImageIids() { + const std::uint32_t sentinel = 7; + const std::uint32_t* iid = &sentinel; + Require(GetImageEncodeIID_nid_postfix(".jxr", &iid) == 0 && *iid == 101); + Require(GetImageDecodeIID_nid_postfix(".jxr", &iid) == 0 && *iid == 201); + Require(GetImageEncodeIID_nid_postfix(".JXR", &iid) == 0 && *iid == 101); + Require(GetImageEncodeIID_nid_postfix(".jxr.tmp", &iid) == 0 && *iid == 101); + Require(GetImageEncodeIID_nid_postfix(".wdp", &iid) == 0 && *iid == 100); + Require(GetImageDecodeIID_nid_postfix(".WDP", &iid) == 0 && *iid == 201); + Require(GetImageEncodeIID_nid_postfix(".hdp", &iid) == 0 && *iid == 100); + Require(GetImageDecodeIID_nid_postfix(".Hdp", &iid) == 0 && *iid == 201); + const std::uint32_t* encode = nullptr; + const std::uint32_t* again = nullptr; + Require(GetImageEncodeIID_nid_postfix(".jxr", &encode) == 0); + Require(GetImageEncodeIID_nid_postfix(".jxr", &again) == 0 && encode == again); + for (const char* extension : {".png", ".jx", "", "jxr", ".j\xc3r", ".jpg"}) { + iid = &sentinel; + Require(GetImageEncodeIID_nid_postfix(extension, &iid) == -106 && iid == &sentinel); + Require(GetImageDecodeIID_nid_postfix(extension, &iid) == -106 && iid == &sentinel); + Require(GetImageEncodeIID_nid_postfix(extension, nullptr) == -106); + Require(GetImageDecodeIID_nid_postfix(extension, nullptr) == -106); + } + RequireThrows([] { GetImageEncodeIID_nid_postfix(".jxr", nullptr); }); + RequireThrows([] { GetImageDecodeIID_nid_postfix(".wdp", nullptr); }); + RequireThrows([] { + const std::uint32_t* out = nullptr; + GetImageEncodeIID_nid_postfix(nullptr, &out); + }); + RequireThrows([] { + const std::uint32_t* out = nullptr; + GetImageDecodeIID_nid_postfix(nullptr, &out); + }); +} + +void TestAlignedAllocation() { + for (std::size_t alignment : {std::size_t{4}, std::size_t{8}, std::size_t{16}, std::size_t{64}, + std::size_t{4096}}) { + for (std::size_t size : {std::size_t{0}, std::size_t{1}, std::size_t{100}}) { + void* memory = reinterpret_cast(std::uintptr_t{1}); + const int before = frees; + Require(PKAllocAligned_nid_postfix(&memory, size, alignment) == 0); + Require(memory != nullptr && lastCalloc != nullptr); + Require(lastCount == 1 && lastSize == size + sizeof(void*) + alignment - 1); + const auto address = reinterpret_cast(memory); + Require(address % alignment == 0); + const auto offset = address - reinterpret_cast(lastCalloc); + Require(offset >= sizeof(void*) && offset + size <= lastSize); + void* stored = nullptr; + std::memcpy(&stored, static_cast(memory) - sizeof(void*), sizeof(stored)); + Require(stored == lastCalloc); + for (std::size_t index = 0; index < size; ++index) + Require(static_cast(memory)[index] == 0); + void* const original = lastCalloc; + Require(PKFreeAligned_nid_postfix(&memory) == 0); + Require(memory == nullptr && frees == before + 1 && lastFreed == original); + } + } + void* memory = reinterpret_cast(std::uintptr_t{1}); + failCalloc = true; + Require(PKAllocAligned_nid_postfix(&memory, 32, 16) == -101 && memory == nullptr); + failCalloc = false; + const int before = frees; + Require(PKFreeAligned_nid_postfix(&memory) == 0 && frees == before); + Require(PKFreeAligned_nid_postfix(nullptr) == 0 && frees == before); + RequireThrows([] { PKAllocAligned_nid_postfix(nullptr, 32, 16); }); + memory = reinterpret_cast(std::uintptr_t{1}); + RequireThrows([&memory] { PKAllocAligned_nid_postfix(&memory, 32, 0); }); + Require(memory == nullptr); + for (std::size_t alignment : {std::size_t{1}, std::size_t{2}}) { + const int before = frees; + memory = reinterpret_cast(std::uintptr_t{1}); + RequireThrows([&memory, alignment] { PKAllocAligned_nid_postfix(&memory, 32, alignment); }); + Require(memory == nullptr && frees == before + 1 && lastFreed == lastCalloc); + } + constexpr auto maximum = std::numeric_limits::max(); + RequireThrows([] { + void* out = nullptr; + PKAllocAligned_nid_postfix(&out, maximum - 4, 16); + }); + RequireThrows([] { + void* out = nullptr; + PKAllocAligned_nid_postfix(&out, 16, maximum - 8); + }); +} + +} + +int main() { + InitializeHeap(); + TestPixelFormatHash(); + TestImageIids(); + TestAlignedAllocation(); + Require(IDPEmpty_nid_postfix(nullptr) == 0); + int codec = 0; + Require(IDPEmpty_nid_postfix(&codec) == 0 && codec == 0); + return 0; +} diff --git a/docs/dev/debt/libSceJxr.md b/docs/dev/debt/libSceJxr.md index 249c82055a..9f443a47dc 100644 --- a/docs/dev/debt/libSceJxr.md +++ b/docs/dev/debt/libSceJxr.md @@ -12,4 +12,8 @@ - [readIS and writeIS](../../../core/libs/prx/libSceJxr/Export.cpp) use the non-ARM, LP64 `BitIOInfo` and `WMPStream` ABI and packet algorithm from [Microsoft's JPEG XR reference implementation](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L1090-L1177). Console structure layouts and execution remain unverified. Profiling callbacks are omitted as in the reference Unix build with `DISABLE_PERF_MEASUREMENT`; the codec argument is unused. Where the reference uses an invalid pointer, `std::invalid_argument` is thrown instead: a null bit state, stream or callback, a read-only read packet, and a null packet when a read copies its shadow or a successful write offsets it; a null packet still reaches the callback first. No codec is instantiated by these helpers; callers must supply the reference ring buffer and guest-ABI stream callbacks. -- The 132 additional exports in [Export.cpp](../../../core/libs/prx/libSceJxr/Export.cpp) are present in the pinned firmware 13.60 `libSceJxr` export inventory. Their parameter lists, return types and behavior are unverified. Parameterless `int` declarations are import-resolution placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. The `_nid_postfix` aliases keep the guest bindings distinct from private native helpers. No runtime functionality is implemented. +- [GetPixelFormatFromHash, GetImageEncodeIID and GetImageDecodeIID](../../../core/libs/prx/libSceJxr/src/GlueHelpers.cpp) follow jxrlib [JXRGlue.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/jxrgluelib/JXRGlue.c#L256-L329); that the console's module is jxrlib with these prototypes is inferred from the export names, not checked against the console. The hash lookup calls the pinned jxrlib through a C bridge and, like `PixelFormatLookup`, borrows the bridge's read-only GUID instead of the module's. `.wdp` and `.hdp` encoding return a private `IID_PKImageUnsupported` (100), which the module does not export. A null extension, or a null output for a known extension, throws where jxrlib dereferences it. Closes when: the console's functions are disassembled or measured on a PS5 + +- [PKAllocAligned, PKFreeAligned and IDPEmpty](../../../core/libs/prx/libSceJxr/src/GlueHelpers.cpp) follow jxrlib [JXRGlue.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/jxrgluelib/JXRGlue.c#L64-L102) and [strcodec.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L239-L244), unverified on a console. That the allocation goes through the guest `calloc` and `free` is inferred, not checked. A null `PKAllocAligned` output, a zero alignment, a block size over `SIZE_MAX` and an alignment that leaves fewer than 8 bytes for the original pointer throw where jxrlib faults, divides by zero, wraps or writes before the block. Closes when: the console's functions are disassembled or measured on a PS5 + +- The 126 additional exports in [Export.cpp](../../../core/libs/prx/libSceJxr/Export.cpp) are present in the pinned firmware 13.60 `libSceJxr` export inventory. Their parameter lists, return types and behavior are unverified. Parameterless `int` declarations are import-resolution placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. The `_nid_postfix` aliases keep the guest bindings distinct from private native helpers. No runtime functionality is implemented. From 1c522615fe47482e4208b2425ad0fee80637457d Mon Sep 17 00:00:00 2001 From: Noxy-HNR Date: Mon, 2 Nov 2026 01:00:33 +0000 Subject: [PATCH 221/612] refactor(libSceJxr): build the glue helpers from the pinned jxrlib Address review: the pixel-format hash, image IID lookups, aligned allocation and IDPEmpty now call jxrlib's JXRGlue.c and strcodec.c from 3rdparty/jxrlib through a C bridge instead of adapted copies in core/. A dedicated build of JXRGlue.c routes its calloc and free to the guest heap; the C++ adapters keep only the guest ABI, argument checks and IID object mapping. Alignments below 8 are now rejected up front, since jxrlib would write the original pointer before the block. --- core/libs/prx/libSceJxr/CMakeLists.txt | 11 ++- core/libs/prx/libSceJxr/src/GlueHelpers.cpp | 104 ++++++++------------ core/libs/prx/libSceJxr/src/GuestHeap.h | 15 +++ core/libs/prx/libSceJxr/src/NativeGlue.c | 36 +++++++ core/libs/prx/libSceJxr/src/NativeHash.c | 6 -- core/libs/tests/GuestJxrGlue.cpp | 9 +- docs/dev/debt/libSceJxr.md | 4 +- 7 files changed, 108 insertions(+), 77 deletions(-) create mode 100644 core/libs/prx/libSceJxr/src/GuestHeap.h create mode 100644 core/libs/prx/libSceJxr/src/NativeGlue.c delete mode 100644 core/libs/prx/libSceJxr/src/NativeHash.c diff --git a/core/libs/prx/libSceJxr/CMakeLists.txt b/core/libs/prx/libSceJxr/CMakeLists.txt index d9eabc2728..b950f2b27d 100644 --- a/core/libs/prx/libSceJxr/CMakeLists.txt +++ b/core/libs/prx/libSceJxr/CMakeLists.txt @@ -1,9 +1,16 @@ -add_library(jxr_lookup_native STATIC src/NativeLookup.c src/NativeHash.c) +add_library(jxr_lookup_native STATIC src/NativeLookup.c) configure_jxr_native(jxr_lookup_native) target_include_directories(jxr_lookup_native PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(jxr_lookup_native PRIVATE jxr) add_library(libSceJxr SHARED EXCLUDE_FROM_ALL Export.cpp src/GlueHelpers.cpp src/PixelFormatLookup.cpp src/GuidExports.cpp) -target_link_libraries(libSceJxr PRIVATE jxr_lookup_native) +# Pinned jxrlib JXRGlue.c with its allocator routed to the guest heap, plus the C bridge +# GlueHelpers.cpp adapts; both sit in one archive ahead of jxr so this copy is the one linked. +add_library(jxr_glue_guest STATIC ${JXR_SOURCE_DIR}/jxrgluelib/JXRGlue.c src/NativeGlue.c) +configure_jxr_native(jxr_glue_guest) +set_source_files_properties(${JXR_SOURCE_DIR}/jxrgluelib/JXRGlue.c TARGET_DIRECTORY jxr_glue_guest + PROPERTIES COMPILE_OPTIONS "-include;${CMAKE_CURRENT_SOURCE_DIR}/src/GuestHeap.h") +target_link_libraries(jxr_glue_guest PRIVATE jxr) +target_link_libraries(libSceJxr PRIVATE jxr_glue_guest jxr_lookup_native) if(NOT APPLE) target_link_options(libSceJxr PRIVATE LINKER:--exclude-libs,ALL) endif() diff --git a/core/libs/prx/libSceJxr/src/GlueHelpers.cpp b/core/libs/prx/libSceJxr/src/GlueHelpers.cpp index d360c499b8..4f13804117 100644 --- a/core/libs/prx/libSceJxr/src/GlueHelpers.cpp +++ b/core/libs/prx/libSceJxr/src/GlueHelpers.cpp @@ -2,56 +2,49 @@ #include "prx/libc/include/Api.hpp" #include #include -#include #include #include #include -extern "C" const void* JxrHashLookup_nid_no_patch(std::uint8_t hash); +// Thin guest adapters over the pinned jxrlib built from 3rdparty/jxrlib (see NativeGlue.c and the +// jxr_glue_guest target): they check arguments jxrlib would fault on, map its IID values to the +// module's exported IID objects and route its allocator to the guest heap. + +extern "C" { +const void* JxrHashLookup_nid_no_patch(std::uint8_t hash); +std::int64_t JxrEncodeIid_nid_no_patch(const char* extension, std::uint32_t* value); +std::int64_t JxrDecodeIid_nid_no_patch(const char* extension, std::uint32_t* value); +std::int64_t JxrAllocAligned_nid_no_patch(void** memory, std::size_t size, std::size_t alignment); +std::int64_t JxrFreeAligned_nid_no_patch(void** memory); +std::int32_t JxrIdpEmpty_nid_no_patch(void* codec); + +void* JxrGuestCalloc_nid_no_patch(std::size_t count, std::size_t size) { + return calloc_nid_postfix(count, size); +} + +void JxrGuestFree_nid_no_patch(void* memory) { + free_nid_postfix(memory); +} +} namespace { -constexpr std::int64_t WmpErrSuccess = 0; -constexpr std::int64_t WmpErrOutOfMemory = -101; -constexpr std::int64_t WmpErrUnsupportedFormat = -106; constexpr std::uint32_t ImageUnsupported = 100; -struct IidInfo { - const char* extension; - const std::uint32_t* encode; - const std::uint32_t* decode; -}; - -int Lower(char value) { - const auto byte = static_cast(value); - return byte >= 'A' && byte <= 'Z' ? byte + ('a' - 'A') : byte; -} - -bool MatchesPrefix(const char* extension, const char* expected) { - auto count = std::strlen(expected); - for (; Lower(*extension) == Lower(*expected) && *extension && *expected && count; ++extension, ++expected, --count) {} - return count == 0; -} - -const IidInfo* FindIid(const char* extension, const char* function) { +std::int64_t StoreIid(std::int64_t (*lookup)(const char*, std::uint32_t*), const char* extension, + const std::uint32_t** iid, const char* function) { if (!extension) throw std::invalid_argument(std::string(function) + " requires an extension"); - static const IidInfo table[] = { - {".jxr", &JxrGuest_IID_PKImageWmpEncode_nid_no_patch, &JxrGuest_IID_PKImageWmpDecode_nid_no_patch}, - {".wdp", &ImageUnsupported, &JxrGuest_IID_PKImageWmpDecode_nid_no_patch}, - {".hdp", &ImageUnsupported, &JxrGuest_IID_PKImageWmpDecode_nid_no_patch}, - }; - for (const auto& entry : table) - if (MatchesPrefix(extension, entry.extension)) return &entry; - return nullptr; -} - -std::int64_t StoreIid(const std::uint32_t* IidInfo::*field, const char* extension, const std::uint32_t** iid, - const char* function) { - const auto* info = FindIid(extension, function); - if (!info) return WmpErrUnsupportedFormat; + std::uint32_t value = 0; + const auto result = lookup(extension, &value); + if (result < 0) return result; if (!iid) throw std::invalid_argument(std::string(function) + " requires an output for a known extension"); - *iid = info->*field; - return WmpErrSuccess; + switch (value) { + case 101: *iid = &JxrGuest_IID_PKImageWmpEncode_nid_no_patch; break; + case 201: *iid = &JxrGuest_IID_PKImageWmpDecode_nid_no_patch; break; + case ImageUnsupported: *iid = &ImageUnsupported; break; + default: throw std::logic_error(std::string(function) + ": jxrlib returned an unknown IID " + std::to_string(value) + " for " + extension); + } + return result; } } // namespace @@ -63,47 +56,30 @@ const void* APS5_VABI GetPixelFormatFromHash_nid_postfix(std::uint8_t hash) { } std::int64_t APS5_VABI GetImageEncodeIID_nid_postfix(const char* extension, const std::uint32_t** iid) { - return StoreIid(&IidInfo::encode, extension, iid, "GetImageEncodeIID"); + return StoreIid(JxrEncodeIid_nid_no_patch, extension, iid, "GetImageEncodeIID"); } std::int64_t APS5_VABI GetImageDecodeIID_nid_postfix(const char* extension, const std::uint32_t** iid) { - return StoreIid(&IidInfo::decode, extension, iid, "GetImageDecodeIID"); + return StoreIid(JxrDecodeIid_nid_no_patch, extension, iid, "GetImageDecodeIID"); } std::int64_t APS5_VABI PKAllocAligned_nid_postfix(void** memory, std::size_t size, std::size_t alignment) { if (!memory) throw std::invalid_argument("PKAllocAligned requires an output pointer"); *memory = nullptr; - if (alignment == 0) throw std::invalid_argument("PKAllocAligned: zero alignment divides by zero"); + if (alignment < sizeof(void*)) + throw std::invalid_argument("PKAllocAligned: alignments below 8 can leave no room for the original pointer"); constexpr auto maximum = std::numeric_limits::max(); if (size > maximum - sizeof(void*) || alignment - 1 > maximum - sizeof(void*) - size) throw std::length_error("PKAllocAligned: block size overflows size_t"); - const auto blockSize = size + sizeof(void*) + alignment - 1; - auto* original = static_cast(calloc_nid_postfix(1, blockSize)); - if (!original) return WmpErrOutOfMemory; - auto correction = alignment - reinterpret_cast(original) % alignment; - if (correction < sizeof(void*)) correction += alignment; - if (correction < sizeof(void*)) { - free_nid_postfix(original); - throw std::invalid_argument("PKAllocAligned: the alignment leaves no room for the original pointer"); - } - auto* aligned = original + correction; - std::memcpy(aligned - sizeof(void*), &original, sizeof(original)); - *memory = aligned; - return WmpErrSuccess; + return JxrAllocAligned_nid_no_patch(memory, size, alignment); } std::int64_t APS5_VABI PKFreeAligned_nid_postfix(void** memory) { - if (memory && *memory) { - void* original = nullptr; - std::memcpy(&original, static_cast(*memory) - sizeof(void*), sizeof(original)); - free_nid_postfix(original); - *memory = nullptr; - } - return WmpErrSuccess; + return JxrFreeAligned_nid_no_patch(memory); } -std::int32_t APS5_VABI IDPEmpty_nid_postfix(void*) { - return 0; +std::int32_t APS5_VABI IDPEmpty_nid_postfix(void* codec) { + return JxrIdpEmpty_nid_no_patch(codec); } } diff --git a/core/libs/prx/libSceJxr/src/GuestHeap.h b/core/libs/prx/libSceJxr/src/GuestHeap.h new file mode 100644 index 0000000000..0270e05b97 --- /dev/null +++ b/core/libs/prx/libSceJxr/src/GuestHeap.h @@ -0,0 +1,15 @@ +#ifndef CORE_LIBS_PRX_LIBSCEJXR_SRC_GUESTHEAP_H +#define CORE_LIBS_PRX_LIBSCEJXR_SRC_GUESTHEAP_H + +/* Force-included into the pinned jxrlib JXRGlue.c built for libSceJxr, so its PKAlloc, + PKFree, PKAllocAligned and PKFreeAligned allocate from the guest heap. */ +#include +#include + +void* JxrGuestCalloc_nid_no_patch(size_t count, size_t size); +void JxrGuestFree_nid_no_patch(void* memory); + +#define calloc JxrGuestCalloc_nid_no_patch +#define free JxrGuestFree_nid_no_patch + +#endif diff --git a/core/libs/prx/libSceJxr/src/NativeGlue.c b/core/libs/prx/libSceJxr/src/NativeGlue.c new file mode 100644 index 0000000000..7718a39ac6 --- /dev/null +++ b/core/libs/prx/libSceJxr/src/NativeGlue.c @@ -0,0 +1,36 @@ +#include "strcodec.h" +#include "JXRGlue.h" +#include + +const void* JxrHashLookup_nid_no_patch(U8 hash) { + return GetPixelFormatFromHash(hash); +} + +typedef ERR (*IidLookup)(const char*, const PKIID**); + +static int64_t StoreIid(IidLookup lookup, const char* extension, uint32_t* value) { + const PKIID* iid = NULL; + const ERR result = lookup(extension, &iid); + if (result >= 0 && iid) *value = *iid; + return result; +} + +int64_t JxrEncodeIid_nid_no_patch(const char* extension, uint32_t* value) { + return StoreIid(GetImageEncodeIID, extension, value); +} + +int64_t JxrDecodeIid_nid_no_patch(const char* extension, uint32_t* value) { + return StoreIid(GetImageDecodeIID, extension, value); +} + +int64_t JxrAllocAligned_nid_no_patch(void** memory, size_t size, size_t alignment) { + return PKAllocAligned(memory, size, alignment); +} + +int64_t JxrFreeAligned_nid_no_patch(void** memory) { + return PKFreeAligned(memory); +} + +int32_t JxrIdpEmpty_nid_no_patch(void* codec) { + return IDPEmpty((CWMImageStrCodec*)codec); +} diff --git a/core/libs/prx/libSceJxr/src/NativeHash.c b/core/libs/prx/libSceJxr/src/NativeHash.c deleted file mode 100644 index 7ce57bf5a4..0000000000 --- a/core/libs/prx/libSceJxr/src/NativeHash.c +++ /dev/null @@ -1,6 +0,0 @@ -#include "strcodec.h" -#include "JXRGlue.h" - -const void* JxrHashLookup_nid_no_patch(U8 hash) { - return GetPixelFormatFromHash(hash); -} diff --git a/core/libs/tests/GuestJxrGlue.cpp b/core/libs/tests/GuestJxrGlue.cpp index 4b2961c1d5..7c4031147a 100644 --- a/core/libs/tests/GuestJxrGlue.cpp +++ b/core/libs/tests/GuestJxrGlue.cpp @@ -135,7 +135,7 @@ void TestImageIids() { } void TestAlignedAllocation() { - for (std::size_t alignment : {std::size_t{4}, std::size_t{8}, std::size_t{16}, std::size_t{64}, + for (std::size_t alignment : {std::size_t{8}, std::size_t{16}, std::size_t{24}, std::size_t{64}, std::size_t{4096}}) { for (std::size_t size : {std::size_t{0}, std::size_t{1}, std::size_t{100}}) { void* memory = reinterpret_cast(std::uintptr_t{1}); @@ -168,11 +168,14 @@ void TestAlignedAllocation() { memory = reinterpret_cast(std::uintptr_t{1}); RequireThrows([&memory] { PKAllocAligned_nid_postfix(&memory, 32, 0); }); Require(memory == nullptr); - for (std::size_t alignment : {std::size_t{1}, std::size_t{2}}) { + for (std::size_t alignment : {std::size_t{1}, std::size_t{2}, std::size_t{4}, std::size_t{7}}) { const int before = frees; + void* const previous = lastCalloc; + lastCalloc = nullptr; memory = reinterpret_cast(std::uintptr_t{1}); RequireThrows([&memory, alignment] { PKAllocAligned_nid_postfix(&memory, 32, alignment); }); - Require(memory == nullptr && frees == before + 1 && lastFreed == lastCalloc); + Require(memory == nullptr && frees == before && lastCalloc == nullptr); + lastCalloc = previous; } constexpr auto maximum = std::numeric_limits::max(); RequireThrows([] { diff --git a/docs/dev/debt/libSceJxr.md b/docs/dev/debt/libSceJxr.md index 9f443a47dc..98831643ea 100644 --- a/docs/dev/debt/libSceJxr.md +++ b/docs/dev/debt/libSceJxr.md @@ -12,8 +12,8 @@ - [readIS and writeIS](../../../core/libs/prx/libSceJxr/Export.cpp) use the non-ARM, LP64 `BitIOInfo` and `WMPStream` ABI and packet algorithm from [Microsoft's JPEG XR reference implementation](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L1090-L1177). Console structure layouts and execution remain unverified. Profiling callbacks are omitted as in the reference Unix build with `DISABLE_PERF_MEASUREMENT`; the codec argument is unused. Where the reference uses an invalid pointer, `std::invalid_argument` is thrown instead: a null bit state, stream or callback, a read-only read packet, and a null packet when a read copies its shadow or a successful write offsets it; a null packet still reaches the callback first. No codec is instantiated by these helpers; callers must supply the reference ring buffer and guest-ABI stream callbacks. -- [GetPixelFormatFromHash, GetImageEncodeIID and GetImageDecodeIID](../../../core/libs/prx/libSceJxr/src/GlueHelpers.cpp) follow jxrlib [JXRGlue.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/jxrgluelib/JXRGlue.c#L256-L329); that the console's module is jxrlib with these prototypes is inferred from the export names, not checked against the console. The hash lookup calls the pinned jxrlib through a C bridge and, like `PixelFormatLookup`, borrows the bridge's read-only GUID instead of the module's. `.wdp` and `.hdp` encoding return a private `IID_PKImageUnsupported` (100), which the module does not export. A null extension, or a null output for a known extension, throws where jxrlib dereferences it. Closes when: the console's functions are disassembled or measured on a PS5 +- [GetPixelFormatFromHash, GetImageEncodeIID and GetImageDecodeIID](../../../core/libs/prx/libSceJxr/src/GlueHelpers.cpp) call jxrlib's [JXRGlue.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/jxrgluelib/JXRGlue.c#L256-L329), built from `3rdparty/jxrlib`, through a C bridge. That the console's module is jxrlib with these prototypes is inferred from the export names. The hash lookup, like `PixelFormatLookup`, borrows the bridge's read-only GUID. IID values map to the module's exported IID objects; `.wdp` and `.hdp` encoding return a private `IID_PKImageUnsupported` (100), which the module does not export. A null extension, or a null output for a known extension, throws where jxrlib dereferences it. Closes when: the console's functions are disassembled or measured on a PS5 -- [PKAllocAligned, PKFreeAligned and IDPEmpty](../../../core/libs/prx/libSceJxr/src/GlueHelpers.cpp) follow jxrlib [JXRGlue.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/jxrgluelib/JXRGlue.c#L64-L102) and [strcodec.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L239-L244), unverified on a console. That the allocation goes through the guest `calloc` and `free` is inferred, not checked. A null `PKAllocAligned` output, a zero alignment, a block size over `SIZE_MAX` and an alignment that leaves fewer than 8 bytes for the original pointer throw where jxrlib faults, divides by zero, wraps or writes before the block. Closes when: the console's functions are disassembled or measured on a PS5 +- [PKAllocAligned, PKFreeAligned and IDPEmpty](../../../core/libs/prx/libSceJxr/src/GlueHelpers.cpp) call jxrlib's [JXRGlue.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/jxrgluelib/JXRGlue.c#L64-L102) and [strcodec.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L239-L244), with that `JXRGlue.c` copy's `calloc` and `free` routed to the guest heap; that the console allocates there is inferred. A null output, an alignment below 8 and a block size over `SIZE_MAX` throw before jxrlib can fault, write before the block or wrap. Closes when: the console's functions are disassembled or measured on a PS5 - The 126 additional exports in [Export.cpp](../../../core/libs/prx/libSceJxr/Export.cpp) are present in the pinned firmware 13.60 `libSceJxr` export inventory. Their parameter lists, return types and behavior are unverified. Parameterless `int` declarations are import-resolution placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. The `_nid_postfix` aliases keep the guest bindings distinct from private native helpers. No runtime functionality is implemented. From 7df215ed5820690de82fa7bb8ac857ad138c0e37 Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Mon, 2 Nov 2026 01:29:20 +0100 Subject: [PATCH 222/612] feat(libSceNpSessionSignaling): implement context properties and state queries Recover the signatures from the 11.00 module wrappers and implement InitializeWithPort, the per-context custom property, the peer-address group lookup, the internal callback registration, the memory information and the connection statistics. The host stores the pool size and the property, reports zero usage and statistics for the offline runtime, and still throws for the signaling paths that need the network. A destroyed context slot clears the property and both callback pointers before it is reused, so a new context starts from a fresh state. --- .../prx/libSceNpSessionSignaling/Export.cpp | 78 ++++++++++++++----- .../libSceNpSessionSignaling/include/Api.hpp | 10 +-- core/libs/tests/GuestNpSessionSignaling.cpp | 61 +++++++++++++++ docs/dev/debt/libSceNpSessionSignaling.md | 14 +++- 4 files changed, 136 insertions(+), 27 deletions(-) diff --git a/core/libs/prx/libSceNpSessionSignaling/Export.cpp b/core/libs/prx/libSceNpSessionSignaling/Export.cpp index a325235968..caae6447b6 100644 --- a/core/libs/prx/libSceNpSessionSignaling/Export.cpp +++ b/core/libs/prx/libSceNpSessionSignaling/Export.cpp @@ -20,10 +20,14 @@ struct SessionSignalingContext { std::uint32_t id; std::int32_t userId; bool active; + std::uint32_t customProperty; + void* internalCallback; + void* internalCallbackUserData; }; static std::mutex g_mutex; static bool g_initialized = false; +static std::uint64_t g_poolSize = 0; static std::array g_contexts{}; static_assert(sizeof(SessionSignalingMemoryInfo) == 24); @@ -75,6 +79,9 @@ static int CreateContext(const SessionSignalingContextParam for (auto& context : g_contexts) { if (!context.active) { context.userId = param->userId; + context.customProperty = 0; + context.internalCallback = nullptr; + context.internalCallbackUserData = nullptr; context.active = true; *contextId = context.id; return 0; @@ -85,17 +92,28 @@ static int CreateContext(const SessionSignalingContextParam extern "C" { -int APS5_VABI sceNpSessionSignalingInitialize(const SessionSignalingInitParam* param) { +static int InitializePort(const SessionSignalingInitParam* param, std::uint32_t port) { std::lock_guard lock(g_mutex); if (g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_ALREADY_INITIALIZED; if (!param || !param->poolSize) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; for (std::uint32_t index = 0; index < g_contexts.size(); ++index) { - g_contexts[index] = {1000 + index, 0, false}; + g_contexts[index] = {1000 + index, 0, false, 0, nullptr, nullptr}; } + (void)port; + g_poolSize = param->poolSize; g_initialized = true; return 0; } +int APS5_VABI sceNpSessionSignalingInitialize(const SessionSignalingInitParam* param) { + return InitializePort(param, 0x217b); +} + +int APS5_VABI sceNpSessionSignalingInitializeWithPort(const SessionSignalingInitParam* param, std::uint32_t port, std::uint32_t reserved) { + (void)reserved; + return InitializePort(param, port); +} + int APS5_VABI sceNpSessionSignalingActivateSession(std::uint32_t contextId, const char* sessionId, std::uint32_t sessionType, const SessionSignalingSessionParam* param, std::uint32_t* groupId) { std::lock_guard lock(g_mutex); if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; @@ -125,6 +143,9 @@ int APS5_VABI sceNpSessionSignalingDestroyContext(std::uint32_t contextId) { if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; auto* context = FindContext(contextId); if (!context) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + context->customProperty = 0; + context->internalCallback = nullptr; + context->internalCallbackUserData = nullptr; context->active = false; const auto nextId = context->id + static_cast(g_contexts.size()); context->id = nextId < 100001 ? nextId : 1000 + contextId % static_cast(g_contexts.size()); @@ -159,6 +180,7 @@ int APS5_VABI sceNpSessionSignalingTerminate(void) { std::lock_guard lock(g_mutex); if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; g_initialized = false; + g_poolSize = 0; for (auto& context : g_contexts) context.active = false; return 0; } @@ -191,12 +213,18 @@ int APS5_VABI sceNpSessionSignalingRequestPrepare(uint32_t contextId, uint32_t* } int APS5_VABI sceNpSessionSignalingGetMemoryInfo(SessionSignalingMemoryInfo* memoryInfo) { - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!memoryInfo) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + *memoryInfo = {g_poolSize, 0, 0}; return 0; } int APS5_VABI sceNpSessionSignalingGetConnectionStatistics(SessionSignalingConnectionStatistics* statistics) { - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!statistics) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + *statistics = {}; return 0; } @@ -251,19 +279,21 @@ int APS5_VABI sceNpSessionSignalingGetConnectionFromNetAddress(std::uint32_t con return SCE_NP_SESSION_SIGNALING_ERROR_CONN_NOT_FOUND; } -int APS5_VABI sceNpSessionSignalingGetCustomProperty() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpSessionSignalingGetCustomProperty(std::uint32_t contextId, std::uint32_t* property) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + auto* context = FindContext(contextId); + if (!context) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + if (property) *property = context->customProperty; return 0; } -int APS5_VABI sceNpSessionSignalingGetGroupFromPeerAddress() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpSessionSignalingInitializeWithPort() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpSessionSignalingGetGroupFromPeerAddress(std::uint32_t contextId, const SessionSignalingPeerAddress* peerAddress, std::uint32_t* groupId) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!peerAddress || !groupId) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + return SCE_NP_SESSION_SIGNALING_ERROR_GROUP_NOT_FOUND; } int APS5_VABI sceNpSessionSignalingJoinActivate() { @@ -271,8 +301,14 @@ int APS5_VABI sceNpSessionSignalingJoinActivate() { return 0; } -int APS5_VABI sceNpSessionSignalingSetCustomProperty() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpSessionSignalingSetCustomProperty(std::uint32_t contextId, const std::uint32_t* property, std::uint32_t* previous) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!property) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + auto* context = FindContext(contextId); + if (!context) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + if (previous) *previous = context->customProperty; + context->customProperty = *property; return 0; } @@ -292,8 +328,14 @@ int APS5_VABI sceNpSessionSignalingManualUdpJoin() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceNpSessionSignalingRegisterInternalCallback() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpSessionSignalingRegisterInternalCallback(std::uint32_t contextId, void* callback, void* userData) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + auto* context = FindContext(contextId); + if (!context) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + context->internalCallback = callback; + context->internalCallbackUserData = userData; + return 0; } } diff --git a/core/libs/prx/libSceNpSessionSignaling/include/Api.hpp b/core/libs/prx/libSceNpSessionSignaling/include/Api.hpp index ad73c1a595..6f72608aff 100644 --- a/core/libs/prx/libSceNpSessionSignaling/include/Api.hpp +++ b/core/libs/prx/libSceNpSessionSignaling/include/Api.hpp @@ -105,15 +105,15 @@ int APS5_VABI sceNpSessionSignalingGetGroupInfo(std::uint32_t contextId, std::ui int APS5_VABI sceNpSessionSignalingGetConnectionFromNetAddress2(std::uint32_t contextId, std::uint32_t groupId, std::uint32_t address, std::uint16_t port, std::uint32_t* connectionId); int APS5_VABI sceNpSessionSignalingCreateJoinActivate(); int APS5_VABI sceNpSessionSignalingGetConnectionFromNetAddress(std::uint32_t contextId, std::uint32_t address, std::uint16_t port, std::uint32_t* connectionId); -int APS5_VABI sceNpSessionSignalingGetCustomProperty(); -int APS5_VABI sceNpSessionSignalingGetGroupFromPeerAddress(); -int APS5_VABI sceNpSessionSignalingInitializeWithPort(); +int APS5_VABI sceNpSessionSignalingGetCustomProperty(std::uint32_t contextId, std::uint32_t* property); +int APS5_VABI sceNpSessionSignalingGetGroupFromPeerAddress(std::uint32_t contextId, const SessionSignalingPeerAddress* peerAddress, std::uint32_t* groupId); +int APS5_VABI sceNpSessionSignalingInitializeWithPort(const SessionSignalingInitParam* param, std::uint32_t port, std::uint32_t reserved); int APS5_VABI sceNpSessionSignalingJoinActivate(); -int APS5_VABI sceNpSessionSignalingSetCustomProperty(); +int APS5_VABI sceNpSessionSignalingSetCustomProperty(std::uint32_t contextId, const std::uint32_t* property, std::uint32_t* previous); int APS5_VABI sceNpSessionSignalingManualUdpAbortConnection(); int APS5_VABI sceNpSessionSignalingManualUdpCreateJoin(); int APS5_VABI sceNpSessionSignalingManualUdpEstablishConnection(); int APS5_VABI sceNpSessionSignalingManualUdpJoin(); -int APS5_VABI sceNpSessionSignalingRegisterInternalCallback(); +int APS5_VABI sceNpSessionSignalingRegisterInternalCallback(std::uint32_t contextId, void* callback, void* userData); } diff --git a/core/libs/tests/GuestNpSessionSignaling.cpp b/core/libs/tests/GuestNpSessionSignaling.cpp index cd5d5c0943..34a6c383e6 100644 --- a/core/libs/tests/GuestNpSessionSignaling.cpp +++ b/core/libs/tests/GuestNpSessionSignaling.cpp @@ -31,11 +31,29 @@ int main() { Require(sceNpSessionSignalingTerminate() == NotInitialized, "terminate before initialize"); Require(sceNpSessionSignalingCreateContext2(nullptr, nullptr) == NotInitialized, "initialization check precedes context arguments"); Require(sceNpSessionSignalingGetConnectionStatus(0, 0, nullptr, nullptr, nullptr) == NotInitialized, "status before initialize"); + Require(sceNpSessionSignalingGetMemoryInfo(nullptr) == NotInitialized, "memory info before initialize"); + Require(sceNpSessionSignalingGetConnectionStatistics(nullptr) == NotInitialized, "statistics before initialize"); + Require(sceNpSessionSignalingGetGroupFromPeerAddress(0, nullptr, nullptr) == NotInitialized, "group from peer before initialize"); + Require(sceNpSessionSignalingGetCustomProperty(0, nullptr) == NotInitialized, "custom property before initialize"); + Require(sceNpSessionSignalingSetCustomProperty(0, nullptr, nullptr) == NotInitialized, "set custom property before initialize"); + Require(sceNpSessionSignalingRegisterInternalCallback(0, nullptr, nullptr) == NotInitialized, "register callback before initialize"); Require(sceNpSessionSignalingInitialize(nullptr) == InvalidArgument, "null initialize parameter"); Require(sceNpSessionSignalingInitialize(reinterpret_cast(initializeParam)) == InvalidArgument, "zero pool size"); const std::uint64_t poolSize = 4 * 1024 * 1024; std::memcpy(initializeParam + 8, &poolSize, sizeof(poolSize)); Require(sceNpSessionSignalingInitialize(reinterpret_cast(initializeParam)) == 0, "initialize failed"); + SessionSignalingMemoryInfo memoryInfo{}; + Require(sceNpSessionSignalingGetMemoryInfo(nullptr) == InvalidArgument, "null memory info"); + Require(sceNpSessionSignalingGetMemoryInfo(&memoryInfo) == 0, "memory info failed"); + Require(memoryInfo.poolSize == poolSize && memoryInfo.usedSize == 0 && memoryInfo.peakUsedSize == 0, "memory info contents"); + SessionSignalingConnectionStatistics statistics{}; + statistics.peakConnectionCount = 1; + statistics.connectionCount = 1; + statistics.connectingCount = 1; + statistics.connectedCount = 1; + Require(sceNpSessionSignalingGetConnectionStatistics(nullptr) == InvalidArgument, "null statistics"); + Require(sceNpSessionSignalingGetConnectionStatistics(&statistics) == 0, "statistics failed"); + Require(statistics.peakConnectionCount == 0 && statistics.connectionCount == 0 && statistics.connectingCount == 0 && statistics.connectedCount == 0, "statistics contents"); Require(sceNpSessionSignalingInitialize(nullptr) == AlreadyInitialized, "initialization check precedes initialize arguments"); alignas(8) unsigned char contextParam[56] = {}; std::uint32_t contextId = 0; @@ -62,6 +80,25 @@ int main() { Require(sceNpSessionSignalingDestroyContext(firstId) == ContextNotFound, "destroy stale context"); Require(sceNpSessionSignalingCreateContext2(reinterpret_cast(contextParam), &contextId) == 0, "reuse context slot"); Require(contextId != firstId, "reused context kept stale id"); + std::uint32_t staleProperty = 0x12345678u; + std::uint32_t previousProperty = 0; + int callbackMarker = 0; + int userDataMarker = 0; + Require(sceNpSessionSignalingSetCustomProperty(contextId, &staleProperty, &previousProperty) == 0 && previousProperty == 0, "set property before context reuse"); + Require(sceNpSessionSignalingRegisterInternalCallback(contextId, &callbackMarker, &userDataMarker) == 0, "register callback before context reuse"); + Require(sceNpSessionSignalingDestroyContext(contextId) == 0, "destroy context before reuse two"); + const std::int32_t secondReuseUser = 11; + std::memcpy(contextParam, &secondReuseUser, sizeof(secondReuseUser)); + Require(sceNpSessionSignalingCreateContext2(reinterpret_cast(contextParam), &contextId) == 0, "reuse context slot two"); + Require(sceNpSessionSignalingGetCustomProperty(contextId, &previousProperty) == 0 && previousProperty == 0, "reused context two kept state"); + Require(sceNpSessionSignalingSetCustomProperty(otherContexts[0], &staleProperty, &previousProperty) == 0 && previousProperty == 0, "set property before context one reuse"); + Require(sceNpSessionSignalingRegisterInternalCallback(otherContexts[0], &callbackMarker, &userDataMarker) == 0, "register callback before context one reuse"); + Require(sceNpSessionSignalingDestroyContext(otherContexts[0]) == 0, "destroy context before reuse one"); + const std::int32_t thirdReuseUser = 12; + std::memcpy(contextParam, &thirdReuseUser, sizeof(thirdReuseUser)); + std::uint32_t reusedContext = 0; + Require(sceNpSessionSignalingCreateContext(reinterpret_cast(contextParam), &reusedContext) == 0, "reuse context slot one"); + Require(sceNpSessionSignalingGetCustomProperty(reusedContext, &previousProperty) == 0 && previousProperty == 0, "reused context one kept state"); std::uint32_t requestId = 0xa5a5a5a5u; Require(sceNpSessionSignalingRequestPrepare(firstId, &requestId) == ContextNotFound, "prepare stale context"); @@ -122,12 +159,31 @@ int main() { Require(sceNpSessionSignalingGetConnectionFromPeerAddress2(netContextId, 1, nullptr, reinterpret_cast(&found)) == InvalidArgument, "null group peer address"); Require(sceNpSessionSignalingGetConnectionFromPeerAddress2(netContextId, 1, reinterpret_cast(peer), nullptr) == InvalidArgument, "null connection id from group peer address"); Require(sceNpSessionSignalingGetConnectionFromPeerAddress2(netContextId, 1, reinterpret_cast(peer), reinterpret_cast(&found)) == GroupNotFound, "connection from group peer address without a session"); + Require(sceNpSessionSignalingGetGroupFromPeerAddress(netContextId, nullptr, reinterpret_cast(&found)) == InvalidArgument, "null group peer address"); + Require(sceNpSessionSignalingGetGroupFromPeerAddress(netContextId, reinterpret_cast(peer), nullptr) == InvalidArgument, "null group id from peer address"); + Require(sceNpSessionSignalingGetGroupFromPeerAddress(firstId, reinterpret_cast(peer), reinterpret_cast(&found)) == ContextNotFound, "group from peer address stale context"); + Require(sceNpSessionSignalingGetGroupFromPeerAddress(netContextId, reinterpret_cast(peer), reinterpret_cast(&found)) == GroupNotFound, "group from peer address without a session"); Require(sceNpSessionSignalingGetGroupInfo(firstId, 1, reinterpret_cast(info)) == ContextNotFound, "group info stale context"); Require(sceNpSessionSignalingGetGroupFromSessionId(firstId, reinterpret_cast(sessionId), reinterpret_cast(&found)) == ContextNotFound, "group from session id stale context"); Require(sceNpSessionSignalingGetConnectionFromNetAddress(firstId, 0x0100007fu, 3658, reinterpret_cast(&found)) == ContextNotFound, "connection from net address stale context"); Require(sceNpSessionSignalingGetConnectionFromNetAddress2(firstId, 1, 0x0100007fu, 3658, reinterpret_cast(&found)) == ContextNotFound, "connection from group net address stale context"); Require(sceNpSessionSignalingGetConnectionFromPeerAddress(firstId, reinterpret_cast(peer), reinterpret_cast(&found)) == ContextNotFound, "connection from peer address stale context"); Require(found == 0x5a5a5a5a, "a failed lookup wrote an id"); + std::uint32_t property = 0; + Require(sceNpSessionSignalingGetCustomProperty(firstId, &property) == ContextNotFound, "custom property stale context"); + Require(sceNpSessionSignalingGetCustomProperty(contextId, nullptr) == 0, "null custom property output"); + Require(sceNpSessionSignalingGetCustomProperty(contextId, &property) == 0 && property == 0, "default custom property"); + Require(sceNpSessionSignalingSetCustomProperty(contextId, nullptr, nullptr) == InvalidArgument, "null custom property value"); + Require(sceNpSessionSignalingSetCustomProperty(firstId, &property, nullptr) == ContextNotFound, "set custom property stale context"); + std::uint32_t value = 0x12345678u; + std::uint32_t previous = 0xa5a5a5a5u; + Require(sceNpSessionSignalingSetCustomProperty(contextId, &value, &previous) == 0 && previous == 0, "set custom property"); + Require(sceNpSessionSignalingGetCustomProperty(contextId, &property) == 0 && property == value, "get custom property"); + value = 0xabcdef01u; + Require(sceNpSessionSignalingSetCustomProperty(contextId, &value, &previous) == 0 && previous == 0x12345678u, "replace custom property"); + Require(sceNpSessionSignalingGetCustomProperty(contextId, &property) == 0 && property == value, "get replaced custom property"); + Require(sceNpSessionSignalingRegisterInternalCallback(firstId, nullptr, nullptr) == ContextNotFound, "register callback stale context"); + Require(sceNpSessionSignalingRegisterInternalCallback(contextId, nullptr, nullptr) == 0, "register callback"); const char* const activateSessionId = reinterpret_cast(sessionId); SessionSignalingSessionParam sessionParam{1, 0}; std::uint32_t activatedGroup = 0xa5a5a5a5u; @@ -161,4 +217,9 @@ int main() { Require(sceNpSessionSignalingRequestPrepare(contextId, &requestId) == ContextNotFound, "reinitialize retained a context"); Require(sceNpSessionSignalingTerminate() == 0, "final terminate"); Require(sceNpSessionSignalingCreateContext(reinterpret_cast(1), &contextId) == NotInitialized, "create context post-terminate"); + Require(sceNpSessionSignalingInitializeWithPort(nullptr, 0x217b, 0) == InvalidArgument, "null initialize with port parameter"); + Require(sceNpSessionSignalingInitializeWithPort(reinterpret_cast(initializeParam), 0x217b, 0) == 0, "initialize with port failed"); + Require(sceNpSessionSignalingInitializeWithPort(reinterpret_cast(initializeParam), 0x217b, 0) == AlreadyInitialized, "initialize with port after initialize"); + Require(sceNpSessionSignalingGetMemoryInfo(&memoryInfo) == 0 && memoryInfo.poolSize == poolSize && memoryInfo.usedSize == 0 && memoryInfo.peakUsedSize == 0, "memory info after initialize with port"); + Require(sceNpSessionSignalingTerminate() == 0, "final terminate after initialize with port"); } diff --git a/docs/dev/debt/libSceNpSessionSignaling.md b/docs/dev/debt/libSceNpSessionSignaling.md index 463498b1a5..9e3e4197fd 100644 --- a/docs/dev/debt/libSceNpSessionSignaling.md +++ b/docs/dev/debt/libSceNpSessionSignaling.md @@ -5,13 +5,19 @@ - [sceNpSessionSignalingInitialize, sceNpSessionSignalingCreateContext and sceNpSessionSignalingCreateContext2](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) - the initialization layout is 40 bytes; both context layouts are 56 bytes with callback and user-data pairs. The second context variant adds a group identifier to its connection callback. Pool allocation, callback delivery and underlying NP account validation are not implemented; priority and affinity settings do not affect host scheduling. - [sceNpSessionSignalingGetConnectionStatus, sceNpSessionSignalingActivateSession and sceNpSessionSignalingActivateUser](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) - initialization, context lookup and output validation follow the system library. Connections never activate in the offline runtime; activation returns unavailable and a connection query on a valid context returns connection-not-found without writing outputs. SDK-dependent peer-address validation and local-network discovery remain incomplete. -- [sceNpSessionSignalingCreateJoinActivate, sceNpSessionSignalingGetCustomProperty, sceNpSessionSignalingGetGroupFromPeerAddress, sceNpSessionSignalingInitializeWithPort, sceNpSessionSignalingJoinActivate and sceNpSessionSignalingSetCustomProperty](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpSessionSignaling.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim. -- [sceNpSessionSignalingManualUdpAbortConnection, sceNpSessionSignalingManualUdpCreateJoin, sceNpSessionSignalingManualUdpEstablishConnection, sceNpSessionSignalingManualUdpJoin, sceNpSessionSignalingRegisterInternalCallback](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) have unknown signatures and behaviour. The parameterless `int` declarations are import-resolution placeholders, not confirmed ABI signatures, and throw through `NotImplemented_nid_no_patch`. Only the names and NIDs are established by the [13.60 export inventory](https://github.com/xdCloudy/PS5-13.60-Function-Stub-Library/blob/bfec40deb4c47a17d7603ee2bc79aa6862c0c56b/data/firmware-13.60/libscenpsessionsignaling-58c8806476.jsonl). -- [sceNpSessionSignalingGetMemoryInfo and sceNpSessionSignalingGetConnectionStatistics](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - both throw through `NotImplemented_nid_no_patch` without reading or writing their output; the prototypes in `Api.hpp` are not confirmed by a console measurement, and what the console returns and writes is unmeasured. +- [sceNpSessionSignalingCreateJoinActivate, sceNpSessionSignalingJoinActivate, sceNpSessionSignalingManualUdpAbortConnection, sceNpSessionSignalingManualUdpCreateJoin, sceNpSessionSignalingManualUdpEstablishConnection and sceNpSessionSignalingManualUdpJoin](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpSessionSignaling.c) and the [13.60 inventory](https://github.com/xdCloudy/PS5-13.60-Function-Stub-Library/blob/bfec40deb4c47a17d7603ee2bc79aa6862c0c56b/data/firmware-13.60/libscenpsessionsignaling-58c8806476.jsonl), but no credible prototypes were found. The parameterless declarations are import-resolution placeholders, not an ABI claim, and they still throw through `NotImplemented_nid_no_patch`. + +- [sceNpSessionSignalingInitializeWithPort, sceNpSessionSignalingSetCustomProperty and sceNpSessionSignalingRegisterInternalCallback](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) - signatures from the 11.00 wrappers. Initialize (`0xab80`) passes port 0x217b and reserved 0 to the initializer at vtable slot 0x10; InitializeWithPort (`0xab40`) forwards `(param, port, reserved)` there too, but the initializer's validation is unmeasured; the host opens no socket and accepts both. SetCustomProperty (`0xb3c0`) checks init, null property and context, then calls slot 0x78 with the opaque pointer and copies the returned dword to the optional previous output; GetCustomProperty (`0xb470`) calls slot 0x80 and copies its dword to the optional output. This establishes the 32-bit previous/output exchange, not the internal four-byte load, so the host models one 32-bit value. RegisterInternalCallback stores both pointers without invoking them; the callback signature is unknown. ### Functional -- [sceNpSessionSignalingGetGroupInfo, sceNpSessionSignalingGetGroupFromSessionId, sceNpSessionSignalingGetConnectionFromNetAddress, sceNpSessionSignalingGetConnectionFromNetAddress2, sceNpSessionSignalingGetConnectionFromPeerAddress, sceNpSessionSignalingGetConnectionFromPeerAddress2](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - no session ever activates here, so every lookup on a valid context ends at not-found (group 0x8055330A, connection 0x8055330C). The signatures and the checks before it are the PS5 13.60 module's, read from disassembly: 0x80553301 before initialization, 0x80553303 for a null pointer, then 0x80553308 for an unknown context +- [sceNpSessionSignalingGetGroupInfo, sceNpSessionSignalingGetGroupFromSessionId, sceNpSessionSignalingGetGroupFromPeerAddress, sceNpSessionSignalingGetConnectionFromNetAddress, sceNpSessionSignalingGetConnectionFromNetAddress2, sceNpSessionSignalingGetConnectionFromPeerAddress, sceNpSessionSignalingGetConnectionFromPeerAddress2](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - no session ever activates here, so every lookup on a valid context ends at not-found (group 0x8055330A, connection 0x8055330C). The signatures and the checks before it are the PS5 13.60 module's, read from disassembly: 0x80553301 before initialization, 0x80553303 for a null pointer, then 0x80553308 for an unknown context - [sceNpSessionSignalingGetGroupFromSessionId, sceNpSessionSignalingGetConnectionFromPeerAddress, sceNpSessionSignalingGetConnectionFromPeerAddress2](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - three titles disassembled for this entry import them, five call sites in all: PPSA02530 (GroupFromSessionId 0x87c9118, PeerAddress2 0x87c912f), PPSA02225 (PeerAddress2 0x9e5595 and 0x9e5787) and PPSA01285 (PeerAddress 0xeecf55). PPSA02530 and PPSA01285 test the result only for negative, PPSA02225 at 0x9e5787 only for nonzero, and none of these sites compares it with a specific code; on a negative result PPSA02530 jumps to 0x87c914d, and on a nonzero result PPSA02225 jumps to 0x9e53d0 at 0x9e5787. What PPSA01285 does after a negative result (a wait path from 0xeecf9b) was not read. + +- [sceNpSessionSignalingGetMemoryInfo and sceNpSessionSignalingGetConnectionStatistics](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - the wrapper checks initialization (0x80553301) and a null output (0x80553303) before delegating. The host stores the pool size from initialization and reports zero used and peak sizes because it allocates no signaling pool, and reports all connection statistics as zero because no connection ever activates. The console's actual usage and statistics values were not measured. + +- [sceNpSessionSignalingGetCustomProperty and sceNpSessionSignalingSetCustomProperty](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - the host keeps one 32-bit custom property per context. Get allows a null output and returns the current value; Set rejects a null property pointer, returns the previous value through the optional output and stores the new value. Reusing a destroyed context slot clears the property and both callback pointers, so a new context starts from the same state as a fresh slot. The input width is inferred from the wrapper's opaque pointer and the 32-bit previous/getter exchange described above, not measured on a console. + +- [sceNpSessionSignalingRegisterInternalCallback](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - stores the callback and user-data pointers in the context and never invokes them. Null pointers are accepted, as the wrapper does not check them, and the call returns 0 after forwarding to the internal object regardless of its result. The callback signature is unknown. From 480b4e43f66c948ca68cbbf9e4e8e7ea587c240a Mon Sep 17 00:00:00 2001 From: Noxy-HNR Date: Mon, 2 Nov 2026 01:06:13 +0000 Subject: [PATCH 223/612] feat(libSceJxr): implement bit I/O through the pinned jxrlib Sixteen bit reader/writer exports (peekBit16, flushBit16, getBit16, getBool16, getBit16s, getBit32, flushToByte, putBit16z, putBit16, putBit32, fillToByte, getBit16_S, getSizeRead, getSizeWrite, getPosRead, dquantBits) now call strcodec.c from 3rdparty/jxrlib through a C bridge. The guest adapters only reject the arguments jxrlib asserts on; getBit16_S adds the existing readIS. A new test drives 256k random reads and writes on a ring buffer against jxrlib directly and covers the rejected arguments. --- core/libs/prx/libSceJxr/CMakeLists.txt | 20 ++- core/libs/prx/libSceJxr/Export.cpp | 64 -------- core/libs/prx/libSceJxr/include/Api.hpp | 32 ++-- core/libs/prx/libSceJxr/src/BitIo.cpp | 128 +++++++++++++++ core/libs/prx/libSceJxr/src/NativeBitIo.c | 20 +++ core/libs/tests/GuestJxrBitIo.cpp | 187 ++++++++++++++++++++++ core/libs/tests/GuestJxrBitIoReference.c | 26 +++ docs/dev/debt/libSceJxr.md | 6 +- 8 files changed, 400 insertions(+), 83 deletions(-) create mode 100644 core/libs/prx/libSceJxr/src/BitIo.cpp create mode 100644 core/libs/prx/libSceJxr/src/NativeBitIo.c create mode 100644 core/libs/tests/GuestJxrBitIo.cpp create mode 100644 core/libs/tests/GuestJxrBitIoReference.c diff --git a/core/libs/prx/libSceJxr/CMakeLists.txt b/core/libs/prx/libSceJxr/CMakeLists.txt index 5d76a6870f..f292efc90e 100644 --- a/core/libs/prx/libSceJxr/CMakeLists.txt +++ b/core/libs/prx/libSceJxr/CMakeLists.txt @@ -1,8 +1,8 @@ -add_library(jxr_lookup_native STATIC src/NativeLookup.c) +add_library(jxr_lookup_native STATIC src/NativeLookup.c src/NativeBitIo.c) configure_jxr_native(jxr_lookup_native) target_include_directories(jxr_lookup_native PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(jxr_lookup_native PRIVATE jxr) -add_library(libSceJxr SHARED EXCLUDE_FROM_ALL Export.cpp src/PixelFormatLookup.cpp src/GuidExports.cpp) +add_library(libSceJxr SHARED EXCLUDE_FROM_ALL Export.cpp src/PixelFormatLookup.cpp src/GuidExports.cpp src/BitIo.cpp) target_link_libraries(libSceJxr PRIVATE jxr_lookup_native) if(NOT APPLE) target_link_options(libSceJxr PRIVATE LINKER:--exclude-libs,ALL) @@ -69,3 +69,19 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_library(jxr_bitio_reference STATIC ${LIBS_INCLUDE_DIR}/tests/GuestJxrBitIoReference.c) + configure_jxr_native(jxr_bitio_reference) + target_link_libraries(jxr_bitio_reference PRIVATE jxr) + add_test_executable(guest_jxr_bitio_tests ${LIBS_INCLUDE_DIR}/tests/GuestJxrBitIo.cpp) + target_include_directories(guest_jxr_bitio_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_jxr_bitio_tests PRIVATE libSceJxr libc jxr_bitio_reference) + configure_windows_unwind(guest_jxr_bitio_tests) + add_test(NAME guest_jxr_bitio COMMAND guest_jxr_bitio_tests) + set_tests_properties(guest_jxr_bitio PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_jxr_bitio PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceJxr/Export.cpp b/core/libs/prx/libSceJxr/Export.cpp index 50f91d2edc..709c8a20e8 100644 --- a/core/libs/prx/libSceJxr/Export.cpp +++ b/core/libs/prx/libSceJxr/Export.cpp @@ -428,26 +428,10 @@ int APS5_VABI detach_SB_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI dquantBits_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI fillToByte_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI flushBit16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI flushToByte_SB_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI flushToByte_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI formatQuantizer_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -468,30 +452,10 @@ int APS5_VABI getACPredMode_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI getBit16_S_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI getBit16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI getBit16s_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI getBit32_SB_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI getBit32_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI getBool16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI getByteRead_SB_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -500,18 +464,6 @@ int APS5_VABI getDCACPredMode_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI getPosRead_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI getSizeRead_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI getSizeWrite_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI getTilePos_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -524,10 +476,6 @@ int APS5_VABI initPostProc_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI peekBit16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI postProcBlock_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -536,18 +484,6 @@ int APS5_VABI postProcMB_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI putBit16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI putBit16z_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI putBit32_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI remapQP_nid_postfix() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceJxr/include/Api.hpp b/core/libs/prx/libSceJxr/include/Api.hpp index b57921294e..c86dbb55c2 100644 --- a/core/libs/prx/libSceJxr/include/Api.hpp +++ b/core/libs/prx/libSceJxr/include/Api.hpp @@ -10,6 +10,22 @@ std::int64_t APS5_VABI PKFree_nid_postfix(void** memory); std::int64_t APS5_VABI PixelFormatLookup_nid_postfix(JxrPixelInfo* value, std::uint8_t mode); std::int64_t APS5_VABI readIS_nid_postfix(void* codec, Jxr::BitIo* io); std::int64_t APS5_VABI writeIS_nid_postfix(void* codec, Jxr::BitIo* io); +std::uint32_t APS5_VABI peekBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bits); +std::uint32_t APS5_VABI flushBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bits); +std::uint32_t APS5_VABI getBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bits); +std::uint32_t APS5_VABI getBool16_nid_postfix(Jxr::BitIo* io); +std::int32_t APS5_VABI getBit16s_nid_postfix(Jxr::BitIo* io, std::uint32_t bits); +std::uint32_t APS5_VABI getBit32_nid_postfix(Jxr::BitIo* io, std::uint32_t bits); +std::uint32_t APS5_VABI flushToByte_nid_postfix(Jxr::BitIo* io); +void APS5_VABI putBit16z_nid_postfix(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits); +void APS5_VABI putBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits); +void APS5_VABI putBit32_nid_postfix(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits); +void APS5_VABI fillToByte_nid_postfix(Jxr::BitIo* io); +std::uint32_t APS5_VABI getBit16_S_nid_postfix(void* codec, Jxr::BitIo* io, std::uint32_t bits); +std::uint32_t APS5_VABI getSizeRead_nid_postfix(Jxr::BitIo* io); +std::uint32_t APS5_VABI getSizeWrite_nid_postfix(Jxr::BitIo* io); +std::uint32_t APS5_VABI getPosRead_nid_postfix(Jxr::BitIo* io); +std::uint8_t APS5_VABI dquantBits_nid_postfix(std::uint8_t qp); int APS5_VABI AllocateCodingContextDec_nid_postfix(); int APS5_VABI CloseWS_List_nid_postfix(); @@ -103,36 +119,20 @@ int APS5_VABI checkImageBuffer_nid_postfix(); int APS5_VABI detachISRead_nid_postfix(); int APS5_VABI detachISWrite_nid_postfix(); int APS5_VABI detach_SB_nid_postfix(); -int APS5_VABI dquantBits_nid_postfix(); -int APS5_VABI fillToByte_nid_postfix(); -int APS5_VABI flushBit16_nid_postfix(); int APS5_VABI flushToByte_SB_nid_postfix(); -int APS5_VABI flushToByte_nid_postfix(); int APS5_VABI formatQuantizer_nid_postfix(); int APS5_VABI freePredInfo_nid_postfix(); int APS5_VABI freeQuantizer_nid_postfix(); int APS5_VABI freeTileInfo_nid_postfix(); int APS5_VABI getACPredMode_nid_postfix(); -int APS5_VABI getBit16_S_nid_postfix(); -int APS5_VABI getBit16_nid_postfix(); -int APS5_VABI getBit16s_nid_postfix(); int APS5_VABI getBit32_SB_nid_postfix(); -int APS5_VABI getBit32_nid_postfix(); -int APS5_VABI getBool16_nid_postfix(); int APS5_VABI getByteRead_SB_nid_postfix(); int APS5_VABI getDCACPredMode_nid_postfix(); -int APS5_VABI getPosRead_nid_postfix(); -int APS5_VABI getSizeRead_nid_postfix(); -int APS5_VABI getSizeWrite_nid_postfix(); int APS5_VABI getTilePos_nid_postfix(); int APS5_VABI initMRPtr_nid_postfix(); int APS5_VABI initPostProc_nid_postfix(); -int APS5_VABI peekBit16_nid_postfix(); int APS5_VABI postProcBlock_nid_postfix(); int APS5_VABI postProcMB_nid_postfix(); -int APS5_VABI putBit16_nid_postfix(); -int APS5_VABI putBit16z_nid_postfix(); -int APS5_VABI putBit32_nid_postfix(); int APS5_VABI remapQP_nid_postfix(); int APS5_VABI setBitIOPointers_nid_postfix(); int APS5_VABI setUniformQuantizer_nid_postfix(); diff --git a/core/libs/prx/libSceJxr/src/BitIo.cpp b/core/libs/prx/libSceJxr/src/BitIo.cpp new file mode 100644 index 0000000000..b2aee36a40 --- /dev/null +++ b/core/libs/prx/libSceJxr/src/BitIo.cpp @@ -0,0 +1,128 @@ +#include "prx/libSceJxr/libSceJxr.hpp" +#include +#include +#include + +// Thin guest adapters over jxrlib's bit I/O in 3rdparty/jxrlib image/sys/strcodec.c (see NativeBitIo.c): +// they reject the arguments jxrlib only asserts on and pass everything else through unchanged. + +extern "C" { +std::uint32_t JxrPeekBit16_nid_no_patch(void* io, std::uint32_t bits); +std::uint32_t JxrFlushBit16_nid_no_patch(void* io, std::uint32_t bits); +std::uint32_t JxrGetBit16_nid_no_patch(void* io, std::uint32_t bits); +std::uint32_t JxrGetBool16_nid_no_patch(void* io); +std::int32_t JxrGetBit16s_nid_no_patch(void* io, std::uint32_t bits); +std::uint32_t JxrGetBit32_nid_no_patch(void* io, std::uint32_t bits); +std::uint32_t JxrFlushToByte_nid_no_patch(void* io); +void JxrPutBit16z_nid_no_patch(void* io, std::uint32_t value, std::uint32_t bits); +void JxrPutBit16_nid_no_patch(void* io, std::uint32_t value, std::uint32_t bits); +void JxrPutBit32_nid_no_patch(void* io, std::uint32_t value, std::uint32_t bits); +void JxrFillToByte_nid_no_patch(void* io); +std::uint32_t JxrGetSizeRead_nid_no_patch(void* io); +std::uint32_t JxrGetSizeWrite_nid_no_patch(void* io); +std::uint32_t JxrGetPosRead_nid_no_patch(void* io); +std::uint8_t JxrDquantBits_nid_no_patch(std::uint8_t qp); +} + +namespace { + +void Require(bool condition, const char* function, const char* message) { + if (!condition) throw std::invalid_argument(std::string(function) + ": " + message); +} + +void* State(Jxr::BitIo* io, const char* function) { + Require(io != nullptr, function, "null bit state"); + return io; +} + +void* Stream(Jxr::BitIo* io, const char* function) { + State(io, function); + Require(io->current != nullptr, function, "null current pointer"); + return io; +} + +void* Read(Jxr::BitIo* io, std::uint32_t bits, std::uint32_t limit, const char* function) { + Stream(io, function); + Require(bits <= limit, function, "bit count over the jxrlib limit"); + Require((io->mask & 1) == 0, function, "odd read wrap mask"); + return io; +} + +void* Write(Jxr::BitIo* io, std::uint32_t bits, std::uint32_t limit, const char* function) { + Stream(io, function); + Require(bits <= limit, function, "bit count over the jxrlib limit"); + return io; +} + +} // namespace + +extern "C" { + +std::uint32_t APS5_VABI peekBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bits) { + return JxrPeekBit16_nid_no_patch(Read(io, bits, 16, __func__), bits); +} + +std::uint32_t APS5_VABI flushBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bits) { + return JxrFlushBit16_nid_no_patch(Read(io, bits, 16, __func__), bits); +} + +std::uint32_t APS5_VABI getBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bits) { + return JxrGetBit16_nid_no_patch(Read(io, bits, 16, __func__), bits); +} + +std::uint32_t APS5_VABI getBool16_nid_postfix(Jxr::BitIo* io) { + return JxrGetBool16_nid_no_patch(Read(io, 1, 16, __func__)); +} + +std::int32_t APS5_VABI getBit16s_nid_postfix(Jxr::BitIo* io, std::uint32_t bits) { + return JxrGetBit16s_nid_no_patch(Read(io, bits, 15, __func__), bits); +} + +std::uint32_t APS5_VABI getBit32_nid_postfix(Jxr::BitIo* io, std::uint32_t bits) { + return JxrGetBit32_nid_no_patch(Read(io, bits, 32, __func__), bits); +} + +std::uint32_t APS5_VABI flushToByte_nid_postfix(Jxr::BitIo* io) { + return JxrFlushToByte_nid_no_patch(Read(io, 0, 16, __func__)); +} + +void APS5_VABI putBit16z_nid_postfix(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits) { + Require(bits >= 32 || (value >> bits) == 0, __func__, "value wider than the bit count"); + JxrPutBit16z_nid_no_patch(Write(io, bits, 16, __func__), value, bits); +} + +void APS5_VABI putBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits) { + JxrPutBit16_nid_no_patch(Write(io, bits, 16, __func__), value, bits); +} + +void APS5_VABI putBit32_nid_postfix(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits) { + JxrPutBit32_nid_no_patch(Write(io, bits, 32, __func__), value, bits); +} + +void APS5_VABI fillToByte_nid_postfix(Jxr::BitIo* io) { + JxrFillToByte_nid_no_patch(Write(io, 0, 16, __func__)); +} + +std::uint32_t APS5_VABI getBit16_S_nid_postfix(void* codec, Jxr::BitIo* io, std::uint32_t bits) { + const auto value = getBit16_nid_postfix(io, bits); + readIS_nid_postfix(codec, io); + return value; +} + +std::uint32_t APS5_VABI getSizeRead_nid_postfix(Jxr::BitIo* io) { + return JxrGetSizeRead_nid_no_patch(State(io, __func__)); +} + +std::uint32_t APS5_VABI getSizeWrite_nid_postfix(Jxr::BitIo* io) { + return JxrGetSizeWrite_nid_no_patch(State(io, __func__)); +} + +std::uint32_t APS5_VABI getPosRead_nid_postfix(Jxr::BitIo* io) { + return JxrGetPosRead_nid_no_patch(State(io, __func__)); +} + +std::uint8_t APS5_VABI dquantBits_nid_postfix(std::uint8_t qp) { + return JxrDquantBits_nid_no_patch(qp); +} + +} // extern "C" diff --git a/core/libs/prx/libSceJxr/src/NativeBitIo.c b/core/libs/prx/libSceJxr/src/NativeBitIo.c new file mode 100644 index 0000000000..6367d026bf --- /dev/null +++ b/core/libs/prx/libSceJxr/src/NativeBitIo.c @@ -0,0 +1,20 @@ +#include "strcodec.h" +#include + +_Static_assert(sizeof(BitIOInfo) == 48, "BitIOInfo size"); + +uint32_t JxrPeekBit16_nid_no_patch(void* io, uint32_t bits) { return peekBit16((BitIOInfo*)io, bits); } +uint32_t JxrFlushBit16_nid_no_patch(void* io, uint32_t bits) { return flushBit16((BitIOInfo*)io, bits); } +uint32_t JxrGetBit16_nid_no_patch(void* io, uint32_t bits) { return getBit16((BitIOInfo*)io, bits); } +uint32_t JxrGetBool16_nid_no_patch(void* io) { return getBool16((BitIOInfo*)io); } +int32_t JxrGetBit16s_nid_no_patch(void* io, uint32_t bits) { return getBit16s((BitIOInfo*)io, bits); } +uint32_t JxrGetBit32_nid_no_patch(void* io, uint32_t bits) { return getBit32((BitIOInfo*)io, bits); } +uint32_t JxrFlushToByte_nid_no_patch(void* io) { return flushToByte((BitIOInfo*)io); } +void JxrPutBit16z_nid_no_patch(void* io, uint32_t value, uint32_t bits) { putBit16z((BitIOInfo*)io, value, bits); } +void JxrPutBit16_nid_no_patch(void* io, uint32_t value, uint32_t bits) { putBit16((BitIOInfo*)io, value, bits); } +void JxrPutBit32_nid_no_patch(void* io, uint32_t value, uint32_t bits) { putBit32((BitIOInfo*)io, value, bits); } +void JxrFillToByte_nid_no_patch(void* io) { fillToByte((BitIOInfo*)io); } +uint32_t JxrGetSizeRead_nid_no_patch(void* io) { return getSizeRead((BitIOInfo*)io); } +uint32_t JxrGetSizeWrite_nid_no_patch(void* io) { return getSizeWrite((BitIOInfo*)io); } +uint32_t JxrGetPosRead_nid_no_patch(void* io) { return getPosRead((BitIOInfo*)io); } +uint8_t JxrDquantBits_nid_no_patch(uint8_t qp) { return dquantBits(qp); } diff --git a/core/libs/tests/GuestJxrBitIo.cpp b/core/libs/tests/GuestJxrBitIo.cpp new file mode 100644 index 0000000000..b65f20a013 --- /dev/null +++ b/core/libs/tests/GuestJxrBitIo.cpp @@ -0,0 +1,187 @@ +#include "prx/libSceJxr/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" std::uint32_t JxrBitIoReference_nid_no_patch(int operation, void* state, std::uint32_t value, std::uint32_t bits); + +namespace { + +constexpr std::size_t RingAlignment = 16384; +constexpr std::int32_t WriteMask = ~8192; +constexpr std::int32_t ReadMask = WriteMask & ~1; + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "JXR bit I/O: %s\n", message); + std::abort(); +} + +template +void RequireInvalidArgument(TAction action, const char* message) { + try { + action(); + } catch (const std::invalid_argument&) { + return; + } + Require(false, message); +} + +struct Ring { + std::vector storage = std::vector(RingAlignment * 3); + std::uint8_t* base = reinterpret_cast( + (reinterpret_cast(storage.data()) + RingAlignment - 1) & ~std::uintptr_t{RingAlignment - 1}); +}; + +std::uint32_t seed = 0x2468ace1; +std::uint32_t Next() { + seed = seed * 1664525u + 1013904223u; + return seed >> 8; +} + +std::uint32_t LoadBigEndian(const std::uint8_t* pointer) { + return (std::uint32_t{pointer[0]} << 24) | (std::uint32_t{pointer[1]} << 16) | (std::uint32_t{pointer[2]} << 8) | pointer[3]; +} + +Jxr::BitIo Attach(Ring& ring, std::size_t offset, std::uint32_t bitsUsed, std::size_t reference) { + Jxr::BitIo io{}; + io.mask = ReadMask; + io.start = ring.base; + io.current = ring.base + offset; + io.bitsUsed = bitsUsed; + io.accumulator = LoadBigEndian(io.current) << bitsUsed; + io.referenceOffset = reference; + return io; +} + +void RequireSame(const Jxr::BitIo& guest, const Ring& guestRing, const Jxr::BitIo& native, const Ring& nativeRing, + const char* message) { + Require(guest.accumulator == native.accumulator && guest.bitsUsed == native.bitsUsed && guest.mask == native.mask && + guest.current - guestRing.base == native.current - nativeRing.base && + guest.start - guestRing.base == native.start - nativeRing.base && + guest.referenceOffset == native.referenceOffset, + message); + Require(std::memcmp(guestRing.base, nativeRing.base, RingAlignment) == 0, message); +} + +std::uint32_t Guest(int operation, Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits) { + switch (operation) { + case 0: return peekBit16_nid_postfix(io, bits); + case 1: return flushBit16_nid_postfix(io, bits); + case 2: return getBit16_nid_postfix(io, bits); + case 3: return getBool16_nid_postfix(io); + case 4: return static_cast(getBit16s_nid_postfix(io, bits)); + case 5: return getBit32_nid_postfix(io, bits); + case 6: return flushToByte_nid_postfix(io); + case 7: return getSizeRead_nid_postfix(io); + case 8: return getPosRead_nid_postfix(io); + case 9: putBit16z_nid_postfix(io, value, bits); return 0; + case 10: putBit16_nid_postfix(io, value, bits); return 0; + case 11: putBit32_nid_postfix(io, value, bits); return 0; + case 12: fillToByte_nid_postfix(io); return 0; + case 13: return getSizeWrite_nid_postfix(io); + default: return 0xffffffffu; + } +} + +void RunReads() { + Ring guestRing, nativeRing; + for (std::size_t i = 0; i < RingAlignment; ++i) guestRing.base[i] = nativeRing.base[i] = static_cast(Next()); + for (int round = 0; round < 64; ++round) { + const auto offset = (Next() % 4096) * 2; + const auto used = Next() % 16; + const std::size_t reference = 100000 + Next() % 100000; + auto guest = Attach(guestRing, offset, used, reference); + auto native = Attach(nativeRing, offset, used, reference); + for (int step = 0; step < 2000; ++step) { + const int operation = static_cast(Next() % 9); + std::uint32_t bits = Next() % 17; + if (operation == 4) bits %= 16; + if (operation == 5) bits = Next() % 33; + const auto expected = JxrBitIoReference_nid_no_patch(operation, &native, 0, bits); + const auto actual = Guest(operation, &guest, 0, bits); + Require(actual == expected, "read result differs from jxrlib"); + RequireSame(guest, guestRing, native, nativeRing, "read state differs from jxrlib"); + } + } +} + +void RunWrites() { + Ring guestRing, nativeRing; + for (int round = 0; round < 64; ++round) { + for (std::size_t i = 0; i < RingAlignment; ++i) guestRing.base[i] = nativeRing.base[i] = static_cast(Next()); + const auto offset = (Next() % 4096) * 2; + const auto used = Next() % 16; + const auto accumulator = Next() & (used == 0 ? 0u : 0xffffffffu >> (32 - used)); + auto guest = Attach(guestRing, offset, used, 0); + guest.start = guestRing.base + (Next() % 2) * 4096; + guest.accumulator = accumulator; + guest.mask = WriteMask; + auto native = guest; + native.start = nativeRing.base + (guest.start - guestRing.base); + native.current = nativeRing.base + offset; + for (int step = 0; step < 2000; ++step) { + const int operation = 9 + static_cast(Next() % 5); + std::uint32_t bits = operation == 11 ? Next() % 33 : Next() % 17; + std::uint32_t value = Next() ^ (Next() << 24); + if (operation == 9) value &= bits == 0 ? 0u : 0xffffffffu >> (32 - bits); + const auto expected = JxrBitIoReference_nid_no_patch(operation, &native, value, bits); + const auto actual = Guest(operation, &guest, value, bits); + Require(actual == expected, "write result differs from jxrlib"); + RequireSame(guest, guestRing, native, nativeRing, "write state differs from jxrlib"); + } + } +} + +void RunEdges() { + for (std::uint32_t qp = 0; qp < 256; ++qp) + Require(dquantBits_nid_postfix(static_cast(qp)) == + JxrBitIoReference_nid_no_patch(14, nullptr, qp, 0), + "dquantBits differs from jxrlib"); + + Ring ring; + for (std::size_t i = 0; i < RingAlignment; ++i) ring.base[i] = static_cast(i * 7); + auto io = Attach(ring, 8190, 4, 0); + Require(getBit16_nid_postfix(&io, 12) == (LoadBigEndian(ring.base + 8190) << 4 >> 20), "wrap read value"); + Require(io.current == ring.base && io.bitsUsed == 0, "read wraps to the ring start"); + + auto copy = Attach(ring, 10, 3, 0); + Require(getBit16_S_nid_postfix(nullptr, ©, 5) == ((LoadBigEndian(ring.base + 10) << 3) >> 27), "getBit16_S value"); + Require(copy.current == ring.base + 10 && copy.bitsUsed == 8, "getBit16_S state"); + auto crossing = Attach(ring, 4096, 0, 0); + RequireInvalidArgument([&] { getBit16_S_nid_postfix(nullptr, &crossing, 1); }, "getBit16_S packet without stream"); + + RequireInvalidArgument([] { getBit16_nid_postfix(nullptr, 1); }, "null read state"); + RequireInvalidArgument([] { putBit16_nid_postfix(nullptr, 1, 1); }, "null write state"); + RequireInvalidArgument([] { getSizeRead_nid_postfix(nullptr); }, "null size state"); + RequireInvalidArgument([] { getSizeWrite_nid_postfix(nullptr); }, "null size write state"); + RequireInvalidArgument([] { getPosRead_nid_postfix(nullptr); }, "null position state"); + RequireInvalidArgument([] { flushToByte_nid_postfix(nullptr); }, "null flush state"); + RequireInvalidArgument([] { fillToByte_nid_postfix(nullptr); }, "null fill state"); + auto edge = Attach(ring, 0, 0, 0); + RequireInvalidArgument([&] { peekBit16_nid_postfix(&edge, 17); }, "17-bit peek"); + RequireInvalidArgument([&] { flushBit16_nid_postfix(&edge, 17); }, "17-bit flush"); + RequireInvalidArgument([&] { getBit16s_nid_postfix(&edge, 16); }, "16-bit signed read"); + RequireInvalidArgument([&] { getBit32_nid_postfix(&edge, 33); }, "33-bit read"); + RequireInvalidArgument([&] { putBit16z_nid_postfix(&edge, 2, 1); }, "wide unmasked value"); + RequireInvalidArgument([&] { putBit16_nid_postfix(&edge, 0, 17); }, "17-bit write"); + RequireInvalidArgument([&] { putBit32_nid_postfix(&edge, 0, 33); }, "33-bit write"); + edge.mask = -1; + RequireInvalidArgument([&] { flushBit16_nid_postfix(&edge, 1); }, "odd wrap mask"); + edge.mask = ReadMask; + edge.current = nullptr; + RequireInvalidArgument([&] { getBit16_nid_postfix(&edge, 1); }, "null current pointer"); +} + +} // namespace + +int main() { + RunReads(); + RunWrites(); + RunEdges(); + return 0; +} diff --git a/core/libs/tests/GuestJxrBitIoReference.c b/core/libs/tests/GuestJxrBitIoReference.c new file mode 100644 index 0000000000..1f6e789868 --- /dev/null +++ b/core/libs/tests/GuestJxrBitIoReference.c @@ -0,0 +1,26 @@ +#include "strcodec.h" +#include + +_Static_assert(sizeof(BitIOInfo) == 48, "BitIOInfo size"); + +uint32_t JxrBitIoReference_nid_no_patch(int operation, void* state, uint32_t value, uint32_t bits) { + BitIOInfo* io = (BitIOInfo*)state; + switch (operation) { + case 0: return peekBit16(io, bits); + case 1: return flushBit16(io, bits); + case 2: return getBit16(io, bits); + case 3: return getBool16(io); + case 4: return (uint32_t)getBit16s(io, bits); + case 5: return getBit32(io, bits); + case 6: return flushToByte(io); + case 7: return getSizeRead(io); + case 8: return getPosRead(io); + case 9: putBit16z(io, value, bits); return 0; + case 10: putBit16(io, value, bits); return 0; + case 11: putBit32(io, value, bits); return 0; + case 12: fillToByte(io); return 0; + case 13: return getSizeWrite(io); + case 14: return dquantBits((U8)value); + default: return 0xffffffffu; + } +} diff --git a/docs/dev/debt/libSceJxr.md b/docs/dev/debt/libSceJxr.md index 249c82055a..300d9ed07b 100644 --- a/docs/dev/debt/libSceJxr.md +++ b/docs/dev/debt/libSceJxr.md @@ -12,4 +12,8 @@ - [readIS and writeIS](../../../core/libs/prx/libSceJxr/Export.cpp) use the non-ARM, LP64 `BitIOInfo` and `WMPStream` ABI and packet algorithm from [Microsoft's JPEG XR reference implementation](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L1090-L1177). Console structure layouts and execution remain unverified. Profiling callbacks are omitted as in the reference Unix build with `DISABLE_PERF_MEASUREMENT`; the codec argument is unused. Where the reference uses an invalid pointer, `std::invalid_argument` is thrown instead: a null bit state, stream or callback, a read-only read packet, and a null packet when a read copies its shadow or a successful write offsets it; a null packet still reaches the callback first. No codec is instantiated by these helpers; callers must supply the reference ring buffer and guest-ABI stream callbacks. -- The 132 additional exports in [Export.cpp](../../../core/libs/prx/libSceJxr/Export.cpp) are present in the pinned firmware 13.60 `libSceJxr` export inventory. Their parameter lists, return types and behavior are unverified. Parameterless `int` declarations are import-resolution placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. The `_nid_postfix` aliases keep the guest bindings distinct from private native helpers. No runtime functionality is implemented. +- [Bit I/O](../../../core/libs/prx/libSceJxr/src/BitIo.cpp) (`peekBit16`, `flushBit16`, `getBit16`, `getBool16`, `getBit16s`, `getBit32`, `flushToByte`, `putBit16z`, `putBit16`, `putBit32`, `fillToByte`, `getSizeRead`, `getSizeWrite`, `getPosRead`, `dquantBits`) calls jxrlib's non-ARM [strcodec.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L905-L1061), built from `3rdparty/jxrlib`, on the guest `BitIOInfo`. That the console's module uses these prototypes and that layout is inferred from the export names. Bit counts over jxrlib's asserted limits, a value wider than `putBit16z`'s count, an odd read wrap mask and a null state or current pointer throw. Zero-bit reads and byte-aligned stores reach jxrlib's 32-bit shift by 32, which C leaves undefined. Closes when: the console's functions are disassembled or measured on a PS5 + +- [getBit16_S](../../../core/libs/prx/libSceJxr/src/BitIo.cpp) is `getBit16` followed by `readIS`, as in [strcodec.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L1021-L1027) with `readIS_L1` defined as `readIS`; the codec argument is passed through. Closes when: the console's function is disassembled or measured on a PS5 + +- The 116 additional exports in [Export.cpp](../../../core/libs/prx/libSceJxr/Export.cpp) are present in the pinned firmware 13.60 `libSceJxr` export inventory. Their parameter lists, return types and behavior are unverified. Parameterless `int` declarations are import-resolution placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. The `_nid_postfix` aliases keep the guest bindings distinct from private native helpers. No runtime functionality is implemented. From f3a9091e51b67dedb8527209f138ad73b7eea1a0 Mon Sep 17 00:00:00 2001 From: Noxy-HNR Date: Mon, 2 Nov 2026 01:07:14 +0000 Subject: [PATCH 224/612] refactor(libSceJxr): drop the explanatory comment from the bit I/O adapters --- core/libs/prx/libSceJxr/src/BitIo.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/libs/prx/libSceJxr/src/BitIo.cpp b/core/libs/prx/libSceJxr/src/BitIo.cpp index b2aee36a40..c3bfee54d8 100644 --- a/core/libs/prx/libSceJxr/src/BitIo.cpp +++ b/core/libs/prx/libSceJxr/src/BitIo.cpp @@ -3,9 +3,6 @@ #include #include -// Thin guest adapters over jxrlib's bit I/O in 3rdparty/jxrlib image/sys/strcodec.c (see NativeBitIo.c): -// they reject the arguments jxrlib only asserts on and pass everything else through unchanged. - extern "C" { std::uint32_t JxrPeekBit16_nid_no_patch(void* io, std::uint32_t bits); std::uint32_t JxrFlushBit16_nid_no_patch(void* io, std::uint32_t bits); From c7a34875b06a3f309354be4464c1dbab46c1abff Mon Sep 17 00:00:00 2001 From: Noxy-HNR Date: Mon, 2 Nov 2026 01:07:24 +0000 Subject: [PATCH 225/612] refactor(libSceJxr): drop explanatory comments from the jxrlib glue --- core/libs/prx/libSceJxr/CMakeLists.txt | 2 -- core/libs/prx/libSceJxr/src/GlueHelpers.cpp | 4 ---- core/libs/prx/libSceJxr/src/GuestHeap.h | 2 -- 3 files changed, 8 deletions(-) diff --git a/core/libs/prx/libSceJxr/CMakeLists.txt b/core/libs/prx/libSceJxr/CMakeLists.txt index b950f2b27d..616b7da9b6 100644 --- a/core/libs/prx/libSceJxr/CMakeLists.txt +++ b/core/libs/prx/libSceJxr/CMakeLists.txt @@ -3,8 +3,6 @@ configure_jxr_native(jxr_lookup_native) target_include_directories(jxr_lookup_native PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(jxr_lookup_native PRIVATE jxr) add_library(libSceJxr SHARED EXCLUDE_FROM_ALL Export.cpp src/GlueHelpers.cpp src/PixelFormatLookup.cpp src/GuidExports.cpp) -# Pinned jxrlib JXRGlue.c with its allocator routed to the guest heap, plus the C bridge -# GlueHelpers.cpp adapts; both sit in one archive ahead of jxr so this copy is the one linked. add_library(jxr_glue_guest STATIC ${JXR_SOURCE_DIR}/jxrgluelib/JXRGlue.c src/NativeGlue.c) configure_jxr_native(jxr_glue_guest) set_source_files_properties(${JXR_SOURCE_DIR}/jxrgluelib/JXRGlue.c TARGET_DIRECTORY jxr_glue_guest diff --git a/core/libs/prx/libSceJxr/src/GlueHelpers.cpp b/core/libs/prx/libSceJxr/src/GlueHelpers.cpp index 4f13804117..a328b6b262 100644 --- a/core/libs/prx/libSceJxr/src/GlueHelpers.cpp +++ b/core/libs/prx/libSceJxr/src/GlueHelpers.cpp @@ -6,10 +6,6 @@ #include #include -// Thin guest adapters over the pinned jxrlib built from 3rdparty/jxrlib (see NativeGlue.c and the -// jxr_glue_guest target): they check arguments jxrlib would fault on, map its IID values to the -// module's exported IID objects and route its allocator to the guest heap. - extern "C" { const void* JxrHashLookup_nid_no_patch(std::uint8_t hash); std::int64_t JxrEncodeIid_nid_no_patch(const char* extension, std::uint32_t* value); diff --git a/core/libs/prx/libSceJxr/src/GuestHeap.h b/core/libs/prx/libSceJxr/src/GuestHeap.h index 0270e05b97..777f12743a 100644 --- a/core/libs/prx/libSceJxr/src/GuestHeap.h +++ b/core/libs/prx/libSceJxr/src/GuestHeap.h @@ -1,8 +1,6 @@ #ifndef CORE_LIBS_PRX_LIBSCEJXR_SRC_GUESTHEAP_H #define CORE_LIBS_PRX_LIBSCEJXR_SRC_GUESTHEAP_H -/* Force-included into the pinned jxrlib JXRGlue.c built for libSceJxr, so its PKAlloc, - PKFree, PKAllocAligned and PKFreeAligned allocate from the guest heap. */ #include #include From 21a07dbca2d13315c491f00d60162a3a48f09995 Mon Sep 17 00:00:00 2001 From: Noxy-HNR Date: Mon, 2 Nov 2026 01:30:58 +0000 Subject: [PATCH 226/612] feat(libSceJxr): implement quantizer remapping, AC prediction mode and zigzag scan setup remapQP, formatQuantizer, getACPredMode and InitZigzagScan now call jxrlib's strPredQuant.c, strcodec.c and image.c from 3rdparty/jxrlib through a C bridge. The guest adapters only reject null pointers, more than 16 channels and scaled shifts that would overflow the QP. A new test compares every quantizer index, random quantizer tables, random macroblocks and the scan tables against jxrlib directly. --- core/libs/prx/libSceJxr/CMakeLists.txt | 20 ++- core/libs/prx/libSceJxr/Export.cpp | 16 -- core/libs/prx/libSceJxr/include/Api.hpp | 9 +- core/libs/prx/libSceJxr/src/NativeQuant.c | 23 +++ core/libs/prx/libSceJxr/src/Quantizer.cpp | 59 +++++++ core/libs/tests/GuestJxrQuantizer.cpp | 165 +++++++++++++++++++ core/libs/tests/GuestJxrQuantizerReference.c | 19 +++ docs/dev/debt/libSceJxr.md | 4 +- 8 files changed, 292 insertions(+), 23 deletions(-) create mode 100644 core/libs/prx/libSceJxr/src/NativeQuant.c create mode 100644 core/libs/prx/libSceJxr/src/Quantizer.cpp create mode 100644 core/libs/tests/GuestJxrQuantizer.cpp create mode 100644 core/libs/tests/GuestJxrQuantizerReference.c diff --git a/core/libs/prx/libSceJxr/CMakeLists.txt b/core/libs/prx/libSceJxr/CMakeLists.txt index 5d76a6870f..eb06c1c65c 100644 --- a/core/libs/prx/libSceJxr/CMakeLists.txt +++ b/core/libs/prx/libSceJxr/CMakeLists.txt @@ -1,8 +1,8 @@ -add_library(jxr_lookup_native STATIC src/NativeLookup.c) +add_library(jxr_lookup_native STATIC src/NativeLookup.c src/NativeQuant.c) configure_jxr_native(jxr_lookup_native) target_include_directories(jxr_lookup_native PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(jxr_lookup_native PRIVATE jxr) -add_library(libSceJxr SHARED EXCLUDE_FROM_ALL Export.cpp src/PixelFormatLookup.cpp src/GuidExports.cpp) +add_library(libSceJxr SHARED EXCLUDE_FROM_ALL Export.cpp src/PixelFormatLookup.cpp src/GuidExports.cpp src/Quantizer.cpp) target_link_libraries(libSceJxr PRIVATE jxr_lookup_native) if(NOT APPLE) target_link_options(libSceJxr PRIVATE LINKER:--exclude-libs,ALL) @@ -69,3 +69,19 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_library(jxr_quantizer_reference STATIC ${LIBS_INCLUDE_DIR}/tests/GuestJxrQuantizerReference.c) + configure_jxr_native(jxr_quantizer_reference) + target_link_libraries(jxr_quantizer_reference PRIVATE jxr) + add_test_executable(guest_jxr_quantizer_tests ${LIBS_INCLUDE_DIR}/tests/GuestJxrQuantizer.cpp) + target_include_directories(guest_jxr_quantizer_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_jxr_quantizer_tests PRIVATE libSceJxr libc jxr_quantizer_reference) + configure_windows_unwind(guest_jxr_quantizer_tests) + add_test(NAME guest_jxr_quantizer COMMAND guest_jxr_quantizer_tests) + set_tests_properties(guest_jxr_quantizer PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_jxr_quantizer PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceJxr/Export.cpp b/core/libs/prx/libSceJxr/Export.cpp index 50f91d2edc..91a0ad2231 100644 --- a/core/libs/prx/libSceJxr/Export.cpp +++ b/core/libs/prx/libSceJxr/Export.cpp @@ -116,10 +116,6 @@ int APS5_VABI IDPEmpty_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI InitZigzagScan_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI PKAllocAligned_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -448,10 +444,6 @@ int APS5_VABI flushToByte_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI formatQuantizer_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI freePredInfo_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -464,10 +456,6 @@ int APS5_VABI freeTileInfo_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI getACPredMode_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI getBit16_S_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -548,10 +536,6 @@ int APS5_VABI putBit32_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI remapQP_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI setBitIOPointers_nid_postfix() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceJxr/include/Api.hpp b/core/libs/prx/libSceJxr/include/Api.hpp index b57921294e..a720652609 100644 --- a/core/libs/prx/libSceJxr/include/Api.hpp +++ b/core/libs/prx/libSceJxr/include/Api.hpp @@ -10,6 +10,11 @@ std::int64_t APS5_VABI PKFree_nid_postfix(void** memory); std::int64_t APS5_VABI PixelFormatLookup_nid_postfix(JxrPixelInfo* value, std::uint8_t mode); std::int64_t APS5_VABI readIS_nid_postfix(void* codec, Jxr::BitIo* io); std::int64_t APS5_VABI writeIS_nid_postfix(void* codec, Jxr::BitIo* io); +void APS5_VABI remapQP_nid_postfix(void* quantizer, std::int32_t shift, std::int32_t scaled); +void APS5_VABI formatQuantizer_nid_postfix(void** quantizers, std::uint8_t mode, std::size_t channels, + std::size_t position, std::int32_t shiftedUv, std::int32_t scaled); +std::int32_t APS5_VABI getACPredMode_nid_postfix(void* macroblock, std::int32_t format); +void APS5_VABI InitZigzagScan_nid_postfix(void* context); int APS5_VABI AllocateCodingContextDec_nid_postfix(); int APS5_VABI CloseWS_List_nid_postfix(); @@ -25,7 +30,6 @@ int APS5_VABI GetPosWS_File_nid_postfix(); int APS5_VABI GetPosWS_List_nid_postfix(); int APS5_VABI GetPosWS_Memory_nid_postfix(); int APS5_VABI IDPEmpty_nid_postfix(); -int APS5_VABI InitZigzagScan_nid_postfix(); int APS5_VABI PKAllocAligned_nid_postfix(); int APS5_VABI PKAlloc_nid_postfix(); int APS5_VABI PKCodecFactory_CreateCodec_nid_postfix(); @@ -108,11 +112,9 @@ int APS5_VABI fillToByte_nid_postfix(); int APS5_VABI flushBit16_nid_postfix(); int APS5_VABI flushToByte_SB_nid_postfix(); int APS5_VABI flushToByte_nid_postfix(); -int APS5_VABI formatQuantizer_nid_postfix(); int APS5_VABI freePredInfo_nid_postfix(); int APS5_VABI freeQuantizer_nid_postfix(); int APS5_VABI freeTileInfo_nid_postfix(); -int APS5_VABI getACPredMode_nid_postfix(); int APS5_VABI getBit16_S_nid_postfix(); int APS5_VABI getBit16_nid_postfix(); int APS5_VABI getBit16s_nid_postfix(); @@ -133,7 +135,6 @@ int APS5_VABI postProcMB_nid_postfix(); int APS5_VABI putBit16_nid_postfix(); int APS5_VABI putBit16z_nid_postfix(); int APS5_VABI putBit32_nid_postfix(); -int APS5_VABI remapQP_nid_postfix(); int APS5_VABI setBitIOPointers_nid_postfix(); int APS5_VABI setUniformQuantizer_nid_postfix(); int APS5_VABI slideOneMBRow_nid_postfix(); diff --git a/core/libs/prx/libSceJxr/src/NativeQuant.c b/core/libs/prx/libSceJxr/src/NativeQuant.c new file mode 100644 index 0000000000..bd332dff86 --- /dev/null +++ b/core/libs/prx/libSceJxr/src/NativeQuant.c @@ -0,0 +1,23 @@ +#include "strcodec.h" +#include + +_Static_assert(sizeof(CWMIQuantizer) == 20, "CWMIQuantizer size"); +_Static_assert(sizeof(CWMIMBInfo) == 1160, "CWMIMBInfo size"); +_Static_assert(sizeof(CAdaptiveScan) == 8 && sizeof(CCodingContext) == 704, "CCodingContext size"); + +void JxrRemapQp_nid_no_patch(void* quantizer, int32_t shift, int32_t scaled) { + remapQP((CWMIQuantizer*)quantizer, shift, scaled); +} + +void JxrFormatQuantizer_nid_no_patch(void** quantizers, uint8_t mode, size_t channels, size_t position, + int32_t shiftedUv, int32_t scaled) { + formatQuantizer((CWMIQuantizer**)quantizers, mode, channels, position, shiftedUv, scaled); +} + +int32_t JxrGetAcPredMode_nid_no_patch(void* macroblock, int32_t format) { + return getACPredMode((CWMIMBInfo*)macroblock, (COLORFORMAT)format); +} + +void JxrInitZigzagScan_nid_no_patch(void* context) { + InitZigzagScan((CCodingContext*)context); +} diff --git a/core/libs/prx/libSceJxr/src/Quantizer.cpp b/core/libs/prx/libSceJxr/src/Quantizer.cpp new file mode 100644 index 0000000000..5f794e214a --- /dev/null +++ b/core/libs/prx/libSceJxr/src/Quantizer.cpp @@ -0,0 +1,59 @@ +#include "prx/libSceJxr/libSceJxr.hpp" +#include +#include +#include +#include + +extern "C" { +void JxrRemapQp_nid_no_patch(void* quantizer, std::int32_t shift, std::int32_t scaled); +void JxrFormatQuantizer_nid_no_patch(void** quantizers, std::uint8_t mode, std::size_t channels, std::size_t position, + std::int32_t shiftedUv, std::int32_t scaled); +std::int32_t JxrGetAcPredMode_nid_no_patch(void* macroblock, std::int32_t format); +void JxrInitZigzagScan_nid_no_patch(void* context); +} + +namespace { + +constexpr std::size_t MaxChannels = 16; + +void Require(bool condition, const char* function, const char* message) { + if (!condition) throw std::invalid_argument(std::string(function) + ": " + message); +} + +void RequireShift(const void* quantizer, std::int32_t shift, std::int32_t scaled, const char* function) { + Require(quantizer != nullptr, function, "null quantizer"); + const auto index = *static_cast(quantizer); + if (index == 0 || !scaled) return; + const auto exponent = index < 16 ? shift : (index >> 4) - 1 + shift; + Require(exponent >= 0 && exponent <= 24, function, "shift leaves the QP outside 32 bits"); +} + +} // namespace + +extern "C" { + +void APS5_VABI remapQP_nid_postfix(void* quantizer, std::int32_t shift, std::int32_t scaled) { + RequireShift(quantizer, shift, scaled, __func__); + JxrRemapQp_nid_no_patch(quantizer, shift, scaled); +} + +void APS5_VABI formatQuantizer_nid_postfix(void** quantizers, std::uint8_t mode, std::size_t channels, + std::size_t position, std::int32_t shiftedUv, std::int32_t scaled) { + if (channels == 0) return; + Require(quantizers != nullptr, __func__, "null quantizer table"); + Require(channels <= MaxChannels, __func__, "more than 16 channels"); + for (std::size_t channel = 0; channel < channels; ++channel) + Require(quantizers[channel] != nullptr, __func__, "null channel quantizer"); + JxrFormatQuantizer_nid_no_patch(quantizers, mode, channels, position, shiftedUv, scaled); +} + +std::int32_t APS5_VABI getACPredMode_nid_postfix(void* macroblock, std::int32_t format) { + Require(macroblock != nullptr, __func__, "null macroblock"); + return JxrGetAcPredMode_nid_no_patch(macroblock, format); +} + +void APS5_VABI InitZigzagScan_nid_postfix(void* context) { + JxrInitZigzagScan_nid_no_patch(context); +} + +} // extern "C" diff --git a/core/libs/tests/GuestJxrQuantizer.cpp b/core/libs/tests/GuestJxrQuantizer.cpp new file mode 100644 index 0000000000..e576d8c979 --- /dev/null +++ b/core/libs/tests/GuestJxrQuantizer.cpp @@ -0,0 +1,165 @@ +#include "prx/libSceJxr/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +void JxrQuantizerReferenceRemap_nid_no_patch(void* quantizer, std::int32_t shift, std::int32_t scaled); +void JxrQuantizerReferenceFormat_nid_no_patch(void** quantizers, std::uint8_t mode, std::size_t channels, + std::size_t position, std::int32_t shiftedUv, std::int32_t scaled); +std::int32_t JxrQuantizerReferenceAcMode_nid_no_patch(void* macroblock, std::int32_t format); +void JxrQuantizerReferenceZigzag_nid_no_patch(void* context); +} + +namespace { + +constexpr std::size_t QuantizerSize = 20; +constexpr std::size_t MacroblockSize = 1160; +constexpr std::size_t ContextSize = 704; + +struct Quantizer { + std::uint8_t index; + std::int32_t qp; + std::int32_t offset; + std::int32_t mantissa; + std::int32_t exponent; +}; +static_assert(sizeof(Quantizer) == QuantizerSize); + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "JXR quantizer: %s\n", message); + std::abort(); +} + +template +void RequireInvalidArgument(TAction action, const char* message) { + try { + action(); + } catch (const std::invalid_argument&) { + return; + } + Require(false, message); +} + +std::uint32_t seed = 0x13579bdf; +std::uint32_t Next() { + seed = seed * 1664525u + 1013904223u; + return seed >> 8; +} + +void TestRemap() { + for (std::int32_t scaled = 0; scaled < 2; ++scaled) { + for (std::int32_t shift = 0; shift < 3; ++shift) { + for (int index = 0; index < 256; ++index) { + Quantizer guest{}; + guest.index = static_cast(index); + guest.qp = -1; + Quantizer native = guest; + remapQP_nid_postfix(&guest, shift, scaled); + JxrQuantizerReferenceRemap_nid_no_patch(&native, shift, scaled); + Require(std::memcmp(&guest, &native, sizeof(guest)) == 0, "remapQP differs from jxrlib"); + } + } + } + Quantizer lossless{}; + lossless.qp = 9; + lossless.offset = lossless.mantissa = lossless.exponent = 7; + remapQP_nid_postfix(&lossless, 1, 1); + Require(lossless.qp == 1 && lossless.offset == 0 && lossless.mantissa == 0 && lossless.exponent == 0, + "index 0 is lossless"); + Quantizer plain{}; + plain.index = 4; + remapQP_nid_postfix(&plain, 1, 0); + Require(plain.qp == 1 && plain.offset == 0, "index 4 maps to QP 1 without scaled arithmetic"); + plain.index = 255; + remapQP_nid_postfix(&plain, 1, 1); + Require(plain.qp == 31 << 15 && plain.offset == ((plain.qp * 3 + 1) >> 3), "index 255 with shift 1"); + + RequireInvalidArgument([] { remapQP_nid_postfix(nullptr, 1, 1); }, "null quantizer"); + Quantizer wide{}; + wide.index = 255; + RequireInvalidArgument([&] { remapQP_nid_postfix(&wide, 11, 1); }, "QP over 32 bits"); + wide.index = 3; + RequireInvalidArgument([&] { remapQP_nid_postfix(&wide, -1, 1); }, "negative shift"); + remapQP_nid_postfix(&wide, -1, 0); +} + +void TestFormat() { + for (int round = 0; round < 2000; ++round) { + const std::size_t channels = 1 + Next() % 16; + const std::size_t cqp = 1 + Next() % 16; + const std::size_t position = Next() % cqp; + const auto mode = static_cast(Next() % 3); + const std::int32_t shifted = static_cast(Next() % 2); + const std::int32_t scaled = static_cast(Next() % 2); + std::array guestStore{}, nativeStore{}; + for (std::size_t i = 0; i < channels * cqp; ++i) { + guestStore[i].index = static_cast(Next()); + guestStore[i].qp = static_cast(Next()); + } + nativeStore = guestStore; + std::array guest{}, native{}; + for (std::size_t channel = 0; channel < channels; ++channel) { + guest[channel] = &guestStore[channel * cqp]; + native[channel] = &nativeStore[channel * cqp]; + } + formatQuantizer_nid_postfix(guest.data(), mode, channels, position, shifted, scaled); + JxrQuantizerReferenceFormat_nid_no_patch(native.data(), mode, channels, position, shifted, scaled); + Require(std::memcmp(guestStore.data(), nativeStore.data(), sizeof(guestStore)) == 0, + "formatQuantizer differs from jxrlib"); + } + formatQuantizer_nid_postfix(nullptr, 0, 0, 0, 0, 0); + std::array table{}; + RequireInvalidArgument([] { formatQuantizer_nid_postfix(nullptr, 0, 1, 0, 0, 0); }, "null quantizer table"); + RequireInvalidArgument([&] { formatQuantizer_nid_postfix(table.data(), 0, 1, 0, 0, 0); }, "null channel"); + RequireInvalidArgument([&] { formatQuantizer_nid_postfix(table.data(), 0, 17, 0, 0, 0); }, "17 channels"); +} + +void TestAcPredMode() { + std::array block{}; + for (int round = 0; round < 20000; ++round) { + for (auto& value : block) value = static_cast(Next() % 2001) - 1000; + if (round % 3 == 0) + for (std::size_t i = 0; i < 48; ++i) block[i] /= 1 + static_cast(Next() % 64); + const auto format = static_cast(round % 7); + Require(getACPredMode_nid_postfix(block.data(), format) == + JxrQuantizerReferenceAcMode_nid_no_patch(block.data(), format), + "getACPredMode differs from jxrlib"); + } + block.fill(0); + Require(getACPredMode_nid_postfix(block.data(), 0) == 2, "no energy"); + block[1] = 5; + Require(getACPredMode_nid_postfix(block.data(), 0) == 0, "horizontal energy"); + block[4] = 5; + Require(getACPredMode_nid_postfix(block.data(), 0) == 2, "balanced energy"); + block[1] = 0; + Require(getACPredMode_nid_postfix(block.data(), 0) == 1, "vertical energy"); + RequireInvalidArgument([] { getACPredMode_nid_postfix(nullptr, 0); }, "null macroblock"); +} + +void TestZigzag() { + std::array guest{}, native{}; + for (std::size_t i = 0; i < ContextSize; ++i) guest[i] = native[i] = static_cast(Next()); + InitZigzagScan_nid_postfix(guest.data()); + JxrQuantizerReferenceZigzag_nid_no_patch(native.data()); + Require(guest == native, "InitZigzagScan differs from jxrlib"); + std::uint32_t first = 0; + std::memcpy(&first, guest.data() + 216 + 4, sizeof(first)); + Require(first == 0, "lowpass scan starts at coefficient 0"); + InitZigzagScan_nid_postfix(nullptr); +} + +} // namespace + +int main() { + TestRemap(); + TestFormat(); + TestAcPredMode(); + TestZigzag(); + return 0; +} diff --git a/core/libs/tests/GuestJxrQuantizerReference.c b/core/libs/tests/GuestJxrQuantizerReference.c new file mode 100644 index 0000000000..d342b56e35 --- /dev/null +++ b/core/libs/tests/GuestJxrQuantizerReference.c @@ -0,0 +1,19 @@ +#include "strcodec.h" +#include + +void JxrQuantizerReferenceRemap_nid_no_patch(void* quantizer, int32_t shift, int32_t scaled) { + remapQP((CWMIQuantizer*)quantizer, shift, scaled); +} + +void JxrQuantizerReferenceFormat_nid_no_patch(void** quantizers, uint8_t mode, size_t channels, size_t position, + int32_t shiftedUv, int32_t scaled) { + formatQuantizer((CWMIQuantizer**)quantizers, mode, channels, position, shiftedUv, scaled); +} + +int32_t JxrQuantizerReferenceAcMode_nid_no_patch(void* macroblock, int32_t format) { + return getACPredMode((CWMIMBInfo*)macroblock, (COLORFORMAT)format); +} + +void JxrQuantizerReferenceZigzag_nid_no_patch(void* context) { + InitZigzagScan((CCodingContext*)context); +} diff --git a/docs/dev/debt/libSceJxr.md b/docs/dev/debt/libSceJxr.md index 249c82055a..c1d06a397b 100644 --- a/docs/dev/debt/libSceJxr.md +++ b/docs/dev/debt/libSceJxr.md @@ -12,4 +12,6 @@ - [readIS and writeIS](../../../core/libs/prx/libSceJxr/Export.cpp) use the non-ARM, LP64 `BitIOInfo` and `WMPStream` ABI and packet algorithm from [Microsoft's JPEG XR reference implementation](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L1090-L1177). Console structure layouts and execution remain unverified. Profiling callbacks are omitted as in the reference Unix build with `DISABLE_PERF_MEASUREMENT`; the codec argument is unused. Where the reference uses an invalid pointer, `std::invalid_argument` is thrown instead: a null bit state, stream or callback, a read-only read packet, and a null packet when a read copies its shadow or a successful write offsets it; a null packet still reaches the callback first. No codec is instantiated by these helpers; callers must supply the reference ring buffer and guest-ABI stream callbacks. -- The 132 additional exports in [Export.cpp](../../../core/libs/prx/libSceJxr/Export.cpp) are present in the pinned firmware 13.60 `libSceJxr` export inventory. Their parameter lists, return types and behavior are unverified. Parameterless `int` declarations are import-resolution placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. The `_nid_postfix` aliases keep the guest bindings distinct from private native helpers. No runtime functionality is implemented. +- [remapQP, formatQuantizer, getACPredMode and InitZigzagScan](../../../core/libs/prx/libSceJxr/src/Quantizer.cpp) call jxrlib's [strPredQuant.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strPredQuant.c#L79-L192), [strcodec.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L861-L874) and [image.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/image.c#L173-L183), built from `3rdparty/jxrlib`, on the caller's 20-byte quantizer, 1,160-byte macroblock and 704-byte coding context without `WMP_OPT_QT`. These prototypes and layouts are inferred from the export names. A null quantizer, quantizer table, used channel or macroblock, more than 16 channels, and a scaled shift that takes the QP outside 32 bits throw. Closes when: the console's functions are disassembled or measured on a PS5 + +- The 128 additional exports in [Export.cpp](../../../core/libs/prx/libSceJxr/Export.cpp) are present in the pinned firmware 13.60 `libSceJxr` export inventory. Their parameter lists, return types and behavior are unverified. Parameterless `int` declarations are import-resolution placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. The `_nid_postfix` aliases keep the guest bindings distinct from private native helpers. No runtime functionality is implemented. From 8ed02595bc799a5972f87c63637e7cb752832ae9 Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Mon, 2 Nov 2026 02:41:34 +0100 Subject: [PATCH 227/612] feat(libSceContentSearch): implement the application queries Recover the signatures of sceContentSearchGetNumOfApplication, sceContentSearchGetApplicationLastUpdateId and sceContentSearchGetMyApplicationIndex from the 11.00 module wrappers and answer them from the empty host library: zero applications, update id 0 and application index 0. The wrappers establish the pointer/length pair checks, the 64-bit update-id output, the 32-bit index output and the initialization and null-pointer error order. sceContentSearchSearchApplication and the metadata and AV-event exports still throw. --- core/libs/prx/libSceContentSearch/Export.cpp | 21 ++++++++---- .../prx/libSceContentSearch/include/Api.hpp | 6 ++-- core/libs/tests/GuestContentSearch.cpp | 32 +++++++++++++++++-- docs/dev/debt/libSceContentSearch.md | 3 +- 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/core/libs/prx/libSceContentSearch/Export.cpp b/core/libs/prx/libSceContentSearch/Export.cpp index 13c17b161a..72a644817c 100644 --- a/core/libs/prx/libSceContentSearch/Export.cpp +++ b/core/libs/prx/libSceContentSearch/Export.cpp @@ -190,16 +190,25 @@ int APS5_VABI sceContentBinderStart() { int APS5_VABI sceContentBinderTerminate() { return static_cast(0x809D000F); } -int APS5_VABI sceContentSearchGetApplicationLastUpdateId() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceContentSearchGetApplicationLastUpdateId(std::int64_t* lastUpdateId) { + if (!g_initialized.load()) return ErrorNotInitialized; + if (lastUpdateId == nullptr) return ErrorInvalidArgument; + *lastUpdateId = LibraryUpdateId; return 0; } -int APS5_VABI sceContentSearchGetMyApplicationIndex() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceContentSearchGetMyApplicationIndex(std::int32_t* index) { + if (!g_initialized.load()) return ErrorNotInitialized; + if (index == nullptr) return ErrorInvalidArgument; + *index = 0; return 0; } -int APS5_VABI sceContentSearchGetNumOfApplication() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceContentSearchGetNumOfApplication(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, const ContentSearchContentOrderByCondition* orderByConditions, std::uint32_t orderByConditionsLength, std::int64_t* numOfApplication, std::int64_t* lastUpdateId) { + if (!g_initialized.load()) return ErrorNotInitialized; + if ((columnSet == nullptr) != (columnSetLength == 0) || (orderByConditions == nullptr) != (orderByConditionsLength == 0) || numOfApplication == nullptr || lastUpdateId == nullptr) return ErrorInvalidArgument; + ValidateColumnSets(__func__, columnSet, columnSetLength); + ValidateOrderBy(__func__, orderByConditions, orderByConditionsLength); + *numOfApplication = 0; + *lastUpdateId = LibraryUpdateId; return 0; } int APS5_VABI sceContentSearchSearchApplication() { diff --git a/core/libs/prx/libSceContentSearch/include/Api.hpp b/core/libs/prx/libSceContentSearch/include/Api.hpp index b54e8efb17..c648e0c87f 100644 --- a/core/libs/prx/libSceContentSearch/include/Api.hpp +++ b/core/libs/prx/libSceContentSearch/include/Api.hpp @@ -23,9 +23,9 @@ int APS5_VABI sceContentBinderReset(); int APS5_VABI sceContentBinderSearchDifference(); int APS5_VABI sceContentBinderStart(); int APS5_VABI sceContentBinderTerminate(); -int APS5_VABI sceContentSearchGetApplicationLastUpdateId(); -int APS5_VABI sceContentSearchGetMyApplicationIndex(); -int APS5_VABI sceContentSearchGetNumOfApplication(); +int APS5_VABI sceContentSearchGetApplicationLastUpdateId(std::int64_t* lastUpdateId); +int APS5_VABI sceContentSearchGetMyApplicationIndex(std::int32_t* index); +int APS5_VABI sceContentSearchGetNumOfApplication(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, const ContentSearchContentOrderByCondition* orderByConditions, std::uint32_t orderByConditionsLength, std::int64_t* numOfApplication, std::int64_t* lastUpdateId); int APS5_VABI sceContentSearchSearchApplication(); } diff --git a/core/libs/tests/GuestContentSearch.cpp b/core/libs/tests/GuestContentSearch.cpp index 7101d21c48..76c6660d85 100644 --- a/core/libs/tests/GuestContentSearch.cpp +++ b/core/libs/tests/GuestContentSearch.cpp @@ -49,7 +49,11 @@ int main() { std::int64_t size = -1; std::int64_t updateId = -1; std::int32_t metadataId = -1; + std::int32_t applicationIndex = -1; + Require(sceContentSearchGetApplicationLastUpdateId(&updateId) == ErrorNotInitialized); + Require(sceContentSearchGetMyApplicationIndex(&applicationIndex) == ErrorNotInitialized); + Require(sceContentSearchGetNumOfApplication(&columnSet, 1, &orderBy, 1, &count, &updateId) == ErrorNotInitialized); Require(sceContentSearchGetContentLastUpdateId(&updateId) == ErrorNotInitialized); Require(sceContentSearchGetNumOfContent(&columnSet, 1, &count, &updateId) == ErrorNotInitialized); Require(sceContentSearchGetTotalContentSize(&columnSet, 1, &size, &updateId) == ErrorNotInitialized); @@ -92,8 +96,28 @@ int main() { updateId = -1; Require(sceContentSearchSearchContent(&columnSet, 1, &orderBy, 1, 0, 93, &count, infos, &updateId) == ErrorLimitTooBig); Require(count == -1 && updateId == -1); + updateId = -1; + Require(sceContentSearchGetApplicationLastUpdateId(&updateId) == 0 && updateId == 0); + applicationIndex = -1; + Require(sceContentSearchGetMyApplicationIndex(&applicationIndex) == 0 && applicationIndex == 0); + count = -1; + updateId = -1; + Require(sceContentSearchGetNumOfApplication(&columnSet, 1, &orderBy, 1, &count, &updateId) == 0); + Require(count == 0 && updateId == 0); + count = -1; + updateId = -1; + Require(sceContentSearchGetNumOfApplication(nullptr, 0, nullptr, 0, &count, &updateId) == 0); + Require(count == 0 && updateId == 0); + count = -1; + updateId = -1; Require(sceContentSearchGetContentLastUpdateId(nullptr) == ErrorInvalidArgument); + Require(sceContentSearchGetApplicationLastUpdateId(nullptr) == ErrorInvalidArgument); + Require(sceContentSearchGetMyApplicationIndex(nullptr) == ErrorInvalidArgument); + Require(sceContentSearchGetNumOfApplication(&columnSet, 1, &orderBy, 1, nullptr, &updateId) == ErrorInvalidArgument); + Require(sceContentSearchGetNumOfApplication(&columnSet, 1, &orderBy, 1, &count, nullptr) == ErrorInvalidArgument); + Require(sceContentSearchGetNumOfApplication(&columnSet, 1, &orderBy, 0, &count, &updateId) == ErrorInvalidArgument); + Require(sceContentSearchGetNumOfApplication(&columnSet, 0, &orderBy, 1, &count, &updateId) == ErrorInvalidArgument); Require(sceContentSearchGetNumOfContent(&columnSet, 1, nullptr, &updateId) == ErrorInvalidArgument); Require(sceContentSearchGetNumOfContent(&columnSet, 1, &count, nullptr) == ErrorInvalidArgument); Require(sceContentSearchGetTotalContentSize(nullptr, 1, &size, &updateId) == ErrorInvalidArgument); @@ -113,6 +137,7 @@ int main() { ContentSearchContentColumn unknownColumn{8, 0, &photo, 2}; ContentSearchContentColumnSet unknownColumnSet{&unknownColumn, 1, 2}; Require(Throws([&] { sceContentSearchGetNumOfContent(&unknownColumnSet, 1, &count, &updateId); })); + Require(Throws([&] { sceContentSearchGetNumOfApplication(&unknownColumnSet, 1, &orderBy, 1, &count, &updateId); })); ContentSearchContentColumn missingValue{3, 0, nullptr, 2}; ContentSearchContentColumnSet missingValueSet{&missingValue, 1, 2}; Require(Throws([&] { sceContentSearchGetTotalContentSize(&missingValueSet, 1, &size, &updateId); })); @@ -121,6 +146,7 @@ int main() { Require(Throws([&] { sceContentSearchSearchContent(&unknownOperatorSet, 1, &orderBy, 1, 0, 2, &count, infos, &updateId); })); ContentSearchContentOrderByCondition unknownSort{3, 0}; Require(Throws([&] { sceContentSearchSearchContent(&columnSet, 1, &unknownSort, 1, 0, 2, &count, infos, &updateId); })); + Require(Throws([&] { sceContentSearchGetNumOfApplication(&columnSet, 1, &unknownSort, 1, &count, &updateId); })); Require(count == -1 && updateId == -1); Require(sceContentSearchSearchContent(nullptr, 0, nullptr, 0, 0, 0, nullptr, nullptr, nullptr) == ErrorInvalidArgument); Require(sceContentSearchSearchContent(nullptr, 0, nullptr, 0, 0, 93, nullptr, nullptr, nullptr) == ErrorLimitTooBig); @@ -141,6 +167,9 @@ int main() { Require(sceContentSearchSearchContent(&columnSet, 1, &orderBy, 1, 0, 2, &count, infos, &updateId) == ErrorNotInitialized); Require(sceContentSearchCloseMetadata(1) == ErrorNotInitialized); Require(sceContentSearchGetContentLastUpdateId(&updateId) == ErrorNotInitialized); + Require(sceContentSearchGetApplicationLastUpdateId(&updateId) == ErrorNotInitialized); + Require(sceContentSearchGetMyApplicationIndex(&applicationIndex) == ErrorNotInitialized); + Require(sceContentSearchGetNumOfApplication(&columnSet, 1, &orderBy, 1, &count, &updateId) == ErrorNotInitialized); Require(updateId == -1); constexpr int binderResult = static_cast(0x809D000F); Require(sceContentBinderInitialize() == binderResult); @@ -152,8 +181,5 @@ int main() { Require(sceContentBinderEnd() == binderResult); Require(sceContentBinderTerminate() == binderResult); - Require(ThrowsRuntimeError([] { sceContentSearchGetApplicationLastUpdateId(); })); - Require(ThrowsRuntimeError([] { sceContentSearchGetMyApplicationIndex(); })); - Require(ThrowsRuntimeError([] { sceContentSearchGetNumOfApplication(); })); Require(ThrowsRuntimeError([] { sceContentSearchSearchApplication(); })); } diff --git a/docs/dev/debt/libSceContentSearch.md b/docs/dev/debt/libSceContentSearch.md index 8051fd4b39..7ee5185575 100644 --- a/docs/dev/debt/libSceContentSearch.md +++ b/docs/dev/debt/libSceContentSearch.md @@ -11,11 +11,12 @@ | `sceContentSearchEnableAvContentCreatedEvent` | `vlOqhFPOLDs` | | `sceContentSearchGetContentIdByAvContentCreatedEvent` | `2Dj56xvkS5M` | -- [sceContentBinderEnd, sceContentBinderGetNumOfDifference, sceContentBinderInitialize, sceContentBinderIsUpdated, sceContentBinderReset, sceContentBinderSearchDifference, sceContentBinderStart, sceContentBinderTerminate, sceContentSearchGetApplicationLastUpdateId, sceContentSearchGetMyApplicationIndex, sceContentSearchGetNumOfApplication and sceContentSearchSearchApplication](../../../core/libs/prx/libSceContentSearch/Export.cpp) (libSceContentSearch) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceContentSearch.c), but no credible prototypes were found in the cited sources. They are declared without parameters solely to resolve known imports and make no ABI claim. +- [sceContentBinderEnd, sceContentBinderGetNumOfDifference, sceContentBinderInitialize, sceContentBinderIsUpdated, sceContentBinderReset, sceContentBinderSearchDifference, sceContentBinderStart, sceContentBinderTerminate and sceContentSearchSearchApplication](../../../core/libs/prx/libSceContentSearch/Export.cpp) (libSceContentSearch) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceContentSearch.c), but no credible prototypes were found in the cited sources. They are declared without parameters solely to resolve known imports and make no ABI claim. ### Functional - [Content queries](../../../core/libs/prx/libSceContentSearch/Export.cpp) use an empty host library and always report update id 0; the console's initial update id is unverified. No content rows are produced. Every title is assumed to have search permission. Nested filter/sort validation is incomplete: empty nested column sets, null filter values and unknown enum values throw. Guest heap allocation and the platform-dependent minimum heap size are unimplemented. Initialization and outer pointer/count/limit validation follow the supplied module. +- [sceContentSearchGetNumOfApplication, sceContentSearchGetApplicationLastUpdateId and sceContentSearchGetMyApplicationIndex](../../../core/libs/prx/libSceContentSearch/Export.cpp) (libSceContentSearch) - signatures from the 11.00 module wrapper: `GetNumOfApplication` takes the column-set and order-by pairs plus the count and update-id outputs; `GetApplicationLastUpdateId` writes a 64-bit update id; `GetMyApplicationIndex` writes a 32-bit index, defaulting it to 0 before delegating. The host reports zero applications and update id 0, and index 0, because the host library has no application records; the console values are unverified. All three check initialization and null outputs first, and `GetNumOfApplication` validates the column-set and order-by contents as the content queries do. `sceContentSearchSearchApplication` still throws because its eleven-argument contract is not mapped. - [Metadata access](../../../core/libs/prx/libSceContentSearch/Export.cpp) cannot open metadata by path or content id because missing-content errors remain unresolved: past their argument checks, both opening calls go through `NotImplemented_nid_no_patch`. No metadata handles are issued. The field-info and value queries reject invalid outer pointers, then go through `NotImplemented_nid_no_patch`, as no metadata id is ever opened and the console's error for one depends on the requested field; closing an unopened handle returns the confirmed invalid-argument error. ### Additional callable exports From a6ec5d648b3231bc550b97774d80c1b5361e893d Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Mon, 2 Nov 2026 03:34:03 +0100 Subject: [PATCH 228/612] feat(libSceNpUtility): implement the service checker 2 requests Recover the eight sceNpServiceChecker2Int signatures and error codes from the 11.00 wrappers: Initialize/Finalize hold one host state, CreateRequest writes request ids from 1, Destroy/Abort remove them, GetServiceAvailability and its A form return SIGNED_OUT without writing because no network is available, and IsSetServiceType reads bit 0..9 of a 32-bit mask. The console's availability payload, exact offline error and any request limit are not measured. --- core/libs/prx/libSceNpUtility/Export.cpp | 67 ++++++++++++++----- core/libs/prx/libSceNpUtility/include/Api.hpp | 12 ++-- core/libs/tests/GuestNpUtilityOffline.cpp | 38 +++++++++++ docs/dev/debt/libSceNpUtility.md | 5 +- 4 files changed, 97 insertions(+), 25 deletions(-) diff --git a/core/libs/prx/libSceNpUtility/Export.cpp b/core/libs/prx/libSceNpUtility/Export.cpp index 5f007ec2d8..7d6c626795 100644 --- a/core/libs/prx/libSceNpUtility/Export.cpp +++ b/core/libs/prx/libSceNpUtility/Export.cpp @@ -18,6 +18,10 @@ static constexpr int SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = static_cast< static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ID = static_cast(0x8055070E); static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_TYPE = static_cast(0x80550711); static constexpr int SCE_NP_LOOKUP_ERROR_SYNCHRONOUS_REQUEST = static_cast(0x80550307); +static constexpr int SCE_NP_SERVICE_CHECKER2_ERROR_ALREADY_INITIALIZED = static_cast(0x8055B301); +static constexpr int SCE_NP_SERVICE_CHECKER2_ERROR_NOT_INITIALIZED = static_cast(0x8055B302); +static constexpr int SCE_NP_SERVICE_CHECKER2_ERROR_INVALID_ARGUMENT = static_cast(0x8055B305); +static constexpr int SCE_NP_SERVICE_CHECKER2_ERROR_REQUEST_NOT_FOUND = static_cast(0x8055B304); static constexpr int POLL_FINISHED = 0; static constexpr int POLL_RUNNING = 1; static constexpr std::size_t MAX_TITLE_CONTEXTS = 32; @@ -52,6 +56,14 @@ struct Service { Service lookup(1, 1); Service wordFilter(WORD_FILTER_TITLE_CTX_ID_OFFSET + 1, WORD_FILTER_REQUEST_ID_OFFSET + 1); +struct ServiceChecker2 { + std::mutex mutex; + bool initialized = false; + std::unordered_set requests; + int nextRequest = 1; +}; +ServiceChecker2 serviceChecker2; + int AddTitleContext(Service& service) { std::lock_guard lock(service.mutex); if (service.titleContexts.size() >= MAX_TITLE_CONTEXTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; @@ -369,37 +381,58 @@ int APS5_VABI sceNpLookupNetTerm() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpServiceChecker2IntAbortRequest() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpServiceChecker2IntAbortRequest(int requestId) { + std::lock_guard lock(serviceChecker2.mutex); + if (!serviceChecker2.initialized) return SCE_NP_SERVICE_CHECKER2_ERROR_NOT_INITIALIZED; + serviceChecker2.requests.erase(requestId); return 0; } -int APS5_VABI sceNpServiceChecker2IntCreateRequest() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpServiceChecker2IntCreateRequest(int* requestId) { + std::lock_guard lock(serviceChecker2.mutex); + if (!serviceChecker2.initialized) return SCE_NP_SERVICE_CHECKER2_ERROR_NOT_INITIALIZED; + if (!requestId) return SCE_NP_SERVICE_CHECKER2_ERROR_INVALID_ARGUMENT; + const int id = serviceChecker2.nextRequest++; + serviceChecker2.requests.insert(id); + *requestId = id; return 0; } -int APS5_VABI sceNpServiceChecker2IntDestroyRequest() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpServiceChecker2IntDestroyRequest(int requestId) { + std::lock_guard lock(serviceChecker2.mutex); + if (!serviceChecker2.initialized) return SCE_NP_SERVICE_CHECKER2_ERROR_NOT_INITIALIZED; + serviceChecker2.requests.erase(requestId); return 0; } int APS5_VABI sceNpServiceChecker2IntFinalize() { - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(serviceChecker2.mutex); + serviceChecker2.requests.clear(); + serviceChecker2.initialized = false; return 0; } -int APS5_VABI sceNpServiceChecker2IntGetServiceAvailability() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpServiceChecker2IntGetServiceAvailability(int requestId, const void* npId, void* result) { + std::lock_guard lock(serviceChecker2.mutex); + if (!serviceChecker2.initialized) return SCE_NP_SERVICE_CHECKER2_ERROR_NOT_INITIALIZED; + if (!npId || !result) return SCE_NP_SERVICE_CHECKER2_ERROR_INVALID_ARGUMENT; + if (!serviceChecker2.requests.contains(requestId)) return SCE_NP_SERVICE_CHECKER2_ERROR_REQUEST_NOT_FOUND; + return SCE_NP_ERROR_SIGNED_OUT; } -int APS5_VABI sceNpServiceChecker2IntGetServiceAvailabilityA() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpServiceChecker2IntGetServiceAvailabilityA(int requestId, int userId, void* result) { + std::lock_guard lock(serviceChecker2.mutex); + if (!serviceChecker2.initialized) return SCE_NP_SERVICE_CHECKER2_ERROR_NOT_INITIALIZED; + if (userId == -1 || !result) return SCE_NP_SERVICE_CHECKER2_ERROR_INVALID_ARGUMENT; + if (!serviceChecker2.requests.contains(requestId)) return SCE_NP_SERVICE_CHECKER2_ERROR_REQUEST_NOT_FOUND; + return SCE_NP_ERROR_SIGNED_OUT; } int APS5_VABI sceNpServiceChecker2IntInitialize() { - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(serviceChecker2.mutex); + if (serviceChecker2.initialized) return SCE_NP_SERVICE_CHECKER2_ERROR_ALREADY_INITIALIZED; + serviceChecker2.requests.clear(); + serviceChecker2.nextRequest = 1; + serviceChecker2.initialized = true; return 0; } -int APS5_VABI sceNpServiceChecker2IntIsSetServiceType() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpServiceChecker2IntIsSetServiceType(const std::uint32_t* serviceTypes, std::uint32_t serviceType) { + if (!serviceTypes || serviceType > 9) return 0; + return static_cast((serviceTypes[serviceType >> 5] >> serviceType) & 1); } int APS5_VABI sceNpServiceCheckerIntAbortRequest() { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/prx/libSceNpUtility/include/Api.hpp b/core/libs/prx/libSceNpUtility/include/Api.hpp index 469ecd9005..cb1e95553d 100644 --- a/core/libs/prx/libSceNpUtility/include/Api.hpp +++ b/core/libs/prx/libSceNpUtility/include/Api.hpp @@ -77,14 +77,14 @@ int APS5_VABI sceNpLookupNetNpId(); int APS5_VABI sceNpLookupNetSanitizeComment(); int APS5_VABI sceNpLookupNetSetTimeout(); int APS5_VABI sceNpLookupNetTerm(); -int APS5_VABI sceNpServiceChecker2IntAbortRequest(); -int APS5_VABI sceNpServiceChecker2IntCreateRequest(); -int APS5_VABI sceNpServiceChecker2IntDestroyRequest(); +int APS5_VABI sceNpServiceChecker2IntAbortRequest(int requestId); +int APS5_VABI sceNpServiceChecker2IntCreateRequest(int* requestId); +int APS5_VABI sceNpServiceChecker2IntDestroyRequest(int requestId); int APS5_VABI sceNpServiceChecker2IntFinalize(); -int APS5_VABI sceNpServiceChecker2IntGetServiceAvailability(); -int APS5_VABI sceNpServiceChecker2IntGetServiceAvailabilityA(); +int APS5_VABI sceNpServiceChecker2IntGetServiceAvailability(int requestId, const void* npId, void* result); +int APS5_VABI sceNpServiceChecker2IntGetServiceAvailabilityA(int requestId, int userId, void* result); int APS5_VABI sceNpServiceChecker2IntInitialize(); -int APS5_VABI sceNpServiceChecker2IntIsSetServiceType(); +int APS5_VABI sceNpServiceChecker2IntIsSetServiceType(const std::uint32_t* serviceTypes, std::uint32_t serviceType); int APS5_VABI sceNpServiceCheckerIntAbortRequest(); int APS5_VABI sceNpServiceCheckerIntCreateRequest(); int APS5_VABI sceNpServiceCheckerIntDestroyRequest(); diff --git a/core/libs/tests/GuestNpUtilityOffline.cpp b/core/libs/tests/GuestNpUtilityOffline.cpp index f9604b54ae..2a0cffa199 100644 --- a/core/libs/tests/GuestNpUtilityOffline.cpp +++ b/core/libs/tests/GuestNpUtilityOffline.cpp @@ -120,6 +120,43 @@ void CheckWordFilter() { Require(sceNpLookupDeleteTitleCtx(context) == InvalidId, "sceNpLookupDeleteTitleCtx", "a word filter context was found among the lookup contexts"); } +void CheckServiceChecker2() { + constexpr int AlreadyInitialized = static_cast(0x8055B301u); + constexpr int NotInitialized = static_cast(0x8055B302u); + constexpr int RequestNotFound = static_cast(0x8055B304u); + constexpr int CheckerInvalidArgument = static_cast(0x8055B305u); + int request = 0; + Require(sceNpServiceChecker2IntCreateRequest(&request) == NotInitialized, "sceNpServiceChecker2IntCreateRequest", "a request was created before initialization"); + Require(sceNpServiceChecker2IntInitialize() == 0, "sceNpServiceChecker2IntInitialize", "initialization failed"); + Require(sceNpServiceChecker2IntInitialize() == AlreadyInitialized, "sceNpServiceChecker2IntInitialize", "a second initialization was taken"); + Require(sceNpServiceChecker2IntCreateRequest(nullptr) == CheckerInvalidArgument, "sceNpServiceChecker2IntCreateRequest", "a null output was taken"); + Require(sceNpServiceChecker2IntCreateRequest(&request) == 0 && request == 1, "sceNpServiceChecker2IntCreateRequest", "the first request is not id 1"); + std::uint32_t availability = 0xA5A5A5A5; + Require(sceNpServiceChecker2IntGetServiceAvailability(request, nullptr, &availability) == CheckerInvalidArgument, "sceNpServiceChecker2IntGetServiceAvailability", "a null NpId was taken"); + Require(sceNpServiceChecker2IntGetServiceAvailability(request, selfNpId, nullptr) == CheckerInvalidArgument, "sceNpServiceChecker2IntGetServiceAvailability", "a null output was taken"); + Require(sceNpServiceChecker2IntGetServiceAvailability(request + 100, selfNpId, &availability) == RequestNotFound, "sceNpServiceChecker2IntGetServiceAvailability", "an unknown request was taken"); + Require(sceNpServiceChecker2IntGetServiceAvailability(request, selfNpId, &availability) == SignedOut, "sceNpServiceChecker2IntGetServiceAvailability", "the lookup did not return SIGNED_OUT"); + Require(availability == 0xA5A5A5A5, "sceNpServiceChecker2IntGetServiceAvailability", "an availability was written"); + Require(sceNpServiceChecker2IntGetServiceAvailabilityA(request, -1, &availability) == CheckerInvalidArgument, "sceNpServiceChecker2IntGetServiceAvailabilityA", "user -1 was taken"); + Require(sceNpServiceChecker2IntGetServiceAvailabilityA(request, 1, nullptr) == CheckerInvalidArgument, "sceNpServiceChecker2IntGetServiceAvailabilityA", "a null output was taken"); + Require(sceNpServiceChecker2IntGetServiceAvailabilityA(request + 100, 1, &availability) == RequestNotFound, "sceNpServiceChecker2IntGetServiceAvailabilityA", "an unknown request was taken"); + Require(sceNpServiceChecker2IntGetServiceAvailabilityA(request, 1, &availability) == SignedOut, "sceNpServiceChecker2IntGetServiceAvailabilityA", "the lookup did not return SIGNED_OUT"); + Require(availability == 0xA5A5A5A5, "sceNpServiceChecker2IntGetServiceAvailabilityA", "an availability was written"); + int second = 0; + Require(sceNpServiceChecker2IntCreateRequest(&second) == 0 && second == 2, "sceNpServiceChecker2IntCreateRequest", "the second request is not id 2"); + Require(sceNpServiceChecker2IntDestroyRequest(second) == 0, "sceNpServiceChecker2IntDestroyRequest", "destroying the request failed"); + Require(sceNpServiceChecker2IntGetServiceAvailability(second, selfNpId, &availability) == RequestNotFound, "sceNpServiceChecker2IntDestroyRequest", "a destroyed request was found"); + Require(sceNpServiceChecker2IntAbortRequest(request) == 0, "sceNpServiceChecker2IntAbortRequest", "aborting the request failed"); + const std::uint32_t serviceTypes[1] = {0x5}; + Require(sceNpServiceChecker2IntIsSetServiceType(serviceTypes, 0) == 1, "sceNpServiceChecker2IntIsSetServiceType", "bit 0 was not set"); + Require(sceNpServiceChecker2IntIsSetServiceType(serviceTypes, 1) == 0, "sceNpServiceChecker2IntIsSetServiceType", "bit 1 was set"); + Require(sceNpServiceChecker2IntIsSetServiceType(serviceTypes, 2) == 1, "sceNpServiceChecker2IntIsSetServiceType", "bit 2 was not set"); + Require(sceNpServiceChecker2IntIsSetServiceType(serviceTypes, 10) == 0, "sceNpServiceChecker2IntIsSetServiceType", "type 10 was taken"); + Require(sceNpServiceChecker2IntIsSetServiceType(nullptr, 0) == 0, "sceNpServiceChecker2IntIsSetServiceType", "a null mask was taken"); + Require(sceNpServiceChecker2IntFinalize() == 0, "sceNpServiceChecker2IntFinalize", "finalization failed"); + Require(sceNpServiceChecker2IntCreateRequest(&request) == NotInitialized, "sceNpServiceChecker2IntCreateRequest", "a request was created after finalization"); +} + void CheckBandwidthTestInitStart() { Require(sceNpBandwidthTestInitStart() == SignedOut, "sceNpBandwidthTestInitStart", "starting a test while signed out did not return SIGNED_OUT"); Require(sceNpBandwidthTestInitStart() == sceNpBandwidthTestInitStartDownload(nullptr), "sceNpBandwidthTestInitStart", "the answer differs from sceNpBandwidthTestInitStartDownload"); @@ -259,6 +296,7 @@ int main() { std::strcpy(reinterpret_cast(selfNpId), "player_1"); CheckBandwidthTestInitStart(); CheckBandwidthTestShutdownWithDetailedInfo(); + CheckServiceChecker2(); CheckLookup(); CheckWordFilter(); CheckLookupAsync(); diff --git a/docs/dev/debt/libSceNpUtility.md b/docs/dev/debt/libSceNpUtility.md index a026372797..ab3d26649a 100644 --- a/docs/dev/debt/libSceNpUtility.md +++ b/docs/dev/debt/libSceNpUtility.md @@ -11,14 +11,15 @@ - [sceNpLookupNetAbortRequest and sceNpLookupNetCensorComment](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. - [sceNpLookupNetConvertJidToNpId, sceNpLookupNetConvertNpIdToJid, sceNpLookupNetCreateRequest, sceNpLookupNetCreateTitleCtx, sceNpLookupNetDeleteRequest, sceNpLookupNetDeleteTitleCtx and sceNpLookupNetInit](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. - [sceNpLookupNetInitWithFunctionPointer, sceNpLookupNetInitWithMemoryPool, sceNpLookupNetIsInit, sceNpLookupNetNpId, sceNpLookupNetSanitizeComment, sceNpLookupNetSetTimeout and sceNpLookupNetTerm](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. -- [sceNpServiceChecker2IntAbortRequest, sceNpServiceChecker2IntCreateRequest, sceNpServiceChecker2IntDestroyRequest and sceNpServiceChecker2IntFinalize](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. -- [sceNpServiceChecker2IntGetServiceAvailability, sceNpServiceChecker2IntGetServiceAvailabilityA, sceNpServiceChecker2IntInitialize, sceNpServiceChecker2IntIsSetServiceType and sceNpServiceCheckerIntAbortRequest](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpServiceCheckerIntAbortRequest](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. - [sceNpServiceCheckerIntCreateRequest, sceNpServiceCheckerIntDestroyRequest, sceNpServiceCheckerIntFinalize, sceNpServiceCheckerIntGetAvailability, sceNpServiceCheckerIntGetAvailabilityList and sceNpServiceCheckerIntInitialize](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. - [sceNpServiceCheckerIntIsCached, sceNpTitleMetadataIntAbortRequest, sceNpTitleMetadataIntCreateRequest, sceNpTitleMetadataIntDeleteRequest, sceNpTitleMetadataIntGetInfo, sceNpTitleMetadataIntGetNpTitleId and sceNpUtilityInit](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. - [sceNpUtilityTerm](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. ### Functional +- [sceNpServiceChecker2IntAbortRequest, sceNpServiceChecker2IntCreateRequest, sceNpServiceChecker2IntDestroyRequest, sceNpServiceChecker2IntFinalize, sceNpServiceChecker2IntGetServiceAvailability, sceNpServiceChecker2IntGetServiceAvailabilityA, sceNpServiceChecker2IntInitialize and sceNpServiceChecker2IntIsSetServiceType](../../../core/libs/prx/libSceNpUtility/Export.cpp) - signatures and errors from the 11.00 wrappers: Initialize/Finalize hold one host state, CreateRequest writes request ids from 1, Destroy/Abort remove them, and the two availability calls return SIGNED_OUT without writing because no network is available; `IsSetServiceType` reads bit 0..9 of a 32-bit mask. The console's availability payload, its exact offline error and any request limit are unmeasured. + - [sceNpBandwidthTestInitStart, sceNpBandwidthTestShutdownWithDetailedInfo](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - offline answers, not measured on a PS5: InitStart returns `SCE_NP_ERROR_SIGNED_OUT` as `sceNpBandwidthTestInitStartDownload` and `InitStartUpload` do, so no test is ever started, and ShutdownWithDetailedInfo returns `SCE_NP_ERROR_INVALID_ARGUMENT` and writes nothing, as `sceNpBandwidthTestShutdown` does. Neither reads an argument, so their parameterless declarations stand; their prototypes are still unknown - [NpLookup and NpWordFilter](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - offline only, not measured on a PS5: the argument checks and their order, the limit of 32 title contexts and 32 requests, the word filter id bases `0x01000001` and `0x02000001`, the 0x400-byte comment limit and single-use requests (`INVALID_TYPE`) follow shadPS4's [lookup](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_utility/np_utility.cpp#L41-L222) and [word filter](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_utility/np_utility.cpp#L705-L909) calls. `sceNpLookupNpId`, `sceNpWordFilterCensorComment` and `sceNpWordFilterSanitizeComment` then finish with `SCE_NP_ERROR_SIGNED_OUT` and write no NpId or comment; shadPS4's word filter accepts every comment as a stand-in for the server, so its result is not used - [NpLookup and NpWordFilter waits and timeouts](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - IDs here carry no service: on the console, measured on firmware 13.42's module in an emulator (#10640), each ID holds its service in its high bits (Lookup requests `0x2xxxxxxx` and title contexts `0x4xxxxxxx`, word filter `0x02xxxxxx` and `0x04xxxxxx`), and `SetTimeout`, `AbortRequest`, `PollAsync` and `WaitAsync` find the object by those bits whichever service's call is made; here each call finds only its own service's IDs, which keep the ranges above. The timeouts are checked as measured and then have no effect, since no request reaches a server; the console stores them. A wait on a request deleted meanwhile returns `INVALID_ID`, unmeasured. The measured checks are followed: a null result first (`0x80550704`), a synchronous request (`0x80550307` for Lookup, `INVALID_ID` for the word filter), the timeout bounds and the 0x18-byte async parameter From 50327e7c8c58014c0f549739963bd523d656e79a Mon Sep 17 00:00:00 2001 From: BunnyFl <166477956+BunnyFl@users.noreply.github.com> Date: Mon, 2 Nov 2026 03:39:59 +0100 Subject: [PATCH 229/612] feat(libSceVideoOut): answer seven inventory calls as firmware 13.42 does sceVideoOutAddBufferHdrPrivilege, sceVideoOutRazorRecallCallbacks and sceVideoOutSysUpdateRenderingMode return 0, sceVideoOutCurveSamplePointsCalc_ returns 0x8029001C, sceVideoOutDriverGetLatestEopHandle 0x802900FF and sceVideoOutSysGetMonitorInfo_ -1, and sceVideoOutSysModeIsVr returns bit 1 of the dword at offset 0x20 of its argument, as firmware 13.42's libSceVideoOut does (read to the return, disassembly only, not measured on a console). Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceVideoOut/CMakeLists.txt | 11 ++++++ .../prx/libSceVideoOut/InventoryExports.cpp | 25 +++++++++----- .../include/InventoryExports.hpp | 2 +- .../tests/GuestVideoOutFirmwareAnswers.cpp | 34 +++++++++++++++++++ docs/dev/debt/libSceVideoOut.md | 11 ++---- 5 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 core/libs/tests/GuestVideoOutFirmwareAnswers.cpp diff --git a/core/libs/prx/libSceVideoOut/CMakeLists.txt b/core/libs/prx/libSceVideoOut/CMakeLists.txt index 0f2e75f24b..e485095689 100644 --- a/core/libs/prx/libSceVideoOut/CMakeLists.txt +++ b/core/libs/prx/libSceVideoOut/CMakeLists.txt @@ -81,6 +81,17 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_video_out_firmware_answers_tests ${LIBS_INCLUDE_DIR}/tests/GuestVideoOutFirmwareAnswers.cpp) + target_include_directories(guest_video_out_firmware_answers_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_video_out_firmware_answers_tests PRIVATE libSceVideoOut libkernel libc) + configure_windows_unwind(guest_video_out_firmware_answers_tests) + add_test(NAME guest_video_out_firmware_answers COMMAND guest_video_out_firmware_answers_tests) + set_tests_properties(guest_video_out_firmware_answers PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_video_out_firmware_answers PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_video_out_high_frame_rate_tests ${LIBS_INCLUDE_DIR}/tests/GuestVideoOutHighFrameRate.cpp) target_include_directories(guest_video_out_high_frame_rate_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_video_out_high_frame_rate_tests PRIVATE libSceVideoOut libSceAgcDriver libkernel libc) diff --git a/core/libs/prx/libSceVideoOut/InventoryExports.cpp b/core/libs/prx/libSceVideoOut/InventoryExports.cpp index e298727bb5..b52f34735b 100644 --- a/core/libs/prx/libSceVideoOut/InventoryExports.cpp +++ b/core/libs/prx/libSceVideoOut/InventoryExports.cpp @@ -2,6 +2,13 @@ #include "prx/libc/include/general/ExportMacros.hpp" #include "prx/libSceVideoOut/include/InventoryExports.hpp" #include "prx/libc/include/General.hpp" +#include +#include + +namespace { +constexpr int VideoOutErrorCurveSamplePoints = static_cast(0x8029001Cu); +constexpr int VideoOutErrorLatestEopHandle = static_cast(0x802900FFu); +} extern "C" { @@ -18,7 +25,7 @@ int APS5_VABI sceVideoOutAddBufferAnisoPrivilege() { } int APS5_VABI sceVideoOutAddBufferHdrPrivilege() { - NotImplemented_nid_no_patch(__func__); + return 0; } int APS5_VABI sceVideoOutAddBufferYccPrivilege() { @@ -90,7 +97,7 @@ int APS5_VABI sceVideoOutCursorSetPositionStereo() { } int APS5_VABI sceVideoOutCurveSamplePointsCalc_() { - NotImplemented_nid_no_patch(__func__); + return VideoOutErrorCurveSamplePoints; } int APS5_VABI sceVideoOutDebugAddVrrPrivilege() { @@ -154,7 +161,7 @@ int APS5_VABI sceVideoOutDriverGetFinishedEopHandle() { } int APS5_VABI sceVideoOutDriverGetLatestEopHandle() { - NotImplemented_nid_no_patch(__func__); + return VideoOutErrorLatestEopHandle; } int APS5_VABI sceVideoOutDriverIncrementBufferLabel() { @@ -254,7 +261,7 @@ int APS5_VABI sceVideoOutRazorAddCallback() { } int APS5_VABI sceVideoOutRazorRecallCallbacks() { - NotImplemented_nid_no_patch(__func__); + return 0; } int APS5_VABI sceVideoOutRazorRemoveCallback() { @@ -382,7 +389,7 @@ int APS5_VABI sceVideoOutSysGetKsvList() { } int APS5_VABI sceVideoOutSysGetMonitorInfo_() { - NotImplemented_nid_no_patch(__func__); + return -1; } int APS5_VABI sceVideoOutSysGetOutputStatus() { @@ -461,8 +468,10 @@ int APS5_VABI sceVideoOutSysModeIsVrNative() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceVideoOutSysModeIsVr() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVideoOutSysModeIsVr(const void* mode) { + std::uint32_t flags; + std::memcpy(&flags, static_cast(mode) + 0x20, sizeof(flags)); + return static_cast((flags >> 1) & 1u); } int APS5_VABI sceVideoOutSysOpenInternal() { @@ -522,7 +531,7 @@ int APS5_VABI sceVideoOutSysUpdatePrivilege() { } int APS5_VABI sceVideoOutSysUpdateRenderingMode() { - NotImplemented_nid_no_patch(__func__); + return 0; } int APS5_VABI sceVideoOutUnlockBuffer() { diff --git a/core/libs/prx/libSceVideoOut/include/InventoryExports.hpp b/core/libs/prx/libSceVideoOut/include/InventoryExports.hpp index 5c65f81866..6b9f6feedc 100644 --- a/core/libs/prx/libSceVideoOut/include/InventoryExports.hpp +++ b/core/libs/prx/libSceVideoOut/include/InventoryExports.hpp @@ -118,7 +118,7 @@ int APS5_VABI sceVideoOutSysModeIsHdr(); int APS5_VABI sceVideoOutSysModeIsSystemDefault(); int APS5_VABI sceVideoOutSysModeIsVrLegacy(); int APS5_VABI sceVideoOutSysModeIsVrNative(); -int APS5_VABI sceVideoOutSysModeIsVr(); +int APS5_VABI sceVideoOutSysModeIsVr(const void* mode); int APS5_VABI sceVideoOutSysOpenInternal(); int APS5_VABI sceVideoOutSysOpenVrNative(); int APS5_VABI sceVideoOutSysReadCrc64(); diff --git a/core/libs/tests/GuestVideoOutFirmwareAnswers.cpp b/core/libs/tests/GuestVideoOutFirmwareAnswers.cpp new file mode 100644 index 0000000000..c463fc5d09 --- /dev/null +++ b/core/libs/tests/GuestVideoOutFirmwareAnswers.cpp @@ -0,0 +1,34 @@ +#include "prx/libSceVideoOut/include/InventoryExports.hpp" +#include +#include +#include +#include +#include + +static void Require(bool value, const char* message) { + if (!value) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +static int ModeIsVr(std::uint32_t flags, std::uint8_t fill) { + std::array mode; + mode.fill(fill); + std::memcpy(mode.data() + 0x20, &flags, sizeof(flags)); + return sceVideoOutSysModeIsVr(mode.data()); +} + +int main() { + Require(sceVideoOutAddBufferHdrPrivilege() == 0, "sceVideoOutAddBufferHdrPrivilege did not return 0"); + Require(sceVideoOutRazorRecallCallbacks() == 0, "sceVideoOutRazorRecallCallbacks did not return 0"); + Require(sceVideoOutSysUpdateRenderingMode() == 0, "sceVideoOutSysUpdateRenderingMode did not return 0"); + Require(sceVideoOutCurveSamplePointsCalc_() == static_cast(0x8029001Cu), "sceVideoOutCurveSamplePointsCalc_ did not return 0x8029001C"); + Require(sceVideoOutDriverGetLatestEopHandle() == static_cast(0x802900FFu), "sceVideoOutDriverGetLatestEopHandle did not return 0x802900FF"); + Require(sceVideoOutSysGetMonitorInfo_() == -1, "sceVideoOutSysGetMonitorInfo_ did not return -1"); + Require(ModeIsVr(0x2u, 0) == 1, "bit 1 of the dword at 0x20 did not report VR"); + Require(ModeIsVr(0xFFFFFFFFu, 0xFF) == 1, "a mode with every bit set did not report VR"); + Require(ModeIsVr(0xFFFFFFFDu, 0xFF) == 0, "the other bits of the dword at 0x20 reported VR"); + Require(ModeIsVr(0u, 0xFF) == 0, "bytes outside the dword at 0x20 reported VR"); + return 0; +} diff --git a/docs/dev/debt/libSceVideoOut.md b/docs/dev/debt/libSceVideoOut.md index 1439e1d254..63187a8771 100644 --- a/docs/dev/debt/libSceVideoOut.md +++ b/docs/dev/debt/libSceVideoOut.md @@ -56,14 +56,13 @@ - The [revision-pinned identity inventory](https://gist.github.com/kyon-eth/ea0a096e6231dbe7b98b5a308e2ac91e/00f8422a1ae71e32816d72ae579c2f2d097f63a0) records module, logical library, symbol type and NIDs for [InventoryExports.cpp](../../../core/libs/prx/libSceVideoOut/InventoryExports.cpp); it is a metadata summary rather than independent verification. Some NIDs belong to multiple logical libraries within the module. -- [Additional callable exports](../../../core/libs/prx/libSceVideoOut/InventoryExports.cpp) - 182 entries observed in a 3.20-labelled image and a later 13.60-labelled image; 107 match the public 3.20 catalogue and 75 do not. These are import-resolution declarations only. Neither image label authenticates a retail firmware version. Parameter lists, return types and behavior are unverified; parameterless `int` declarations are placeholders, not verified ABI contracts. Every entry throws through `NotImplemented_nid_no_patch`. Unidentified names use literal NID aliases. No runtime functionality is implemented; data exports need separate analysis. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). +- [Additional callable exports](../../../core/libs/prx/libSceVideoOut/InventoryExports.cpp) - 175 entries observed in a 3.20-labelled image and a later 13.60-labelled image; of the original 182, 107 match the public 3.20 catalogue and 75 do not. These are import-resolution declarations only. Neither image label authenticates a retail firmware version. Parameter lists, return types and behavior are unverified; parameterless `int` declarations are placeholders, not verified ABI contracts. Every entry throws through `NotImplemented_nid_no_patch`. Unidentified names use literal NID aliases. No runtime functionality is implemented; data exports need separate analysis. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). | Name | NID | | --- | --- | | `sceVideoOutAddBuffer` | `4iy9hG9Du1s` | | `sceVideoOutAddBuffer4k2kPrivilege` | `twrSaoZ9cJs` | | `sceVideoOutAddBufferAnisoPrivilege` | `lm+1oyWKwy0` | -| `sceVideoOutAddBufferHdrPrivilege` | `VAlir52gpQI` | | `sceVideoOutAddBufferYccPrivilege` | `D3s8DoEnuR4` | | `sceVideoOutAddDisplayPositionEvent` | `Cxb2w82t7lw` | | `sceVideoOutAddDriver` | `HtwSd4H2Tws` | @@ -81,7 +80,6 @@ | `sceVideoOutControlHdcpEncryption` | `M-JkRj7X+No` | | `sceVideoOutCursorIsUpdatePending` | `HibOtezzT6M` | | `sceVideoOutCursorSetPositionStereo` | `735GfVO9v2s` | -| `sceVideoOutCurveSamplePointsCalc_` | `kazBRXPqR7c` | | `sceVideoOutDebugAddVrrPrivilege` | `rAYR4zzw414` | | `sceVideoOutDebugBlankPipe` | `-9YA0lVAq+U` | | `sceVideoOutDebugReadCrc` | `4E8qvHf7Cj0` | @@ -97,7 +95,6 @@ | `sceVideoOutDriverGetBufferLabelAddress` | `NGyEl5aT4fc` | | `sceVideoOutDriverGetEopHandle` | `P1bIHoga4jE` | | `sceVideoOutDriverGetFinishedEopHandle` | `G6peSSDP4iA` | -| `sceVideoOutDriverGetLatestEopHandle` | `GL8+3RHW5vk` | | `sceVideoOutDriverIncrementBufferLabel` | `Ztx4b2LhRDk` | | `sceVideoOutDriverResetBufferLabel` | `soAIjspgdt8` | | `sceVideoOutDriverTriggerEvent` | `OHnnkrUcShM` | @@ -122,7 +119,6 @@ | `sceVideoOutHdmiMonitorInfoIsSupportedVideoOutMode_` | `WBZrUaQY+EU` | | `sceVideoOutLockBuffer` | `7ifZFxWJ9Q4` | | `sceVideoOutRazorAddCallback` | `ZX7C79uOujU` | -| `sceVideoOutRazorRecallCallbacks` | `hGcTks-qqIU` | | `sceVideoOutRazorRemoveCallback` | `zUDW7kIFclI` | | `sceVideoOutRegisterBufferAttribute` | `EJQifjOp8s4` | | `sceVideoOutRegisterStereoBuffers` | `lCTCOogRbk0` | @@ -154,7 +150,6 @@ | `sceVideoOutSysGetDeviceInfo` | `2sgenMqwczM` | | `sceVideoOutSysGetFlipStatus` | `21sS8EU45cw` | | `sceVideoOutSysGetKsvList` | `Ur8iOIm0tyE` | -| `sceVideoOutSysGetMonitorInfo_` | `vMLWuJckGv0` | | `sceVideoOutSysGetOutputStatus` | `Ip78YCQUrTo` | | `sceVideoOutSysGetPipelineStatus` | `dFhciCfO31s` | | `sceVideoOutSysGetResolutionStatus2` | `qLDCAl8ygCw` | @@ -174,7 +169,6 @@ | `sceVideoOutSysModeIsSystemDefault` | `5Y8uUbGMio0` | | `sceVideoOutSysModeIsVrLegacy` | `zecl8Gox+P0` | | `sceVideoOutSysModeIsVrNative` | `SgA2MtojPto` | -| `sceVideoOutSysModeIsVr` | `QEiuHxjungc` | | `sceVideoOutSysOpenInternal` | `kP4l+nyBjRA` | | `sceVideoOutSysOpenVrNative` | `WUU+ufqgLJg` | | `sceVideoOutSysReadCrc64` | `eBvoaLwfBBQ` | @@ -189,7 +183,6 @@ | `sceVideoOutSysSetOverscanRatio` | `nMbAqI1ty8w` | | `sceVideoOutSysUpdateDisplayParameter` | `UFDoqv4XXC0` | | `sceVideoOutSysUpdatePrivilege` | `Jl91AhxKc0w` | -| `sceVideoOutSysUpdateRenderingMode` | `4G37EIMMD+Q` | | `sceVideoOutUnlockBuffer` | `amghnA-Ev5s` | | `sceVideoOutUnregisterBufferAttribute` | `SRQSpP4QK5U` | | `Unknown` | `-5AIEU9+11g` | @@ -243,6 +236,8 @@ | `Unknown` | `yoK0AjkkBkI` | | `Unknown` | `ys239q8VgXY` | +- [Firmware 13.42 answers](../../../core/libs/prx/libSceVideoOut/InventoryExports.cpp) (libSceVideoOut) - `sceVideoOutAddBufferHdrPrivilege`, `sceVideoOutRazorRecallCallbacks` and `sceVideoOutSysUpdateRenderingMode` return 0, `sceVideoOutCurveSamplePointsCalc_` returns 0x8029001C, `sceVideoOutDriverGetLatestEopHandle` 0x802900FF and `sceVideoOutSysGetMonitorInfo_` -1, and `sceVideoOutSysModeIsVr` returns bit 1 of the dword at offset 0x20 of its argument, as firmware 13.42's libSceVideoOut does: read to the return, disassembly only, not measured on a console. Their parameter lists are unverified beyond what the code reads; the layout of the structure `sceVideoOutSysModeIsVr` reads is unknown apart from that dword + - [sceVideoOutSetConfigurationMode](../../../core/libs/prx/libSceVideoOut/InventoryExports.cpp): the name hashes to NID `N1+j0RymMUQ`, a function export in both older and later version-labelled images. Its signature and behavior are unverified; this import-resolution declaration throws. - [Older callable NIDs](../../../core/libs/prx/libSceVideoOut/InventoryExports.cpp): 7 unidentified exports have unverified names, signatures and behavior. These are throwing import-resolution placeholders; no functionality is implemented. No matching NIDs were found in the later module inventory. Six use the primary library qualifier and one uses the nosubmission qualifier; qualified guest import resolution is unverified. From a4e7f8f3f629174f94df2222434362940e365278 Mon Sep 17 00:00:00 2001 From: BunnyFl <166477956+BunnyFl@users.noreply.github.com> Date: Mon, 2 Nov 2026 03:50:34 +0100 Subject: [PATCH 230/612] feat(libSceXml): answer the NodeId null checks as firmware 13.42 does sce::Xml::Dom::NodeId::operator!, operator==(int) and operator!=(int) now treat a NodeId as null when both pointers and the flag byte are zero, padding ignored: operator! reports that, operator==(int) is true only for a null NodeId compared with 0, and operator!=(int) is its negation, as firmware 13.42's libSceXml.sprx does (read to the return, disassembly only, not measured on a console). Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceXml/Export.cpp | 16 ++++++++++------ core/libs/prx/libSceXml/include/Api.hpp | 6 +++--- core/libs/tests/GuestXmlValueTypes.cpp | 13 +++++++++++++ docs/dev/debt/libSceXml.md | 2 ++ 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/core/libs/prx/libSceXml/Export.cpp b/core/libs/prx/libSceXml/Export.cpp index 9515d73682..060e53b4b1 100644 --- a/core/libs/prx/libSceXml/Export.cpp +++ b/core/libs/prx/libSceXml/Export.cpp @@ -13,6 +13,10 @@ void AssignNodeId(XmlNodeId* self, const XmlNodeId& value) { self->flag = value.flag; } +bool NodeIdIsNull(const XmlNodeId* self) { + return self->first == nullptr && self->second == nullptr && self->flag == 0; +} + void CopyNode(XmlNode* self, const XmlNode* other) { AssignNodeId(&self->id, other->id); self->document = other->document; @@ -258,20 +262,20 @@ bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdeqES2_(const XmlNodeId* self, XmlNodeId oth return other.first == self->first && other.second == self->second && other.flag == self->flag; } -int APS5_VABI _ZN3sce3Xml3Dom6NodeIdeqEi() { - NotImplemented_nid_no_patch(__func__); +bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdeqEi(const XmlNodeId* self, int value) { + return value == 0 && NodeIdIsNull(self); } bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdneES2_(const XmlNodeId* self, XmlNodeId other) { return other.first != self->first || other.second != self->second || other.flag != self->flag; } -int APS5_VABI _ZN3sce3Xml3Dom6NodeIdneEi() { - NotImplemented_nid_no_patch(__func__); +bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdneEi(const XmlNodeId* self, int value) { + return value != 0 || !NodeIdIsNull(self); } -int APS5_VABI _ZN3sce3Xml3Dom6NodeIdntEv() { - NotImplemented_nid_no_patch(__func__); +bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdntEv(const XmlNodeId* self) { + return NodeIdIsNull(self); } int APS5_VABI _ZN3sce3Xml3Dom8Document10importNodeENS1_6NodeIdES3_PKS2_S3_() { diff --git a/core/libs/prx/libSceXml/include/Api.hpp b/core/libs/prx/libSceXml/include/Api.hpp index 777a60e2ac..945a1f8adf 100644 --- a/core/libs/prx/libSceXml/include/Api.hpp +++ b/core/libs/prx/libSceXml/include/Api.hpp @@ -96,10 +96,10 @@ void APS5_VABI _ZN3sce3Xml3Dom6NodeIdC2Ev(XmlNodeId* self); XmlNodeId APS5_VABI _ZN3sce3Xml3Dom6NodeIdaSES2_(XmlNodeId* self, XmlNodeId value); void* APS5_VABI _ZN3sce3Xml3Dom6NodeIdcvPvEv(XmlNodeId*); bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdeqES2_(const XmlNodeId* self, XmlNodeId other); -int APS5_VABI _ZN3sce3Xml3Dom6NodeIdeqEi(); +bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdeqEi(const XmlNodeId* self, int value); bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdneES2_(const XmlNodeId* self, XmlNodeId other); -int APS5_VABI _ZN3sce3Xml3Dom6NodeIdneEi(); -int APS5_VABI _ZN3sce3Xml3Dom6NodeIdntEv(); +bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdneEi(const XmlNodeId* self, int value); +bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdntEv(const XmlNodeId* self); int APS5_VABI _ZN3sce3Xml3Dom8Document10importNodeENS1_6NodeIdES3_PKS2_S3_(); int APS5_VABI _ZN3sce3Xml3Dom8Document10initializeEPKNS0_11InitializerE(); int APS5_VABI _ZN3sce3Xml3Dom8Document10insertNodeENS1_6NodeIdES3_S3_(); diff --git a/core/libs/tests/GuestXmlValueTypes.cpp b/core/libs/tests/GuestXmlValueTypes.cpp index 3a9bb35b5f..bddfb6d2aa 100644 --- a/core/libs/tests/GuestXmlValueTypes.cpp +++ b/core/libs/tests/GuestXmlValueTypes.cpp @@ -179,6 +179,19 @@ void TestNodeId() { for (const XmlNodeId* other : {&otherFlag, &otherFirst, &otherSecond}) { Require(!_ZN3sce3Xml3Dom6NodeIdeqES2_(&base, *other) && _ZN3sce3Xml3Dom6NodeIdneES2_(&base, *other), "NodeId comparison uses every field"); } + for (const std::uint8_t padding : {static_cast(0x00), static_cast(0xff)}) { + const XmlNodeId null = NodeIdValue(0, 0, 0, padding); + Require(_ZN3sce3Xml3Dom6NodeIdntEv(&null), "operator! is true for a null NodeId whatever its padding"); + Require(_ZN3sce3Xml3Dom6NodeIdeqEi(&null, 0) && !_ZN3sce3Xml3Dom6NodeIdneEi(&null, 0), "a null NodeId compares equal to 0"); + for (const int value : {1, -1, 0x7fffffff}) { + Require(!_ZN3sce3Xml3Dom6NodeIdeqEi(&null, value) && _ZN3sce3Xml3Dom6NodeIdneEi(&null, value), "a NodeId never compares equal to a nonzero int"); + } + } + for (const XmlNodeId value : {NodeIdValue(1, 0, 0, 0), NodeIdValue(0, 1, 0, 0), NodeIdValue(0, 0, 1, 0), NodeIdValue(0, 0, 0x80, 0xff)}) { + Require(!_ZN3sce3Xml3Dom6NodeIdntEv(&value), "operator! is false once any field is set"); + Require(!_ZN3sce3Xml3Dom6NodeIdeqEi(&value, 0) && _ZN3sce3Xml3Dom6NodeIdneEi(&value, 0), "a NodeId with any field set compares unequal to 0"); + Require(!_ZN3sce3Xml3Dom6NodeIdeqEi(&value, 1) && _ZN3sce3Xml3Dom6NodeIdneEi(&value, 1), "a NodeId with any field set compares unequal to a nonzero int"); + } XmlNodeId any = NodeIdValue(0x1234, 0x5678, 1, 0); Require(_ZN3sce3Xml3Dom6NodeIdcvPvEv(&any) == nullptr, "NodeId::operator void* is null"); } diff --git a/docs/dev/debt/libSceXml.md b/docs/dev/debt/libSceXml.md index 34ee59a045..86d2c03dbe 100644 --- a/docs/dev/debt/libSceXml.md +++ b/docs/dev/debt/libSceXml.md @@ -6,4 +6,6 @@ ### Value types and handle constructors +- [`NodeId::operator!`, `operator==(int)` and `operator!=(int)`](../../../core/libs/prx/libSceXml/Export.cpp) (libSceXml) - a `NodeId` is null when both pointers and the flag byte are zero, padding ignored. `operator!` reports that, `operator==(int)` is true only for a null `NodeId` and 0, and `operator!=(int)` is its negation, as firmware 13.42's `libSceXml.sprx` does: read to the return, disassembly only, not measured on a console. Whether titles compare with values other than 0 is not known. + - [The `String`, `SimpleData`, `SerializeParameter`, `NodeId` and `Node` value operations and the handle constructors](../../../core/libs/prx/libSceXml/Export.cpp) (libSceXml) - follow `libSceXml.sprx` of firmware 13.60, read from disassembly, not measured on a console. `NodeId::operator void*` returns null for every `NodeId`, and the `NodeId` constructor and copies leave its padding bytes untouched. The handle constructors only clear the implementation pointer. Exports that read the objects `initialize` builds still throw, because nothing creates those objects yet: `AttributeList` `getLength` and `isAvailable`, `Document::resetStatus`, the `DocumentBuilder` and `Sax::Parser` setters, `Node(NodeId)`, and `Node` `getNodeType`, `hasChildNodes`, `hasAttributes` and `getOwnerDocument`. `Node::getAttributes` and `getChildNodes` are a bare return in the module, and the deleting `MemAllocator` destructor is `ud2`, so they still throw too. From 072371afe905abbb8a1aa0296d43c7e67cfffe22 Mon Sep 17 00:00:00 2001 From: BunnyFl <166477956+BunnyFl@users.noreply.github.com> Date: Mon, 2 Nov 2026 03:56:22 +0100 Subject: [PATCH 231/612] docs(libSceVideoOut): record that sceVideoOutSysModeIsVr does not check its pointer Co-Authored-By: Claude Opus 5.5 --- docs/dev/debt/libSceVideoOut.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceVideoOut.md b/docs/dev/debt/libSceVideoOut.md index 63187a8771..84cc0c64ec 100644 --- a/docs/dev/debt/libSceVideoOut.md +++ b/docs/dev/debt/libSceVideoOut.md @@ -236,7 +236,7 @@ | `Unknown` | `yoK0AjkkBkI` | | `Unknown` | `ys239q8VgXY` | -- [Firmware 13.42 answers](../../../core/libs/prx/libSceVideoOut/InventoryExports.cpp) (libSceVideoOut) - `sceVideoOutAddBufferHdrPrivilege`, `sceVideoOutRazorRecallCallbacks` and `sceVideoOutSysUpdateRenderingMode` return 0, `sceVideoOutCurveSamplePointsCalc_` returns 0x8029001C, `sceVideoOutDriverGetLatestEopHandle` 0x802900FF and `sceVideoOutSysGetMonitorInfo_` -1, and `sceVideoOutSysModeIsVr` returns bit 1 of the dword at offset 0x20 of its argument, as firmware 13.42's libSceVideoOut does: read to the return, disassembly only, not measured on a console. Their parameter lists are unverified beyond what the code reads; the layout of the structure `sceVideoOutSysModeIsVr` reads is unknown apart from that dword +- [Firmware 13.42 answers](../../../core/libs/prx/libSceVideoOut/InventoryExports.cpp) (libSceVideoOut) - `sceVideoOutAddBufferHdrPrivilege`, `sceVideoOutRazorRecallCallbacks` and `sceVideoOutSysUpdateRenderingMode` return 0, `sceVideoOutCurveSamplePointsCalc_` returns 0x8029001C, `sceVideoOutDriverGetLatestEopHandle` 0x802900FF and `sceVideoOutSysGetMonitorInfo_` -1, and `sceVideoOutSysModeIsVr` returns bit 1 of the dword at offset 0x20 of its argument, as firmware 13.42's libSceVideoOut does: read to the return, disassembly only, not measured on a console. Their parameter lists are unverified beyond what the code reads; the layout of the structure `sceVideoOutSysModeIsVr` reads is unknown apart from that dword. The module reads that dword without checking the pointer, so a null argument faults on the console as it does here - [sceVideoOutSetConfigurationMode](../../../core/libs/prx/libSceVideoOut/InventoryExports.cpp): the name hashes to NID `N1+j0RymMUQ`, a function export in both older and later version-labelled images. Its signature and behavior are unverified; this import-resolution declaration throws. From bbfc44a57c0cbc2f710643ce6e11a90f79477e49 Mon Sep 17 00:00:00 2001 From: BunnyFl <166477956+BunnyFl@users.noreply.github.com> Date: Mon, 2 Nov 2026 04:02:49 +0100 Subject: [PATCH 232/612] feat(libSceLibcInternal): answer _Daysto, _FXp_getw and _Isdst as firmware 13.42 does _Isdst returns -1. _Daysto(year, month) adds the leap days before the year to the common or leap days-before-month table, as Dinkumware's _Daysto does, and throws for a month outside the twelve table entries the module would index past. _FXp_getw sums up to four float parts as Dinkumware's _Xp_getw does. Read to the return from firmware 13.42's libSceLibcInternal, disassembly only, not measured on a console. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceLibcInternal/CMakeLists.txt | 13 ++++ core/libs/prx/libSceLibcInternal/Export.cpp | 27 +++++--- .../include/MathInternals.hpp | 2 +- .../include/RuntimeInternals.hpp | 2 +- .../GuestLibcInternalFirmwareAnswers.cpp | 65 +++++++++++++++++++ docs/dev/debt/libSceLibcInternal.md | 7 +- 6 files changed, 103 insertions(+), 13 deletions(-) create mode 100644 core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp diff --git a/core/libs/prx/libSceLibcInternal/CMakeLists.txt b/core/libs/prx/libSceLibcInternal/CMakeLists.txt index 2655d73c0b..ef2bf4ca52 100644 --- a/core/libs/prx/libSceLibcInternal/CMakeLists.txt +++ b/core/libs/prx/libSceLibcInternal/CMakeLists.txt @@ -222,3 +222,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_libc_internal_firmware_answers_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcInternalFirmwareAnswers.cpp) + target_include_directories(guest_libc_internal_firmware_answers_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libc_internal_firmware_answers_tests PRIVATE libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_libc_internal_firmware_answers_tests) + add_test(NAME guest_libc_internal_firmware_answers COMMAND guest_libc_internal_firmware_answers_tests) + set_tests_properties(guest_libc_internal_firmware_answers PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_libc_internal_firmware_answers PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibcInternal/Export.cpp b/core/libs/prx/libSceLibcInternal/Export.cpp index 628e648c33..51ae83331a 100644 --- a/core/libs/prx/libSceLibcInternal/Export.cpp +++ b/core/libs/prx/libSceLibcInternal/Export.cpp @@ -1129,9 +1129,15 @@ int APS5_VABI _Closreg_nid_postfix() { return 0; } -int APS5_VABI _Daysto_nid_postfix() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _Daysto_nid_postfix(int year, int month) { + static constexpr short DaysBeforeMonth[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + static constexpr short LeapDaysBeforeMonth[12] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}; + if (month < 0 || month >= 12) throw std::out_of_range("_Daysto: the month index is outside the twelve-entry tables"); + int leapDays = 0; + if (year > 0) leapDays = (year - 1) / 4; + else if (year <= -4) leapDays = 1 + (4 - year) / 4; + const bool common = (year & 3) != 0 || year == 0; + return leapDays + (common ? DaysBeforeMonth[month] : LeapDaysBeforeMonth[month]); } int APS5_VABI _Deletegloballocale_nid_postfix() { @@ -1241,9 +1247,15 @@ int APS5_VABI _FXp_addx_nid_postfix() { return 0; } -int APS5_VABI _FXp_getw_nid_postfix() { - NotImplemented_nid_no_patch(__func__); - return 0; +float APS5_VABI _FXp_getw_nid_postfix(const float* parts, int count) { + if (count == 0) return 0.0f; + if (count == 1 || parts[0] == 0.0f || parts[1] == 0.0f) return parts[0]; + if (count == 2 || parts[2] == 0.0f) return parts[0] + parts[1]; + const float head = parts[0] + parts[1]; + float tail = parts[2]; + if (count >= 4) tail += parts[3]; + if (head - parts[0] == parts[1]) return head + tail; + return parts[0] + (parts[1] + tail); } int APS5_VABI _FXp_invx_nid_postfix() { @@ -1433,8 +1445,7 @@ int APS5_VABI sceLibcInternalUnknown_wUa_oPQvFZ0_nid_no_patch() { APS5_EXPORT("wUa+oPQvFZ0", sceLibcInternalUnknown_wUa_oPQvFZ0_nid_no_patch); int APS5_VABI _Isdst_nid_postfix() { - NotImplemented_nid_no_patch(__func__); - return 0; + return -1; } int APS5_VABI _LAtan_nid_postfix() { diff --git a/core/libs/prx/libSceLibcInternal/include/MathInternals.hpp b/core/libs/prx/libSceLibcInternal/include/MathInternals.hpp index d706b9c36f..508e2c90dd 100644 --- a/core/libs/prx/libSceLibcInternal/include/MathInternals.hpp +++ b/core/libs/prx/libSceLibcInternal/include/MathInternals.hpp @@ -16,7 +16,7 @@ int APS5_VABI _FTan_nid_postfix(); int APS5_VABI _FTgamma_nid_postfix(); int APS5_VABI _FXp_addh_nid_postfix(); int APS5_VABI _FXp_addx_nid_postfix(); -int APS5_VABI _FXp_getw_nid_postfix(); +float APS5_VABI _FXp_getw_nid_postfix(const float* parts, int count); int APS5_VABI _FXp_invx_nid_postfix(); int APS5_VABI _FXp_ldexpx_nid_postfix(); int APS5_VABI _FXp_movx_nid_postfix(); diff --git a/core/libs/prx/libSceLibcInternal/include/RuntimeInternals.hpp b/core/libs/prx/libSceLibcInternal/include/RuntimeInternals.hpp index 4d3634a435..b3a225156c 100644 --- a/core/libs/prx/libSceLibcInternal/include/RuntimeInternals.hpp +++ b/core/libs/prx/libSceLibcInternal/include/RuntimeInternals.hpp @@ -11,7 +11,7 @@ int APS5_VABI _CStrxfrm_nid_postfix(); int APS5_VABI _CWcsxfrm_nid_postfix(); int APS5_VABI _Call_onceEx_nid_postfix(); int APS5_VABI _Closreg_nid_postfix(); -int APS5_VABI _Daysto_nid_postfix(); +int APS5_VABI _Daysto_nid_postfix(int year, int month); int APS5_VABI _Deletegloballocale_nid_postfix(); int APS5_VABI _Do_call_nid_postfix(); int APS5_VABI _Fac_tidy_nid_postfix(); diff --git a/core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp b/core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp new file mode 100644 index 0000000000..43dd6300d9 --- /dev/null +++ b/core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp @@ -0,0 +1,65 @@ +#include "prx/libSceLibcInternal/include/MathInternals.hpp" +#include "prx/libSceLibcInternal/include/RuntimeInternals.hpp" +#include +#include +#include +#include + +static void Require(bool value, const char* message) { + if (!value) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +static bool Throws(int year, int month) { + try { + _Daysto_nid_postfix(year, month); + } catch (const std::out_of_range&) { + return true; + } + return false; +} + +static void TestDaysto() { + Require(_Daysto_nid_postfix(1, 0) == 0, "_Daysto(1, 0)"); + Require(_Daysto_nid_postfix(1, 1) == 31, "_Daysto(1, 1)"); + Require(_Daysto_nid_postfix(4, 2) == 60, "a positive multiple of four uses the leap table"); + Require(_Daysto_nid_postfix(5, 2) == 60, "year 5 adds one leap day to the common table"); + Require(_Daysto_nid_postfix(0, 2) == 59, "year 0 uses the common table with no leap days"); + Require(_Daysto_nid_postfix(-1, 2) == 59, "years -1 to -3 add no leap days"); + Require(_Daysto_nid_postfix(-3, 11) == 334, "_Daysto(-3, 11)"); + Require(_Daysto_nid_postfix(-4, 2) == 63, "year -4 adds 1 + 8 / 4 days to the leap table"); + Require(_Daysto_nid_postfix(-5, 0) == 3, "year -5 adds 1 + 9 / 4 days to the common table"); + Require(_Daysto_nid_postfix(100, 11) == 359, "every fourth year uses the leap table, centuries included"); + Require(_Daysto_nid_postfix(2023, 11) == 334 + 505, "_Daysto(2023, 11)"); + Require(_Daysto_nid_postfix(2024, 11) == 335 + 505, "_Daysto(2024, 11)"); + Require(Throws(2024, 12) && Throws(2024, -1), "a month outside the tables throws"); +} + +static void TestXpGetw() { + const float one[] = {5.0f, 7.0f, 11.0f, 13.0f}; + Require(_FXp_getw_nid_postfix(one, 0) == 0.0f, "no parts sum to 0"); + Require(_FXp_getw_nid_postfix(one, 1) == 5.0f, "one part is returned as it is"); + const float zeroHead[] = {0.0f, 7.0f, 11.0f}; + Require(_FXp_getw_nid_postfix(zeroHead, 3) == 0.0f, "a zero first part ends the sum"); + const float zeroSecond[] = {5.0f, 0.0f, 11.0f}; + Require(_FXp_getw_nid_postfix(zeroSecond, 3) == 5.0f, "a zero second part ends the sum"); + Require(_FXp_getw_nid_postfix(one, 2) == 12.0f, "two parts are added"); + const float zeroThird[] = {5.0f, 7.0f, 0.0f, 13.0f}; + Require(_FXp_getw_nid_postfix(zeroThird, 4) == 12.0f, "a zero third part ends the sum"); + Require(_FXp_getw_nid_postfix(one, 3) == 23.0f, "three exact parts are added"); + Require(_FXp_getw_nid_postfix(one, 4) == 36.0f, "the fourth part joins the third"); + const float carry[] = {16777216.0f, 1.0f, 1.0f, 2.0f}; + Require(_FXp_getw_nid_postfix(carry, 3) == 16777218.0f, "a second part lost in the first sum is folded with the third first"); + Require(_FXp_getw_nid_postfix(carry, 4) == 16777220.0f, "a second part lost in the first sum is folded with the third and fourth first"); + const float nan[] = {std::nanf(""), 1.0f}; + Require(std::isnan(_FXp_getw_nid_postfix(nan, 2)), "a NaN first part is summed, not compared away"); +} + +int main() { + Require(_Isdst_nid_postfix() == -1, "_Isdst did not return -1"); + TestDaysto(); + TestXpGetw(); + return 0; +} diff --git a/docs/dev/debt/libSceLibcInternal.md b/docs/dev/debt/libSceLibcInternal.md index 82d71328ac..742308f9db 100644 --- a/docs/dev/debt/libSceLibcInternal.md +++ b/docs/dev/debt/libSceLibcInternal.md @@ -351,20 +351,21 @@ The named entries below use provider-specific NID aliases to avoid interposing h - [3.20 pending imports](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - The following PS5 3.20 inventory names are declared for import resolution only; their prototypes and behavior are unverified, and each call throws through `NotImplemented_nid_no_patch`. The source list is [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). - [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_Atan`, `_Atqexit`, `_Atthreadexit`, `_CStrftime`, `_CStrxfrm`, `_CWcsxfrm`, `_Call_onceEx`, `_Closreg` -- [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_Daysto`, `_Deletegloballocale`, `_Do_call`, `_Dtento`, `_Erfc`, `_FAtan` +- [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_Deletegloballocale`, `_Do_call`, `_Dtento`, `_Erfc`, `_FAtan` - [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_FDtento`, `_FErfc`, `_FHypot`, `_FLogpoly`, `_FPlsw`, `_FPmsw`, `_FPow`, `_FQuad` -- [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_FQuadph`, `_FRint`, `_FSincos`, `_FTan`, `_FTgamma`, `_FXp_addh`, `_FXp_addx`, `_FXp_getw`, `_FXp_invx` +- [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_FQuadph`, `_FRint`, `_FSincos`, `_FTan`, `_FTgamma`, `_FXp_addh`, `_FXp_addx`, `_FXp_invx` - [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_FXp_ldexpx`, `_FXp_movx`, `_FXp_mulh`, `_FXp_mulx`, `_FXp_setn`, `_FXp_setw`, `_FXp_sqrtx`, `_FXp_subx`, `_Fac_tidy`, `_Fail_s` - [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - - [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_Fgpos`, `_Fofind`, `_Fofree`, `_Fopen`, `_Foprep`, `_Frprep`, `_Fspos` - [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_Fwprep`, `_Genld`, `_Gentime`, `_Getcloc`, `_Getctyptab`, `_Getdst`, `_Geterrno`, `_Getfld`, `_Getfloat`, `_Getgloballocale` - [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_Getint`, `_Getpcostate`, `_Getpwcostate`, `_Getpwctrtab`, `_Getpwctytab`, `_Getstr`, `_Gettime`, `_Getzone`, `_Hypot` -- [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_Isdst`, `_LAtan`, `_LDtento`, `_LErfc`, `_LHypot`, `_LLogpoly` +- [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_LAtan`, `_LDtento`, `_LErfc`, `_LHypot`, `_LLogpoly` - [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_LPlsw`, `_LPmsw`, `_LPow`, `_LQuad`, `_LQuadph`, `_LRint`, `_LSincos`, `_LTan` - [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_LTgamma`, `_LXp_addh`, `_LXp_addx`, `_LXp_getw`, `_LXp_invx`, `_LXp_ldexpx`, `_LXp_movx`, `_LXp_mulh`, `_LXp_mulx`, `_LXp_setn` - [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_LXp_setw`, `_LXp_sqrtx`, `_LXp_subx`, `_Ldtob`, `_Litob`, `_Lock_spin_lock`, `_Locsum`, `_Locterm`, `_Locvar`, `_Logpoly` - [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_Makeloc`, `_Makestab`, `_Makewct`, `_Mbtowc`, `_Nnl` - [Additional imported names](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - `_Plsw`, `_Pmsw`, `_Pow`, `_Printf`, `_Putfld`, `_Putstr`, `_Puttxt`, `_Quad`, `_Quadph`, `_Readloc` +- [`_Daysto`, `_FXp_getw` and `_Isdst`](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - follow firmware 13.42's libSceLibcInternal: read to the return, disassembly only, not measured on a console. `_Isdst` returns -1 without reading its argument. `_Daysto(year, month)` adds the leap days before `year` to the module's twelve-entry common or leap month table, as Dinkumware's `_Daysto` does; the module indexes the table with any month, and a month outside 0 to 11 throws here instead of reading past it. `_FXp_getw` follows Dinkumware's `_Xp_getw` for `float` parts, branch for branch. ### Additional PS5 3.20 imports (round 6) From 0986fdea42e1e7f0d6b6f83107595b6ae81070d0 Mon Sep 17 00:00:00 2001 From: BunnyFl <166477956+BunnyFl@users.noreply.github.com> Date: Mon, 2 Nov 2026 04:20:44 +0100 Subject: [PATCH 233/612] fix(libSceLibcInternal): compute _Daysto's leap days without signed overflow The firmware subtracts and shifts as unsigned 32-bit values, so 4 - year wraps for the five years from INT_MIN to INT_MIN + 4 instead of overflowing; do the same, and test both sides of that range and INT_MAX. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceLibcInternal/Export.cpp | 11 ++++++----- core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp | 8 ++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceLibcInternal/Export.cpp b/core/libs/prx/libSceLibcInternal/Export.cpp index 51ae83331a..f501e7a671 100644 --- a/core/libs/prx/libSceLibcInternal/Export.cpp +++ b/core/libs/prx/libSceLibcInternal/Export.cpp @@ -1133,11 +1133,12 @@ int APS5_VABI _Daysto_nid_postfix(int year, int month) { static constexpr short DaysBeforeMonth[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; static constexpr short LeapDaysBeforeMonth[12] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}; if (month < 0 || month >= 12) throw std::out_of_range("_Daysto: the month index is outside the twelve-entry tables"); - int leapDays = 0; - if (year > 0) leapDays = (year - 1) / 4; - else if (year <= -4) leapDays = 1 + (4 - year) / 4; - const bool common = (year & 3) != 0 || year == 0; - return leapDays + (common ? DaysBeforeMonth[month] : LeapDaysBeforeMonth[month]); + const auto bits = static_cast(year); + std::uint32_t leapDays = 0; + if (year > 0) leapDays = (bits - 1u) >> 2; + else if (year <= -4) leapDays = ((4u - bits) >> 2) + 1u; + const bool common = (bits & 3u) != 0 || year == 0; + return static_cast(leapDays + static_cast(common ? DaysBeforeMonth[month] : LeapDaysBeforeMonth[month])); } int APS5_VABI _Deletegloballocale_nid_postfix() { diff --git a/core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp b/core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp index 43dd6300d9..c6cb0a4128 100644 --- a/core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp +++ b/core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include static void Require(bool value, const char* message) { @@ -35,6 +36,13 @@ static void TestDaysto() { Require(_Daysto_nid_postfix(2023, 11) == 334 + 505, "_Daysto(2023, 11)"); Require(_Daysto_nid_postfix(2024, 11) == 335 + 505, "_Daysto(2024, 11)"); Require(Throws(2024, 12) && Throws(2024, -1), "a month outside the tables throws"); + constexpr int Lowest = std::numeric_limits::min(); + constexpr int Highest = std::numeric_limits::max(); + Require(_Daysto_nid_postfix(Lowest, 0) == 536870914 && _Daysto_nid_postfix(Lowest, 11) == 536871249, "the lowest year wraps 4 - year as an unsigned 32-bit value"); + Require(_Daysto_nid_postfix(Lowest + 1, 0) == 536870913 && _Daysto_nid_postfix(Lowest + 3, 11) == 536871247, "years just above the lowest wrap 4 - year"); + Require(_Daysto_nid_postfix(Lowest + 4, 0) == 536870913 && _Daysto_nid_postfix(Lowest + 4, 11) == 536871248, "the last wrapping year"); + Require(_Daysto_nid_postfix(Lowest + 5, 0) == 536870912 && _Daysto_nid_postfix(Lowest + 5, 11) == 536871246, "the first year whose 4 - year fits in int"); + Require(_Daysto_nid_postfix(Highest, 0) == 536870911 && _Daysto_nid_postfix(Highest, 11) == 536871245, "the highest year"); } static void TestXpGetw() { From 17aebbdc1634263e34c7ff1edf22e088e1c21c13 Mon Sep 17 00:00:00 2001 From: BunnyFl <166477956+BunnyFl@users.noreply.github.com> Date: Mon, 2 Nov 2026 04:33:29 +0100 Subject: [PATCH 234/612] test(libSceLibcInternal): stop pinning the out-of-range _Daysto month to a throw Co-Authored-By: Claude Opus 5.5 --- core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp b/core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp index c6cb0a4128..a07b48fb45 100644 --- a/core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp +++ b/core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp @@ -4,7 +4,6 @@ #include #include #include -#include static void Require(bool value, const char* message) { if (!value) { @@ -13,15 +12,6 @@ static void Require(bool value, const char* message) { } } -static bool Throws(int year, int month) { - try { - _Daysto_nid_postfix(year, month); - } catch (const std::out_of_range&) { - return true; - } - return false; -} - static void TestDaysto() { Require(_Daysto_nid_postfix(1, 0) == 0, "_Daysto(1, 0)"); Require(_Daysto_nid_postfix(1, 1) == 31, "_Daysto(1, 1)"); @@ -35,7 +25,6 @@ static void TestDaysto() { Require(_Daysto_nid_postfix(100, 11) == 359, "every fourth year uses the leap table, centuries included"); Require(_Daysto_nid_postfix(2023, 11) == 334 + 505, "_Daysto(2023, 11)"); Require(_Daysto_nid_postfix(2024, 11) == 335 + 505, "_Daysto(2024, 11)"); - Require(Throws(2024, 12) && Throws(2024, -1), "a month outside the tables throws"); constexpr int Lowest = std::numeric_limits::min(); constexpr int Highest = std::numeric_limits::max(); Require(_Daysto_nid_postfix(Lowest, 0) == 536870914 && _Daysto_nid_postfix(Lowest, 11) == 536871249, "the lowest year wraps 4 - year as an unsigned 32-bit value"); From 05a1773f84341706a9d61ab8e4efd4642fef8929 Mon Sep 17 00:00:00 2001 From: BunnyFl <166477956+BunnyFl@users.noreply.github.com> Date: Mon, 2 Nov 2026 05:12:52 +0100 Subject: [PATCH 235/612] feat(libSceVideoOut): answer the mode helpers as firmware 13.42 does sceVideoOutSysInitMode, sceVideoOutSysModeIsSystemDefault, sceVideoOutSysModeIsHdr, sceVideoOutSysModeIsVrLegacy and sceVideoOutSysModeIsVrNative read or fill the mode structure as firmware 13.42's libSceVideoOut does. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceVideoOut/InventoryExports.cpp | 33 +++++++----- .../include/InventoryExports.hpp | 10 ++-- .../tests/GuestVideoOutFirmwareAnswers.cpp | 52 +++++++++++++++++++ docs/dev/debt/libSceVideoOut.md | 7 +-- 4 files changed, 79 insertions(+), 23 deletions(-) diff --git a/core/libs/prx/libSceVideoOut/InventoryExports.cpp b/core/libs/prx/libSceVideoOut/InventoryExports.cpp index b52f34735b..b936a40eef 100644 --- a/core/libs/prx/libSceVideoOut/InventoryExports.cpp +++ b/core/libs/prx/libSceVideoOut/InventoryExports.cpp @@ -8,6 +8,12 @@ namespace { constexpr int VideoOutErrorCurveSamplePoints = static_cast(0x8029001Cu); constexpr int VideoOutErrorLatestEopHandle = static_cast(0x802900FFu); + +std::uint32_t ModeFlags(const void* mode) { + std::uint32_t flags; + std::memcpy(&flags, static_cast(mode) + 0x20, sizeof(flags)); + return flags; +} } extern "C" { @@ -424,8 +430,10 @@ int APS5_VABI sceVideoOutSysHdmiMonitorInfoIsMorpheus() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceVideoOutSysInitMode() { - NotImplemented_nid_no_patch(__func__); +void APS5_VABI sceVideoOutSysInitMode(void* mode) { + std::memset(mode, 0xFF, 0x20); + const std::uint32_t size = 0x20; + std::memcpy(mode, &size, sizeof(size)); } int APS5_VABI sceVideoOutSysIsModeS3d() { @@ -452,26 +460,25 @@ int APS5_VABI sceVideoOutSysIsUserStatusVr() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceVideoOutSysModeIsHdr() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVideoOutSysModeIsHdr(const void* mode) { + const auto value = static_cast(mode)[6]; + return static_cast(value - 12u) < 6u ? 1 : 0; } -int APS5_VABI sceVideoOutSysModeIsSystemDefault() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVideoOutSysModeIsSystemDefault(const void* mode) { + return static_cast(~ModeFlags(mode) & 1u); } -int APS5_VABI sceVideoOutSysModeIsVrLegacy() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVideoOutSysModeIsVrLegacy(const void* mode) { + return (ModeFlags(mode) & 6u) == 6u ? 1 : 0; } -int APS5_VABI sceVideoOutSysModeIsVrNative() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVideoOutSysModeIsVrNative(const void* mode) { + return (ModeFlags(mode) & 6u) == 2u ? 1 : 0; } int APS5_VABI sceVideoOutSysModeIsVr(const void* mode) { - std::uint32_t flags; - std::memcpy(&flags, static_cast(mode) + 0x20, sizeof(flags)); - return static_cast((flags >> 1) & 1u); + return static_cast((ModeFlags(mode) >> 1) & 1u); } int APS5_VABI sceVideoOutSysOpenInternal() { diff --git a/core/libs/prx/libSceVideoOut/include/InventoryExports.hpp b/core/libs/prx/libSceVideoOut/include/InventoryExports.hpp index 6b9f6feedc..40c9079b46 100644 --- a/core/libs/prx/libSceVideoOut/include/InventoryExports.hpp +++ b/core/libs/prx/libSceVideoOut/include/InventoryExports.hpp @@ -107,17 +107,17 @@ int APS5_VABI sceVideoOutSysGetVblankStatus2(); int APS5_VABI sceVideoOutSysGetVblankStatus(); int APS5_VABI sceVideoOutSysGetVideoOutModeByBusSpecifier(); int APS5_VABI sceVideoOutSysHdmiMonitorInfoIsMorpheus(); -int APS5_VABI sceVideoOutSysInitMode(); +void APS5_VABI sceVideoOutSysInitMode(void* mode); int APS5_VABI sceVideoOutSysIsModeS3d(); int APS5_VABI sceVideoOutSysIsOutputSupported(); int APS5_VABI sceVideoOutSysIsSupportedByMonitorInfo_(); int APS5_VABI sceVideoOutSysIsUserStatusHdr(); int APS5_VABI sceVideoOutSysIsUserStatusSystemDefault(); int APS5_VABI sceVideoOutSysIsUserStatusVr(); -int APS5_VABI sceVideoOutSysModeIsHdr(); -int APS5_VABI sceVideoOutSysModeIsSystemDefault(); -int APS5_VABI sceVideoOutSysModeIsVrLegacy(); -int APS5_VABI sceVideoOutSysModeIsVrNative(); +int APS5_VABI sceVideoOutSysModeIsHdr(const void* mode); +int APS5_VABI sceVideoOutSysModeIsSystemDefault(const void* mode); +int APS5_VABI sceVideoOutSysModeIsVrLegacy(const void* mode); +int APS5_VABI sceVideoOutSysModeIsVrNative(const void* mode); int APS5_VABI sceVideoOutSysModeIsVr(const void* mode); int APS5_VABI sceVideoOutSysOpenInternal(); int APS5_VABI sceVideoOutSysOpenVrNative(); diff --git a/core/libs/tests/GuestVideoOutFirmwareAnswers.cpp b/core/libs/tests/GuestVideoOutFirmwareAnswers.cpp index c463fc5d09..c1f55364e6 100644 --- a/core/libs/tests/GuestVideoOutFirmwareAnswers.cpp +++ b/core/libs/tests/GuestVideoOutFirmwareAnswers.cpp @@ -1,5 +1,6 @@ #include "prx/libSceVideoOut/include/InventoryExports.hpp" #include +#include #include #include #include @@ -19,6 +20,54 @@ static int ModeIsVr(std::uint32_t flags, std::uint8_t fill) { return sceVideoOutSysModeIsVr(mode.data()); } +static std::array Mode(std::uint32_t flags, std::uint8_t fill) { + std::array mode; + mode.fill(fill); + std::memcpy(mode.data() + 0x20, &flags, sizeof(flags)); + return mode; +} + +static std::array ModeWithByte6(std::uint8_t value, std::uint8_t fill) { + std::array mode; + mode.fill(fill); + mode[6] = value; + return mode; +} + +static void CheckInitMode() { + std::array mode; + mode.fill(0x5A); + sceVideoOutSysInitMode(mode.data()); + std::uint32_t size; + std::memcpy(&size, mode.data(), sizeof(size)); + Require(size == 0x20, "sceVideoOutSysInitMode did not store 0x20 in the first dword"); + for (std::size_t i = 4; i < 0x20; ++i) Require(mode[i] == 0xFF, "sceVideoOutSysInitMode did not fill bytes 4 to 0x1F with 0xFF"); + for (std::size_t i = 0x20; i < mode.size(); ++i) Require(mode[i] == 0x5A, "sceVideoOutSysInitMode wrote past 0x20 bytes"); +} + +static void CheckModeFlags() { + for (std::uint8_t fill : {std::uint8_t{0}, std::uint8_t{0xFF}}) { + for (std::uint32_t flags = 0; flags < 8; ++flags) { + for (std::uint32_t high : {0u, 0xFFFFFFF8u}) { + const auto mode = Mode(flags | high, fill); + Require(sceVideoOutSysModeIsSystemDefault(mode.data()) == ((flags & 1u) ? 0 : 1), "sceVideoOutSysModeIsSystemDefault did not report bit 0 of the dword at 0x20 clear"); + Require(sceVideoOutSysModeIsVrNative(mode.data()) == ((flags & 6u) == 2u ? 1 : 0), "sceVideoOutSysModeIsVrNative did not report bits 1 and 2 of the dword at 0x20 as 1 and 0"); + Require(sceVideoOutSysModeIsVrLegacy(mode.data()) == ((flags & 6u) == 6u ? 1 : 0), "sceVideoOutSysModeIsVrLegacy did not report bits 1 and 2 of the dword at 0x20 both set"); + } + } + } +} + +static void CheckModeIsHdr() { + for (unsigned value = 0; value < 256; ++value) { + const int expected = value >= 12 && value <= 17 ? 1 : 0; + for (std::uint8_t fill : {std::uint8_t{0}, std::uint8_t{0xFF}}) { + const auto mode = ModeWithByte6(static_cast(value), fill); + Require(sceVideoOutSysModeIsHdr(mode.data()) == expected, "sceVideoOutSysModeIsHdr did not report byte 6 from 12 to 17"); + } + } +} + int main() { Require(sceVideoOutAddBufferHdrPrivilege() == 0, "sceVideoOutAddBufferHdrPrivilege did not return 0"); Require(sceVideoOutRazorRecallCallbacks() == 0, "sceVideoOutRazorRecallCallbacks did not return 0"); @@ -30,5 +79,8 @@ int main() { Require(ModeIsVr(0xFFFFFFFFu, 0xFF) == 1, "a mode with every bit set did not report VR"); Require(ModeIsVr(0xFFFFFFFDu, 0xFF) == 0, "the other bits of the dword at 0x20 reported VR"); Require(ModeIsVr(0u, 0xFF) == 0, "bytes outside the dword at 0x20 reported VR"); + CheckInitMode(); + CheckModeFlags(); + CheckModeIsHdr(); return 0; } diff --git a/docs/dev/debt/libSceVideoOut.md b/docs/dev/debt/libSceVideoOut.md index 84cc0c64ec..00882208ec 100644 --- a/docs/dev/debt/libSceVideoOut.md +++ b/docs/dev/debt/libSceVideoOut.md @@ -158,17 +158,12 @@ | `sceVideoOutSysGetVblankStatus` | `d1AjT2uZJn0` | | `sceVideoOutSysGetVideoOutModeByBusSpecifier` | `AAELy8Yc3Mc` | | `sceVideoOutSysHdmiMonitorInfoIsMorpheus` | `3-9DXFGVg0M` | -| `sceVideoOutSysInitMode` | `9cmjhjqa7Sg` | | `sceVideoOutSysIsModeS3d` | `iUigHJ2op4M` | | `sceVideoOutSysIsOutputSupported` | `Z0EyU29WqOg` | | `sceVideoOutSysIsSupportedByMonitorInfo_` | `mfxElr79O7Q` | | `sceVideoOutSysIsUserStatusHdr` | `q+vWPvYIh8o` | | `sceVideoOutSysIsUserStatusSystemDefault` | `O57F5ikhGxo` | | `sceVideoOutSysIsUserStatusVr` | `4XsQdhiOaAc` | -| `sceVideoOutSysModeIsHdr` | `I7-lGGvFgBM` | -| `sceVideoOutSysModeIsSystemDefault` | `5Y8uUbGMio0` | -| `sceVideoOutSysModeIsVrLegacy` | `zecl8Gox+P0` | -| `sceVideoOutSysModeIsVrNative` | `SgA2MtojPto` | | `sceVideoOutSysOpenInternal` | `kP4l+nyBjRA` | | `sceVideoOutSysOpenVrNative` | `WUU+ufqgLJg` | | `sceVideoOutSysReadCrc64` | `eBvoaLwfBBQ` | @@ -238,6 +233,8 @@ - [Firmware 13.42 answers](../../../core/libs/prx/libSceVideoOut/InventoryExports.cpp) (libSceVideoOut) - `sceVideoOutAddBufferHdrPrivilege`, `sceVideoOutRazorRecallCallbacks` and `sceVideoOutSysUpdateRenderingMode` return 0, `sceVideoOutCurveSamplePointsCalc_` returns 0x8029001C, `sceVideoOutDriverGetLatestEopHandle` 0x802900FF and `sceVideoOutSysGetMonitorInfo_` -1, and `sceVideoOutSysModeIsVr` returns bit 1 of the dword at offset 0x20 of its argument, as firmware 13.42's libSceVideoOut does: read to the return, disassembly only, not measured on a console. Their parameter lists are unverified beyond what the code reads; the layout of the structure `sceVideoOutSysModeIsVr` reads is unknown apart from that dword. The module reads that dword without checking the pointer, so a null argument faults on the console as it does here +- [Firmware 13.42 mode helpers](../../../core/libs/prx/libSceVideoOut/InventoryExports.cpp) (libSceVideoOut) - `sceVideoOutSysInitMode` fills the first 0x20 bytes of its argument with 0xFF and then stores 0x20 in the first dword. Of the dword at offset 0x20, `sceVideoOutSysModeIsSystemDefault` reports bit 0 clear, `sceVideoOutSysModeIsVrNative` bits 2:1 equal to 01 and `sceVideoOutSysModeIsVrLegacy` both bits set; `sceVideoOutSysModeIsHdr` reports byte 6 from 12 to 17. As firmware 13.42's libSceVideoOut does: read to the return, disassembly only, not measured on a console. The init function leaves EAX unset, so it is declared `void`. Field meanings are unknown, and none of them checks the pointer. + - [sceVideoOutSetConfigurationMode](../../../core/libs/prx/libSceVideoOut/InventoryExports.cpp): the name hashes to NID `N1+j0RymMUQ`, a function export in both older and later version-labelled images. Its signature and behavior are unverified; this import-resolution declaration throws. - [Older callable NIDs](../../../core/libs/prx/libSceVideoOut/InventoryExports.cpp): 7 unidentified exports have unverified names, signatures and behavior. These are throwing import-resolution placeholders; no functionality is implemented. No matching NIDs were found in the later module inventory. Six use the primary library qualifier and one uses the nosubmission qualifier; qualified guest import resolution is unverified. From aef43daa600b1f03e26ca841c6e2d671969576c4 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 09:51:12 +0530 Subject: [PATCH 236/612] fix(libSceVrHand): validate parameter and result pointers in sceVrHandQueryMemory --- core/libs/prx/libSceVrHand/Export.cpp | 20 ++++++++++++-------- core/libs/tests/GuestVrHand.cpp | 3 +++ docs/dev/debt/libSceVrHand.md | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/core/libs/prx/libSceVrHand/Export.cpp b/core/libs/prx/libSceVrHand/Export.cpp index 5d646b9218..d4bd80f8ac 100644 --- a/core/libs/prx/libSceVrHand/Export.cpp +++ b/core/libs/prx/libSceVrHand/Export.cpp @@ -1,4 +1,5 @@ #include "prx/libSceVrHand/include/Api.hpp" +#include #include #include #include @@ -35,17 +36,20 @@ int APS5_VABI sceVrHandInitialize() { int APS5_VABI sceVrHandQueryMemory(const void* param, void* result) { constexpr int errInvalidArg = static_cast(0x8a870006); if (param == nullptr || result == nullptr) return errInvalidArg; - const auto* input = static_cast(param); std::uint32_t size = 0; - std::memcpy(&size, input, sizeof(size)); + if (!GuestCopyIn_nid_no_patch(&size, param, sizeof(size))) return errInvalidArg; if (size != 0x20) return errInvalidArg; + std::array input{}; + if (!GuestCopyIn_nid_no_patch(input.data(), param, sizeof(input))) return errInvalidArg; for (std::size_t index = 4; index < 0x20; ++index) if (input[index] != 0) return errInvalidArg; - auto* output = static_cast(result); - const std::uint32_t at0 = 0x3c00000; - const std::uint32_t at4 = 0x10000; - std::memcpy(output, &at0, sizeof(at0)); - std::memcpy(output + 4, &at4, sizeof(at4)); - std::memset(output + 8, 0, 0x14); + struct MemoryInfo { + std::uint32_t memorySize{0x3c00000}; + std::uint32_t alignment{0x10000}; + std::uint8_t reserved[0x14]{}; + }; + static_assert(sizeof(MemoryInfo) == 28); + const MemoryInfo info{}; + if (!GuestCopyOut_nid_no_patch(result, &info, sizeof(info))) return errInvalidArg; return 0; } diff --git a/core/libs/tests/GuestVrHand.cpp b/core/libs/tests/GuestVrHand.cpp index aeb0d2d77f..ff21ab3290 100644 --- a/core/libs/tests/GuestVrHand.cpp +++ b/core/libs/tests/GuestVrHand.cpp @@ -34,6 +34,9 @@ int main() { auto param = ValidParam(); out.fill(kFill); + auto* const badPtr = reinterpret_cast(std::uintptr_t{0x10}); + Require(sceVrHandQueryMemory(badPtr, out.data()) == kErrInvalidArg && Untouched(out)); + Require(sceVrHandQueryMemory(param.data(), badPtr) == kErrInvalidArg); Require(sceVrHandQueryMemory(nullptr, out.data()) == kErrInvalidArg && Untouched(out)); Require(sceVrHandQueryMemory(param.data(), nullptr) == kErrInvalidArg); diff --git a/docs/dev/debt/libSceVrHand.md b/docs/dev/debt/libSceVrHand.md index a08a9d93a4..c91567c01d 100644 --- a/docs/dev/debt/libSceVrHand.md +++ b/docs/dev/debt/libSceVrHand.md @@ -3,4 +3,4 @@ ### Unknown function info - [sceVrHandFinalize, sceVrHandGetMesh, sceVrHandGetMeshSize, sceVrHandGetResult, sceVrHandInitialize, sceVrHandStart, sceVrHandStop](../../../core/libs/prx/libSceVrHand/Export.cpp) (libSceVrHand) - names and NIDs verified from pinned [ps5rs stubs](https://github.com/claimore22/ps5rs/tree/5f252a658ff470bcb2b6cd15205fd8ca64ae4477/data/stubs/by_library) (`libSceVrHand.txt`), with NID match confirmed via `tools/nid_names.py` `compute_nid`. Signatures, parameter layouts, and runtime behavior remain unverified; all paths throw through `NotImplemented_nid_no_patch`. -- [sceVrHandQueryMemory](../../../core/libs/prx/libSceVrHand/Export.cpp) (libSceVrHand) - the meaning of the parameter and output fields is unknown. It follows firmware 13.42's libSceVrHand.sprx, read from its disassembly and run natively on x86-64 (not a console): a null pointer, a first 32-bit parameter word other than 0x20, or any nonzero parameter byte from offset 4 to 31 returns `0x8a870006` and writes nothing; otherwise it stores the 32-bit values 0x3c00000 and 0x10000 at output offsets 0 and 4, clears output bytes 8 to 27 and returns 0. Parameter bytes from offset 32 are not read. +- [sceVrHandQueryMemory](../../../core/libs/prx/libSceVrHand/Export.cpp) (libSceVrHand) - the meaning of the parameter and output fields is unknown. It follows firmware 13.42's libSceVrHand.sprx, read from its disassembly and run natively on x86-64 (not a console): a null pointer, unreadable or unwritable pointers, a first 32-bit parameter word other than 0x20, or any nonzero parameter byte from offset 4 to 31 returns `0x8a870006` and writes nothing; otherwise it stores the 32-bit values 0x3c00000 and 0x10000 at output offsets 0 and 4, clears output bytes 8 to 27 and returns 0. Returning `0x8a870006` for unwritable or unreadable pointers is a defensive host-protection fallback; console behavior is unmeasured. From e39e407cb96b8e7bab3f99d142dda4c6a179285d Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 10:59:41 +0530 Subject: [PATCH 237/612] fix(libSceNpCommerce): return INVALID_ARGUMENT from sceNpCommerceDialogGetResult for an unwritable result --- core/libs/prx/libSceNpCommerce/Export.cpp | 23 +++++++++++++++++++---- core/libs/tests/GuestNpCommerceDialog.cpp | 1 + docs/dev/debt/libSceNpCommerce.md | 1 + 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceNpCommerce/Export.cpp b/core/libs/prx/libSceNpCommerce/Export.cpp index e964c0be97..41d52abe7d 100644 --- a/core/libs/prx/libSceNpCommerce/Export.cpp +++ b/core/libs/prx/libSceNpCommerce/Export.cpp @@ -1,5 +1,6 @@ #include "prx/libSceNpCommerce/include/Api.hpp" #include "prx/libSceUserService/UserService.hpp" +#include #include #include #include @@ -126,11 +127,25 @@ int APS5_VABI sceNpCommerceDialogGetResult(NpCommerceDialogResult* result) { if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; if (g_status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; if (g_status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; + const std::int32_t cancelResult = COMMON_DIALOG_RESULT_USER_CANCELED; + const std::uint8_t authorized = 0; + const std::uint64_t userData = g_userData; + const int savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(&result->result, &cancelResult, sizeof(cancelResult)) || + !GuestCopyOut_nid_no_patch(&result->authorized, &authorized, sizeof(authorized)) || + !GuestCopyOut_nid_no_patch(&result->userData, &userData, sizeof(userData))) { + errno = savedErrno; + return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + } const auto sdkVersion = compiledSdkVersion(); - result->result = COMMON_DIALOG_RESULT_USER_CANCELED; - result->authorized = 0; - result->userData = g_userData; - if (sdkVersion >= 0x01700000) std::fill(std::begin(result->reserved), std::end(result->reserved), 0); + if (sdkVersion >= 0x01700000) { + const std::uint8_t zeroReserved[sizeof(result->reserved)] = {0}; + if (!GuestCopyOut_nid_no_patch(result->reserved, zeroReserved, sizeof(zeroReserved))) { + errno = savedErrno; + return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + } + } + errno = savedErrno; return COMMON_DIALOG_RESULT_USER_CANCELED; } diff --git a/core/libs/tests/GuestNpCommerceDialog.cpp b/core/libs/tests/GuestNpCommerceDialog.cpp index 12cd7fa4bf..d8c338121c 100644 --- a/core/libs/tests/GuestNpCommerceDialog.cpp +++ b/core/libs/tests/GuestNpCommerceDialog.cpp @@ -27,6 +27,7 @@ constexpr std::int32_t resultUserCanceled = 1; void RequireCanceledResult(std::uint64_t userData, bool clearsReserved) { NpCommerceDialogResult result; std::memset(&result, 0x33, sizeof(result)); + Require(sceNpCommerceDialogGetResult(reinterpret_cast(0x10)) == static_cast(0x80B8000Au)); Require(sceNpCommerceDialogGetResult(&result) == resultUserCanceled); Require(result.result == resultUserCanceled); Require(result.authorized == 0); diff --git a/docs/dev/debt/libSceNpCommerce.md b/docs/dev/debt/libSceNpCommerce.md index a4bb61b2f1..79e2d1976f 100644 --- a/docs/dev/debt/libSceNpCommerce.md +++ b/docs/dev/debt/libSceNpCommerce.md @@ -2,6 +2,7 @@ ### Silent stubs +- [sceNpCommerceDialogGetResult](../../../core/libs/prx/libSceNpCommerce/Export.cpp) (libSceNpCommerce) - returning `COMMON_DIALOG_ERROR_INVALID_ARGUMENT` for an unwritable result is a host-safety fallback as console behavior for unwritable memory has not been measured. - [sceNpCommerceDialogOpen, sceNpCommerceDialogOpen2](../../../core/libs/prx/libSceNpCommerce/Export.cpp) (libSceNpCommerce) - with `ANYPS5_PSN_DIALOGS=show`, a successful open shows AnyPS5's notice that the feature needs PlayStation Network; the console's own display without PlayStation Network is not measured, and the notice wording is AnyPS5's. - [libSceNpCommerce](../../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls and `sceNpCommerceSetPsStoreIconLayout` do nothing From 53c689a5d8e8d1a6bc78bdee9ac63b3cee1a749c Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 11:09:33 +0530 Subject: [PATCH 238/612] fix(libSceUsbStorageDialog): return INVALID_ARGUMENT from sceUsbStorageDialogGetResult for an unwritable result --- .../prx/libSceUsbStorageDialog/Export.cpp | 33 ++++++++++++++++--- core/libs/tests/GuestUsbStorageDialog.cpp | 1 + docs/dev/debt/libSceUsbStorageDialog.md | 2 +- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceUsbStorageDialog/Export.cpp b/core/libs/prx/libSceUsbStorageDialog/Export.cpp index 290ab47eca..ba8378debe 100644 --- a/core/libs/prx/libSceUsbStorageDialog/Export.cpp +++ b/core/libs/prx/libSceUsbStorageDialog/Export.cpp @@ -1,6 +1,7 @@ #include "prx/libSceUsbStorageDialog/include/Api.hpp" #include "prx/libSceUserService/UserService.hpp" #include +#include #include #include #include @@ -73,13 +74,37 @@ int APS5_VABI sceUsbStorageDialogGetResult(UsbStorageDialogResult* result) { const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; if (g_closed.load()) { - result->result = COMMON_DIALOG_ERROR_NOT_FINISHED; - result->status = 0; + const std::int32_t notFinished = COMMON_DIALOG_ERROR_NOT_FINISHED; + const std::int32_t zeroStatus = 0; + const int savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(&result->result, ¬Finished, sizeof(notFinished)) || + !GuestCopyOut_nid_no_patch(&result->status, &zeroStatus, sizeof(zeroStatus))) { + errno = savedErrno; + return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + } + errno = savedErrno; return 0; } if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; - if (g_sdkVersion >= 0x04500000 && (result->size != sizeof(*result) || result->result != COMMON_DIALOG_ERROR_BUSY)) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; - result->result = COMMON_DIALOG_RESULT_USER_CANCELED; + if (g_sdkVersion >= 0x04500000) { + std::uint64_t size = 0; + std::int32_t currentResult = 0; + const int savedErrno = errno; + if (!GuestCopyIn_nid_no_patch(&size, &result->size, sizeof(size)) || + !GuestCopyIn_nid_no_patch(¤tResult, &result->result, sizeof(currentResult))) { + errno = savedErrno; + return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + } + errno = savedErrno; + if (size != sizeof(*result) || currentResult != COMMON_DIALOG_ERROR_BUSY) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + } + const std::int32_t cancelResult = COMMON_DIALOG_RESULT_USER_CANCELED; + const int savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(&result->result, &cancelResult, sizeof(cancelResult))) { + errno = savedErrno; + return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + } + errno = savedErrno; return COMMON_DIALOG_RESULT_USER_CANCELED; } diff --git a/core/libs/tests/GuestUsbStorageDialog.cpp b/core/libs/tests/GuestUsbStorageDialog.cpp index 62ecc41378..41f7ef2e08 100644 --- a/core/libs/tests/GuestUsbStorageDialog.cpp +++ b/core/libs/tests/GuestUsbStorageDialog.cpp @@ -80,6 +80,7 @@ int main() { Require(sceUsbStorageDialogGetStatus() == kStatusFinished); Require(sceUsbStorageDialogGetResult(nullptr) == kErrArgNull); + Require(sceUsbStorageDialogGetResult(reinterpret_cast(0x10)) == static_cast(0x80B8000Au)); result.size = 79; Require(sceUsbStorageDialogGetResult(&result) == static_cast(0x80B8000Au)); result.size = sizeof(result); diff --git a/docs/dev/debt/libSceUsbStorageDialog.md b/docs/dev/debt/libSceUsbStorageDialog.md index 58151aaf70..a4554af141 100644 --- a/docs/dev/debt/libSceUsbStorageDialog.md +++ b/docs/dev/debt/libSceUsbStorageDialog.md @@ -2,7 +2,7 @@ ### Silent stubs -- [libSceUsbStorageDialog](../../../core/libs/prx/libSceUsbStorageDialog/Export.cpp) (libSceUsbStorageDialog) - the USB storage dialog runs the common-dialog state machine without showing a UI and reports the dialog as canceled by the user (`COMMON_DIALOG_RESULT_USER_CANCELED = 1`). `sceUsbStorageDialogOpen` transitions to `RUNNING` (2), while `sceUsbStorageDialogUpdateStatus` and `sceUsbStorageDialogClose` transition to `FINISHED` (3) before returning results, matching other common dialogs in the repository. +- [libSceUsbStorageDialog](../../../core/libs/prx/libSceUsbStorageDialog/Export.cpp) (libSceUsbStorageDialog) - the USB storage dialog runs the common-dialog state machine without showing a UI and reports the dialog as canceled by the user (`COMMON_DIALOG_RESULT_USER_CANCELED = 1`). `sceUsbStorageDialogOpen` transitions to `RUNNING` (2), while `sceUsbStorageDialogUpdateStatus` and `sceUsbStorageDialogClose` transition to `FINISHED` (3) before returning results, matching other common dialogs in the repository; returning `COMMON_DIALOG_ERROR_INVALID_ARGUMENT` for an unreadable or unwritable result is a host-safety fallback as console behavior for unwritable memory has not been measured. ### Unknown function info From cd2569b1b54885b3470d58228c42f803f2ca4d7d Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 11:33:05 +0530 Subject: [PATCH 239/612] fix(libSceAudioIn): return INVALID_POINTER from sceAudioInGetGain for an unwritable gain --- core/libs/prx/libSceAudioIn/Export.cpp | 8 +++++++- core/libs/tests/GuestAudioIn.cpp | 2 ++ docs/dev/debt/libSceAudioIn.md | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAudioIn/Export.cpp b/core/libs/prx/libSceAudioIn/Export.cpp index 9e9721f6eb..6a902bac4a 100644 --- a/core/libs/prx/libSceAudioIn/Export.cpp +++ b/core/libs/prx/libSceAudioIn/Export.cpp @@ -2,6 +2,7 @@ #include "prx/libSceAudioIn/AudioInCapture.hpp" #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/PreciseWait.hpp" @@ -199,7 +200,12 @@ int32_t APS5_VABI sceAudioInIsPadEmbeddedMic(int32_t handle) { int32_t APS5_VABI sceAudioInGetGain(int32_t handle, float* gain) { if (!gain) return AUDIO_IN_ERROR_INVALID_POINTER; - *gain = 1.0f; + constexpr float defaultGain = 1.0f; + const int savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(gain, &defaultGain, sizeof(defaultGain))) { + errno = savedErrno; + return AUDIO_IN_ERROR_INVALID_POINTER; + } std::lock_guard lock(g_mutex); return find(handle) ? 0 : AUDIO_IN_ERROR_INVALID_HANDLE; } diff --git a/core/libs/tests/GuestAudioIn.cpp b/core/libs/tests/GuestAudioIn.cpp index 7fcbb2a879..4c00bcca04 100644 --- a/core/libs/tests/GuestAudioIn.cpp +++ b/core/libs/tests/GuestAudioIn.cpp @@ -84,6 +84,7 @@ void TestValidation() { Require(sceAudioInGetGain(badHandle, &gain) == invalidHandle && gain == 1.0f); Require(sceAudioInGetRerouteCount(badHandle) == invalidHandle); Require(sceAudioInGetGain(badHandle, nullptr) == invalidPointer); + Require(sceAudioInGetGain(badHandle, reinterpret_cast(static_cast(0x10))) == invalidPointer); Require(sceAudioInClose(badHandle) == invalidHandle); Require(sceAudioInInput(badHandle, nullptr) == invalidHandle); Require(sceAudioInGetSilentState(badHandle) == invalidHandle); @@ -92,6 +93,7 @@ void TestValidation() { float gain = 0.0f; Require(port > 0 && sceAudioInGetGain(port, &gain) == 0 && gain == 1.0f && sceAudioInGetRerouteCount(port) == 0); Require(sceAudioInGetGain(port, nullptr) == invalidPointer); + Require(sceAudioInGetGain(port, reinterpret_cast(static_cast(0x10))) == invalidPointer); Require(sceAudioInClose(port) == 0); gain = 0.25f; Require(sceAudioInGetGain(port, &gain) == invalidHandle && gain == 1.0f && sceAudioInGetRerouteCount(port) == invalidHandle); diff --git a/docs/dev/debt/libSceAudioIn.md b/docs/dev/debt/libSceAudioIn.md index 4e2432f8ba..87b49e81a9 100644 --- a/docs/dev/debt/libSceAudioIn.md +++ b/docs/dev/debt/libSceAudioIn.md @@ -49,4 +49,4 @@ - [sceAudioInIsPadEmbeddedMic](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - returns 0 for every open port: on the console it returns 1 when the port captures from the controller's built-in microphone and 0 for another input device, but ports here capture from the host's default input device - [sceAudioInHqOpen, sceAudioInAsyncOpen](../../../core/libs/prx/libSceAudioIn/Export.cpp) - PS5 wrapper validation requires 48000 Hz followed by exactly 128 samples for both calls; HqOpen then requires format 2. The host uses the same SDL capture path for both, so native asynchronous scheduling and high-quality processing are not reproduced. Common port validation, device selection and host capture pacing remain incomplete. One title (PPSA03355, in its libfmod plugin) disassembled for this entry calls `sceAudioInAsyncOpen` from one call site (0xe5972) with index 0, 128 samples, 48000 Hz and a last argument of 2, plus a type argument of 0 or 1, which agrees with the 48000 Hz and 128 samples above; it stores the result in a slot of its own and branches on a negative result. What the last argument 2 means is not shown. -- [sceAudioInGetGain, sceAudioInGetRerouteCount](../../../core/libs/prx/libSceAudioIn/Export.cpp) - GetGain writes 1.0f before handle validation and returns 0x80260105 for a null output, as confirmed by the PS5 module. Native handle bit checks and uninitialized/inactive port errors differ from the host port table. Native reroute counters follow device reassignment; the host opens each SDL device once and reports zero. +- [sceAudioInGetGain, sceAudioInGetRerouteCount](../../../core/libs/prx/libSceAudioIn/Export.cpp) - GetGain writes 1.0f before handle validation and returns 0x80260105 for a null or unwritable output, as confirmed by the PS5 module for null; returning 0x80260105 for an unwritable pointer is a defensive host-protection fallback and console behavior is unmeasured. Native handle bit checks and uninitialized/inactive port errors differ from the host port table. Native reroute counters follow device reassignment; the host opens each SDL device once and reports zero. From bd785545fdd837736c1217a7a61f6f56a6f8e679 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 11:46:24 +0530 Subject: [PATCH 240/612] fix(libSceHmd): return ParameterNull from device queries for unwritable pointers --- core/libs/prx/libSceHmd/Export.cpp | 19 +++++++++++++++---- core/libs/tests/GuestHmd.cpp | 2 ++ docs/dev/debt/libSceHmd.md | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceHmd/Export.cpp b/core/libs/prx/libSceHmd/Export.cpp index e2a3f61d72..a64d19fb2c 100644 --- a/core/libs/prx/libSceHmd/Export.cpp +++ b/core/libs/prx/libSceHmd/Export.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -106,9 +107,14 @@ std::int32_t APS5_VABI sceHmdClose(std::int32_t handle) { std::int32_t APS5_VABI sceHmdGetDeviceInformation(Hmd::DeviceInformation* info) { if (!info) return Hmd::ParameterNull; if (!initialized.load()) return Hmd::NotInitialized; - *info = {}; - info->status = Hmd::DeviceStatus::NotDetected; - info->userId = -1; + Hmd::DeviceInformation result{}; + result.status = Hmd::DeviceStatus::NotDetected; + result.userId = -1; + const int savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(info, &result, sizeof(result))) { + errno = savedErrno; + return Hmd::ParameterNull; + } return 0; } @@ -121,7 +127,12 @@ std::int32_t APS5_VABI sceHmdGetDeviceInformationByHandle(std::int32_t handle, std::int32_t APS5_VABI sceHmdInternalGetDeviceStatus(Hmd::DeviceStatus* status) { if (!status) return Hmd::ParameterNull; - *status = Hmd::DeviceStatus::NotDetected; + constexpr Hmd::DeviceStatus deviceStatus = Hmd::DeviceStatus::NotDetected; + const int savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(status, &deviceStatus, sizeof(deviceStatus))) { + errno = savedErrno; + return Hmd::ParameterNull; + } return 0; } diff --git a/core/libs/tests/GuestHmd.cpp b/core/libs/tests/GuestHmd.cpp index fa06463949..008a6bb9b6 100644 --- a/core/libs/tests/GuestHmd.cpp +++ b/core/libs/tests/GuestHmd.cpp @@ -42,6 +42,7 @@ void CheckDisconnected() { Require(sceHmdInternalGetDeviceStatus(&status) == 0, "status query failed"); Require(status == Hmd::DeviceStatus::NotDetected, "headset reported connected"); Require(sceHmdInternalGetDeviceStatus(nullptr) == Hmd::ParameterNull, "null status"); + Require(sceHmdInternalGetDeviceStatus(reinterpret_cast(static_cast(0x10))) == Hmd::ParameterNull, "unwritable status"); Require(sceHmdInternalMmapIsConnect() == 0, "connection reported present"); Require(!sceHmdInternalIsGameVr2d(), "game reported in VR2D without a headset"); Require(!sceHmdInternalIsMiniAppVr2d(), "mini app reported in VR2D without a headset"); @@ -205,6 +206,7 @@ int main() { expected.userId = -1; Require(std::memcmp(&info, &expected, sizeof(info)) == 0, "incorrect absent-device output"); Require(sceHmdGetDeviceInformation(nullptr) == Hmd::ParameterNull, "null device query"); + Require(sceHmdGetDeviceInformation(reinterpret_cast(static_cast(0x10))) == Hmd::ParameterNull, "unwritable device query"); Require(sceHmdOpen(-1, 0, 0, nullptr) == Hmd::ParameterInvalid, "invalid user"); Require(sceHmdOpen(0xff, 0, 0, nullptr) == Hmd::ParameterInvalid, "system user"); diff --git a/docs/dev/debt/libSceHmd.md b/docs/dev/debt/libSceHmd.md index e4c20de5bf..b26b01e896 100644 --- a/docs/dev/debt/libSceHmd.md +++ b/docs/dev/debt/libSceHmd.md @@ -2,7 +2,7 @@ ### Silent stubs -- [libSceHmd](../../../core/libs/prx/libSceHmd/Export.cpp) implements only the disconnected-headset path: initialization succeeds, device queries report `NotDetected`, and opening a device returns `DeviceDisconnected`. Headset support, tracking, rendering and additional HMD exports are not implemented; SDK-level ABI compatibility and in-game behaviour remain unverified. +- [libSceHmd](../../../core/libs/prx/libSceHmd/Export.cpp) implements only the disconnected-headset path: initialization succeeds, device queries report `NotDetected`, and opening a device returns `DeviceDisconnected`. Returning ParameterNull for unwritable pointers in sceHmdGetDeviceInformation and sceHmdInternalGetDeviceStatus is a defensive host-protection fallback and console behavior is unmeasured. Headset support, tracking, rendering and additional HMD exports are not implemented; SDK-level ABI compatibility and in-game behaviour remain unverified. ### Unknown function info From 4ecc1f3ff88e882ff09fb5bf261716e9386bb889 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 12:49:09 +0530 Subject: [PATCH 241/612] fix(libSceEditMp4): ignore unwritable pointers in sceEditMp4InitializeParam --- core/libs/prx/libSceEditMp4/Export.cpp | 12 ++++++++---- core/libs/tests/GuestEditMp4.cpp | 1 + docs/dev/debt/libSceEditMp4.md | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceEditMp4/Export.cpp b/core/libs/prx/libSceEditMp4/Export.cpp index 423d5a76e0..552fc7870b 100644 --- a/core/libs/prx/libSceEditMp4/Export.cpp +++ b/core/libs/prx/libSceEditMp4/Export.cpp @@ -1,6 +1,8 @@ #include "prx/libSceEditMp4/include/Api.hpp" #include #include +#include +#include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -29,12 +31,14 @@ int APS5_VABI sceEditMp4Initialize() { void APS5_VABI sceEditMp4InitializeParam(void* param) { if (param == nullptr) return; - auto* bytes = static_cast(param); + std::array initial{}; const std::uint64_t at10 = 0x3f; const std::uint32_t at18 = 700; - std::memset(bytes, 0, 0x10); - std::memcpy(bytes + 0x10, &at10, sizeof(at10)); - std::memcpy(bytes + 0x18, &at18, sizeof(at18)); + std::memcpy(initial.data() + 0x10, &at10, sizeof(at10)); + std::memcpy(initial.data() + 0x18, &at18, sizeof(at18)); + const int savedErrno = errno; + GuestCopyOut_nid_no_patch(param, initial.data(), initial.size()); + errno = savedErrno; } std::uint64_t APS5_VABI sceEditMp4QueryMemSize() { diff --git a/core/libs/tests/GuestEditMp4.cpp b/core/libs/tests/GuestEditMp4.cpp index a20ba36cc3..749ee51d37 100644 --- a/core/libs/tests/GuestEditMp4.cpp +++ b/core/libs/tests/GuestEditMp4.cpp @@ -30,6 +30,7 @@ int main() { for (std::size_t index = 0x1c; index < bytes.size(); ++index) Require(bytes[index] == fill); } sceEditMp4InitializeParam(nullptr); + sceEditMp4InitializeParam(reinterpret_cast(static_cast(0x10))); Require(sceEditMp4QueryMemSize() == 0x387d30); Require(sceEditMp4QueryMemSize() == 0x387d30); diff --git a/docs/dev/debt/libSceEditMp4.md b/docs/dev/debt/libSceEditMp4.md index fbe006c174..d7f13cc074 100644 --- a/docs/dev/debt/libSceEditMp4.md +++ b/docs/dev/debt/libSceEditMp4.md @@ -7,4 +7,4 @@ ### Unknown function info - [sceEditMp4Abort, sceEditMp4Edit, sceEditMp4GetStatus, sceEditMp4Initialize, sceEditMp4SetFinishCallback, sceEditMp4Terminate and sceEditMp4Unite](../../../core/libs/prx/libSceEditMp4/Export.cpp) (libSceEditMp4) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceEditMp4.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified -- [sceEditMp4InitializeParam](../../../core/libs/prx/libSceEditMp4/Export.cpp) (libSceEditMp4) - the size of the parameter structure and the meaning of its fields are unknown. It follows firmware 13.42's libSceEditMp4.sprx, read from its disassembly and run natively on x86-64 (not a console): it returns nothing, ignores a null pointer and otherwise clears bytes 0 to 15, stores the 64-bit value 0x3f at offset 16 and the 32-bit value 700 at offset 24, and leaves the bytes from offset 28 unchanged. +- [sceEditMp4InitializeParam](../../../core/libs/prx/libSceEditMp4/Export.cpp) (libSceEditMp4) - the size of the parameter structure and the meaning of its fields are unknown. It follows firmware 13.42's libSceEditMp4.sprx, read from its disassembly and run natively on x86-64 (not a console): it returns nothing, ignores a null pointer and otherwise clears bytes 0 to 15, stores the 64-bit value 0x3f at offset 16 and the 32-bit value 700 at offset 24, and leaves the bytes from offset 28 unchanged; ignoring an unmapped or unwritable pointer is a defensive host-protection fallback, and console behavior for unwritable memory has not been measured. From c5155fbe024482264a5d7604ac794ef7b7266522 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 13:06:10 +0530 Subject: [PATCH 242/612] fix(libSceFiber): return INVALID from sceFiberOptParamInitialize for an unwritable pointer --- core/libs/prx/libSceFiber/Export.cpp | 8 +++++++- core/libs/tests/Fiber.cpp | 6 ++++++ docs/dev/debt/libSceFiber.md | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceFiber/Export.cpp b/core/libs/prx/libSceFiber/Export.cpp index de28742c01..b70e32786d 100644 --- a/core/libs/prx/libSceFiber/Export.cpp +++ b/core/libs/prx/libSceFiber/Export.cpp @@ -624,7 +624,13 @@ int32_t APS5_VABI sceFiberRename(FiberObject* object, const char* name) { int32_t APS5_VABI sceFiberOptParamInitialize(FiberOptParam* opt_param) { if (!opt_param) return SCE_FIBER_ERROR_NULL; - std::memset(opt_param, 0, FIBER_OPT_PARAM_SIZE); + const std::uint8_t zero[FIBER_OPT_PARAM_SIZE] = {}; + const auto preserved = errno; + if (!GuestCopyOut_nid_no_patch(opt_param, zero, sizeof(zero))) { + errno = preserved; + return SCE_FIBER_ERROR_INVALID; + } + errno = preserved; return SCE_OK; } diff --git a/core/libs/tests/Fiber.cpp b/core/libs/tests/Fiber.cpp index d4eedb1c3a..cdf8f30810 100644 --- a/core/libs/tests/Fiber.cpp +++ b/core/libs/tests/Fiber.cpp @@ -566,6 +566,12 @@ int main(int argc, char** argv) { #endif FiberObject* self = nullptr; Require(sceFiberGetSelf(&self) == FiberErrorPermission, "no fiber on the thread"); + Require(sceFiberOptParamInitialize(nullptr) == FiberErrorNull, "null opt param"); + Require(sceFiberOptParamInitialize(reinterpret_cast(0x10)) == FiberErrorInvalid, "unwritable opt param"); + FiberOptParam optParam{}; + optParam.magic = 0x12345678; + Require(sceFiberOptParamInitialize(&optParam) == 0, "initialize opt param"); + Require(optParam.magic == 0, "opt param cleared"); Require(_sceFiberInitializeImpl_nid_postfix(g_first, "first", FirstEntry, 11, g_firstContext.data(), g_firstContext.size(), nullptr, 0) == 0, "initialize first"); Require(_sceFiberInitializeImpl_nid_postfix(g_second, "second", SecondEntry, 22, g_secondContext.data(), g_secondContext.size(), nullptr, 0) == 0, "initialize second"); const auto threadCsr = _mm_getcsr(); diff --git a/docs/dev/debt/libSceFiber.md b/docs/dev/debt/libSceFiber.md index da142ae02b..14aca4f493 100644 --- a/docs/dev/debt/libSceFiber.md +++ b/docs/dev/debt/libSceFiber.md @@ -2,6 +2,7 @@ ### Functional +- [sceFiberOptParamInitialize](../../../core/libs/prx/libSceFiber/Export.cpp) returns SCE_FIBER_ERROR_INVALID when the destination pointer is unwritable. This is a defensive host-protection fallback; whether the console raises a signal or faults on unmapped or read-only memory is not measured. - [sceFiberGetInfo](../../../core/libs/prx/libSceFiber/Export.cpp) reports the requested-size window at the top of the host stack used for execution, rather than the original caller buffer, which is retained and pinned. The additional 1 MiB host-call margin below that window is not included in `size_context`. Planet Coaster (PPSA01736 v01.017) uses this range for ChakraCore garbage-collector stack scans; reporting the unused caller buffer makes the scan cross unmapped memory. The range is covered by a regression test while running, switching and migrating a fiber between threads. Console behavior for substituted host stacks has no direct equivalent and is not measured. ### Unknown function info From 19e7101e4a9330caf032553ae6921d6e1118f5e4 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 13:14:53 +0530 Subject: [PATCH 243/612] fix(libSceRandom): return INVALID from sceRandomGetRandomNumber for an unwritable buffer --- core/libs/prx/libSceRandom/Export.cpp | 10 +++++++++- core/libs/tests/GuestRandom.cpp | 1 + docs/dev/debt/libSceRandom.md | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceRandom/Export.cpp b/core/libs/prx/libSceRandom/Export.cpp index 2fcda9cfb5..45c7949e54 100644 --- a/core/libs/prx/libSceRandom/Export.cpp +++ b/core/libs/prx/libSceRandom/Export.cpp @@ -4,6 +4,7 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/HostEntropy.hpp" +#include namespace { @@ -16,7 +17,14 @@ extern "C" { int APS5_VABI sceRandomGetRandomNumber(void* buf, size_t size) { if (!buf || size > SCE_RANDOM_MAX_SIZE) return SCE_RANDOM_ERROR_INVALID; - HostEntropy_nid_no_patch(buf, size); + std::uint8_t temp[SCE_RANDOM_MAX_SIZE]; + HostEntropy_nid_no_patch(temp, size); + const auto preserved = errno; + if (!GuestCopyOut_nid_no_patch(buf, temp, size)) { + errno = preserved; + return SCE_RANDOM_ERROR_INVALID; + } + errno = preserved; return 0; } diff --git a/core/libs/tests/GuestRandom.cpp b/core/libs/tests/GuestRandom.cpp index 661667f77f..e677394f5c 100644 --- a/core/libs/tests/GuestRandom.cpp +++ b/core/libs/tests/GuestRandom.cpp @@ -10,6 +10,7 @@ int main() { constexpr int invalid = static_cast(0x817C0016); unsigned char buffer[80]; Require(sceRandomGetRandomNumber(nullptr, 16) == invalid); + Require(sceRandomGetRandomNumber(reinterpret_cast(0x10), 16) == invalid); Require(sceRandomGetRandomNumber(nullptr, 0) == invalid); Require(sceRandomGetRandomNumber(nullptr, 64) == invalid); Require(sceRandomGetRandomNumber(nullptr, 65) == invalid); diff --git a/docs/dev/debt/libSceRandom.md b/docs/dev/debt/libSceRandom.md index 63684ae4b1..889fdd2b1e 100644 --- a/docs/dev/debt/libSceRandom.md +++ b/docs/dev/debt/libSceRandom.md @@ -2,4 +2,4 @@ ### Functional -- [sceRandomGetRandomNumber](../../../core/libs/prx/libSceRandom/Export.cpp) (libSceRandom) - a null buffer returns `SCE_RANDOM_ERROR_INVALID` (0x817C0016) even when the size is 0; that is an assumption, not measured on hardware. shadPS4 (PS4, `src/core/libraries/random/random.cpp`, commit 0614ce324be092791b758d79f93d9f1ca5b10a81) does not check the buffer, and KytyPS5 (PS5, `src/libs/libNet.cpp`, commit 5e837a9beac82c031868eaad9348d6a48245fb99) rejects a null buffer only for a size above 0, so both return success for a null buffer of size 0. fpPS4 (PS4, `src/ps4_libscerandom.pas`, commit 04cefd43e6fddd1ab033e7980cd356d14c964905) rejects it, with `EINVAL` (22) where this library returns the SCE code. +- [sceRandomGetRandomNumber](../../../core/libs/prx/libSceRandom/Export.cpp) (libSceRandom) - a null buffer returns `SCE_RANDOM_ERROR_INVALID` (0x817C0016) even when the size is 0; that is an assumption, not measured on hardware. shadPS4 (PS4, `src/core/libraries/random/random.cpp`, commit 0614ce324be092791b758d79f93d9f1ca5b10a81) does not check the buffer, and KytyPS5 (PS5, `src/libs/libNet.cpp`, commit 5e837a9beac82c031868eaad9348d6a48245fb99) rejects a null buffer only for a size above 0, so both return success for a null buffer of size 0. fpPS4 (PS4, `src/ps4_libscerandom.pas`, commit 04cefd43e6fddd1ab033e7980cd356d14c964905) rejects it, with `EINVAL` (22) where this library returns the SCE code. An unwritable buffer pointer also returns SCE_RANDOM_ERROR_INVALID as a defensive host-protection fallback; console behavior on unmapped or read-only memory is not measured. From aa3af8f736fd8c822e426d4a84ed475bcfb03e05 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 14:08:47 +0530 Subject: [PATCH 244/612] fix(libkernel_sys): return EFAULT on unwritable mode pointer in sceKernelGetCpumodeGame --- core/libs/prx/libkernel_sys/Export.cpp | 3 ++- core/libs/tests/GuestKernelSysCpumode.cpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index c528908a6b..2b069f18cb 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -385,7 +385,8 @@ int APS5_VABI sceKernelGetOpenPsIdForSystem(void* id) { int APS5_VABI sceKernelGetCpumodeGame(int* mode) { if (mode == nullptr) throw std::runtime_error("sceKernelGetCpumodeGame: a null output faults on the console"); - *mode = g_gameCpumode.load(std::memory_order_relaxed); + const int val = g_gameCpumode.load(std::memory_order_relaxed); + if (!GuestCopyOut_nid_no_patch(mode, &val, sizeof(val))) return SCE_KERNEL_ERROR_EFAULT; return 0; } diff --git a/core/libs/tests/GuestKernelSysCpumode.cpp b/core/libs/tests/GuestKernelSysCpumode.cpp index 3c5cb4fd65..76d4db0923 100644 --- a/core/libs/tests/GuestKernelSysCpumode.cpp +++ b/core/libs/tests/GuestKernelSysCpumode.cpp @@ -37,6 +37,7 @@ int main() { Require(Throws([] { sceKernelSetCpumodeGame(8); }), "an unmeasured mode did not throw"); Require(Throws([] { sceKernelSetCpumodeGame(-1); }), "a negative mode did not throw"); Require(Throws([] { sceKernelGetCpumodeGame(nullptr); }), "a null output did not throw"); + Require(sceKernelGetCpumodeGame(reinterpret_cast(0x10)) == static_cast(0x8002000Eu), "unwritable mode did not return EFAULT"); std::puts("game CPU mode tests passed"); return 0; } From 1bb556c15460f49d73d56fe2fab237d4e1b7753c Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 14:10:17 +0530 Subject: [PATCH 245/612] fix(libkernel_sys): use copied path in openat, mkdirat, fchmodat, fstatat and unlinkat --- core/libs/prx/libkernel_sys/Export.cpp | 28 +++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 2b069f18cb..0f8dc1d6d5 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -892,8 +892,8 @@ int APS5_VABI openat_nid_postfix(int fd, const char* path, int flags, ...) { if (File::InvalidOpenFlags(flags)) return static_cast(Fail(GuestEinval)); std::string copy; if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); - if (fd == GuestAtFdcwd || *path == '/') return open_nid_postfix(path, flags, mode); - const int result = OpenInDirectory_nid_no_patch(fd, path, flags, static_cast(mode)); + if (fd == GuestAtFdcwd || copy[0] == '/') return open_nid_postfix(copy.c_str(), flags, mode); + const int result = OpenInDirectory_nid_no_patch(fd, copy.c_str(), flags, static_cast(mode)); if (result < 0) return static_cast(Fail(result & 0xffff)); return result; } @@ -920,13 +920,13 @@ int APS5_VABI _openat_nid_postfix(int fd, const char* path, int flags, ...) { int APS5_VABI mkdirat_nid_postfix(int fd, const char* path, std::uint16_t mode) { std::string copy; if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); - if (fd == GuestAtFdcwd || *path == '/') return mkdir_nid_postfix(path, mode); - if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return static_cast(Fail(GuestEnametoolong)); + if (fd == GuestAtFdcwd || copy[0] == '/') return mkdir_nid_postfix(copy.c_str(), mode); + if (copy.size() >= GuestPathMax || HasLongComponent(copy.c_str())) return static_cast(Fail(GuestEnametoolong)); int error = 0; const char* base = GetDirectoryDescriptorPath_nid_no_patch(fd, &error); if (base == nullptr) return static_cast(Fail(error)); auto native = std::filesystem::path(reinterpret_cast(base)) / - std::filesystem::path(reinterpret_cast(path)); + std::filesystem::path(reinterpret_cast(copy.c_str())); while (!native.has_filename() && native.has_relative_path()) native = native.parent_path(); #ifdef _WIN32 if (!CreateDirectoryW(native.wstring().c_str(), nullptr)) { @@ -958,13 +958,13 @@ int APS5_VABI fchmodat_nid_postfix(int fd, const char* path, int mode, int flag) std::string copy; if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); std::filesystem::path native; - if (fd == GuestAtFdcwd || *path == '/') { - if (const int error = ResolvePathError_nid_no_patch(path, native)) return static_cast(Fail(error)); + if (fd == GuestAtFdcwd || copy[0] == '/') { + if (const int error = ResolvePathError_nid_no_patch(copy.c_str(), native)) return static_cast(Fail(error)); } else { int error = 0; const char* base = GetDirectoryDescriptorPath_nid_no_patch(fd, &error); if (base == nullptr) return static_cast(Fail(error)); - native = std::filesystem::path(reinterpret_cast(base)) / std::filesystem::path(reinterpret_cast(path)); + native = std::filesystem::path(reinterpret_cast(base)) / std::filesystem::path(reinterpret_cast(copy.c_str())); } if (flag == GuestAtSymlinkNofollow) { #ifdef _WIN32 @@ -1056,8 +1056,8 @@ int APS5_VABI fstatat_nid_postfix(int fd, const char* path, FileStat* sb, int fl if (flag & ~GuestAtSymlinkNofollow) return static_cast(Fail(GuestEinval)); std::string copy; if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); - if (fd == GuestAtFdcwd || *path == '/') return flag ? lstat_nid_postfix(path, sb) : stat_nid_postfix(path, sb); - const int result = StatInDirectory_nid_no_patch(fd, path, sb, flag == 0); + if (fd == GuestAtFdcwd || copy[0] == '/') return flag ? lstat_nid_postfix(copy.c_str(), sb) : stat_nid_postfix(copy.c_str(), sb); + const int result = StatInDirectory_nid_no_patch(fd, copy.c_str(), sb, flag == 0); if (result < 0) return static_cast(Fail(result & 0xffff)); return 0; } @@ -1072,18 +1072,18 @@ int APS5_VABI unlinkat_nid_postfix(int fd, const char* path, int flag) { if (flag & ~GuestAtRemovedir) return static_cast(Fail(GuestEinval)); std::string copy; if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); - if (fd == GuestAtFdcwd || *path == '/') return (flag & GuestAtRemovedir) ? rmdir_nid_postfix(path) : unlink_nid_postfix(path); - if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return static_cast(Fail(GuestEnametoolong)); + if (fd == GuestAtFdcwd || copy[0] == '/') return (flag & GuestAtRemovedir) ? rmdir_nid_postfix(copy.c_str()) : unlink_nid_postfix(copy.c_str()); + if (copy.size() >= GuestPathMax || HasLongComponent(copy.c_str())) return static_cast(Fail(GuestEnametoolong)); int error = 0; const char* base = GetDirectoryDescriptorPath_nid_no_patch(fd, &error); if (base == nullptr) return static_cast(Fail(error)); auto native = std::filesystem::path(reinterpret_cast(base)) / - std::filesystem::path(reinterpret_cast(path)); + std::filesystem::path(reinterpret_cast(copy.c_str())); const bool trailingSlash = !native.has_filename() && native.has_relative_path(); while (!native.has_filename() && native.has_relative_path()) native = native.parent_path(); std::error_code statusError; const auto symlinkStatus = std::filesystem::symlink_status(native, statusError); - if (IsDotOrDotDot(path)) return static_cast(Fail(GuestEinval)); + if (IsDotOrDotDot(copy.c_str())) return static_cast(Fail(GuestEinval)); if (flag & GuestAtRemovedir) { if (std::filesystem::is_symlink(symlinkStatus)) return static_cast(Fail(GuestEnotdir)); #ifdef _WIN32 From e380873dc3afc23e37c1db4c29c6d8064d3ebce4 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 14:11:04 +0530 Subject: [PATCH 246/612] fix(libkernel_sys): use copied path in chroot, pathconf and readlinkat --- core/libs/prx/libkernel_sys/Export.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 0f8dc1d6d5..60111d82e6 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -567,20 +567,20 @@ std::int64_t APS5_VABI readlinkat_nid_postfix(int fd, const char* path, char* bu if (size > static_cast(std::numeric_limits::max())) return Fail(GuestEinval); std::string copy; if (const int error = CopyPath(path, copy)) return Fail(error); - if (fd == GuestAtFdcwd || *path == '/') return readlink_nid_postfix(path, buffer, size); + if (fd == GuestAtFdcwd || copy[0] == '/') return readlink_nid_postfix(copy.c_str(), buffer, size); int error = 0; const char* base = GetDirectoryDescriptorPath_nid_no_patch(fd, &error); if (base == nullptr) return Fail(error); return ReadNativeLink(std::filesystem::path(reinterpret_cast(base)) / - std::filesystem::path(reinterpret_cast(path)), buffer, size); + std::filesystem::path(reinterpret_cast(copy.c_str())), buffer, size); } std::int64_t APS5_VABI pathconf_nid_postfix(const char* path, int name) { std::string copy; if (const int error = CopyPath(path, copy)) return Fail(error); - if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return Fail(GuestEnametoolong); + if (copy.size() >= GuestPathMax || HasLongComponent(copy.c_str())) return Fail(GuestEnametoolong); std::filesystem::path native; - if (const int error = ResolvePathError_nid_no_patch(path, native)) return Fail(error); + if (const int error = ResolvePathError_nid_no_patch(copy.c_str(), native)) return Fail(error); std::error_code error; const auto status = std::filesystem::status(native, error); if (error) return Fail(error == std::errc::no_such_file_or_directory && HasNonDirectoryPrefix(native) ? GuestEnotdir : GuestError(error)); @@ -755,9 +755,9 @@ int APS5_VABI settimeofday_nid_postfix(const KernelTimeval* tv, const KernelTime int APS5_VABI chroot_nid_postfix(const char* path) { std::string copy; if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); - if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return static_cast(Fail(GuestEnametoolong)); + if (copy.size() >= GuestPathMax || HasLongComponent(copy.c_str())) return static_cast(Fail(GuestEnametoolong)); std::filesystem::path native; - if (const int error = ResolvePathError_nid_no_patch(path, native)) return static_cast(Fail(error)); + if (const int error = ResolvePathError_nid_no_patch(copy.c_str(), native)) return static_cast(Fail(error)); std::error_code error; const auto status = std::filesystem::status(native, error); if (status.type() == std::filesystem::file_type::not_found) return static_cast(Fail(HasNonDirectoryPrefix(native) ? GuestEnotdir : GuestEnoent)); From 79668d55d0cbc0cbeb00ad88e7ddd2bdd8388e85 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 14:12:28 +0530 Subject: [PATCH 247/612] fix(libkernel_sys): use copied paths in renameat --- core/libs/prx/libkernel_sys/Export.cpp | 28 +++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 60111d82e6..b822f3c792 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -1167,23 +1167,23 @@ int APS5_VABI scePthreadCondattrGetclock(const PthreadCondattr* attr, KernelCloc int APS5_VABI renameat_nid_postfix(int oldfd, const char* from, int newfd, const char* to) { std::string fromCopy, toCopy; if (const int error = CopyPath(from, fromCopy)) return static_cast(Fail(error)); - if ((oldfd == GuestAtFdcwd || *from == '/') && newfd == GuestAtFdcwd) - return RenameGuestPath_nid_no_patch(from, to); - if (std::strlen(from) >= GuestPathMax || HasLongComponent(from)) + if ((oldfd == GuestAtFdcwd || fromCopy[0] == '/') && newfd == GuestAtFdcwd) + return RenameGuestPath_nid_no_patch(fromCopy.c_str(), to); + if (fromCopy.size() >= GuestPathMax || HasLongComponent(fromCopy.c_str())) return static_cast(Fail(GuestEnametoolong)); - if (IsDotOrDotDot(from)) return static_cast(Fail(GuestEinval)); + if (IsDotOrDotDot(fromCopy.c_str())) return static_cast(Fail(GuestEinval)); std::filesystem::path source; - if (oldfd == GuestAtFdcwd || *from == '/') { - if (const int error = ResolvePathError_nid_no_patch(from, source)) return static_cast(Fail(error)); + if (oldfd == GuestAtFdcwd || fromCopy[0] == '/') { + if (const int error = ResolvePathError_nid_no_patch(fromCopy.c_str(), source)) return static_cast(Fail(error)); } else { int error = 0; const char* base = GetDirectoryDescriptorPath_nid_no_patch(oldfd, &error); if (base == nullptr) return static_cast(Fail(error)); source = std::filesystem::path(reinterpret_cast(base)) / - std::filesystem::path(reinterpret_cast(from)); + std::filesystem::path(reinterpret_cast(fromCopy.c_str())); } - const std::string_view fromView(from); + const std::string_view fromView(fromCopy); const bool oldTrailingSlash = !fromView.empty() && fromView.back() == '/'; while (!source.has_filename() && source.has_relative_path()) source = source.parent_path(); std::error_code statusError; @@ -1195,21 +1195,21 @@ int APS5_VABI renameat_nid_postfix(int oldfd, const char* from, int newfd, const if (oldTrailingSlash && !std::filesystem::is_directory(sourceStatus)) return static_cast(Fail(GuestEnotdir)); if (const int error = CopyPath(to, toCopy)) return static_cast(Fail(error)); - if (std::strlen(to) >= GuestPathMax || HasLongComponent(to)) return static_cast(Fail(GuestEnametoolong)); - if (IsDotOrDotDot(to)) return static_cast(Fail(GuestEinval)); + if (toCopy.size() >= GuestPathMax || HasLongComponent(toCopy.c_str())) return static_cast(Fail(GuestEnametoolong)); + if (IsDotOrDotDot(toCopy.c_str())) return static_cast(Fail(GuestEinval)); std::filesystem::path destination; - if (newfd == GuestAtFdcwd || *to == '/') { - if (const int error = ResolvePathError_nid_no_patch(to, destination)) return static_cast(Fail(error)); + if (newfd == GuestAtFdcwd || toCopy[0] == '/') { + if (const int error = ResolvePathError_nid_no_patch(toCopy.c_str(), destination)) return static_cast(Fail(error)); } else { int error = 0; const char* base = GetDirectoryDescriptorPath_nid_no_patch(newfd, &error); if (base == nullptr) return static_cast(Fail(error)); destination = std::filesystem::path(reinterpret_cast(base)) / - std::filesystem::path(reinterpret_cast(to)); + std::filesystem::path(reinterpret_cast(toCopy.c_str())); } - const std::string_view toView(to); + const std::string_view toView(toCopy); const bool newTrailingSlash = !toView.empty() && toView.back() == '/'; while (!destination.has_filename() && destination.has_relative_path()) destination = destination.parent_path(); From 9306b1bab1fff697210ccc23fe0d7f7ae9234c9b Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 14:13:42 +0530 Subject: [PATCH 248/612] fix(libkernel_sys): copy module name safely in sceKernelIsModuleLoaded --- core/libs/prx/libkernel_sys/Export.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index b822f3c792..db3758069d 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -539,13 +539,16 @@ KernelModule APS5_VABI sceKernelLoadStartModuleForSysmodule(const char* moduleFi int APS5_VABI sceKernelIsModuleLoaded(const char* name) { if (!name) throw std::invalid_argument("sceKernelIsModuleLoaded: null name"); + std::string copy; + const int copied = CopyGuestString_nid_no_patch(name, 256, copy); + if (copied != 0) return SCE_KERNEL_ERROR_EFAULT; KernelModule handles[0x300]; std::size_t count = 0; if (const int result = sceKernelGetModuleListInternal(handles, std::size(handles), &count)) return result; for (std::size_t i = 0; i < count; ++i) { ModuleInfo info{}; info.st_size = sizeof(info); - if (sceKernelGetModuleInfo(handles[i], &info) == 0 && std::strncmp(info.name, name, sizeof(info.name)) == 0) return 0; + if (sceKernelGetModuleInfo(handles[i], &info) == 0 && std::strncmp(info.name, copy.c_str(), sizeof(info.name)) == 0) return 0; } return SCE_KERNEL_ERROR_ESRCH; } From 1534d98ea001854f98d9f548fe2542e3cc14c178 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 14:14:55 +0530 Subject: [PATCH 249/612] fix(libkernel_sys): validate attribute and clock pointer in scePthreadCondattrGetclock --- core/libs/prx/libkernel_sys/Export.cpp | 7 +++++-- docs/dev/debt/libkernel_sys.md | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index db3758069d..db691dc2c6 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -1161,9 +1161,12 @@ int APS5_VABI unlinkat_nid_postfix(int fd, const char* path, int flag) { } int APS5_VABI scePthreadCondattrGetclock(const PthreadCondattr* attr, KernelClockid* clockId) { - if (!attr || !*attr) return SCE_KERNEL_ERROR_EINVAL; + if (!attr) return SCE_KERNEL_ERROR_EINVAL; + PthreadCondattr localAttr{}; + if (!GuestCopyIn_nid_no_patch(&localAttr, attr, sizeof(localAttr)) || !localAttr) return SCE_KERNEL_ERROR_EINVAL; if (!clockId) throw std::invalid_argument("scePthreadCondattrGetclock: null clock id"); - *clockId = (*attr)->_clockid; + const auto val = localAttr->_clockid; + if (!GuestCopyOut_nid_no_patch(clockId, &val, sizeof(val))) return SCE_KERNEL_ERROR_EFAULT; return 0; } diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index fb55223f5e..d95e2ebc0b 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -339,7 +339,7 @@ - [sceKernelGetHwModelName](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - throws. On a CFI-2014 PS5 (firmware 13.60) a payload process got 0 from `sceKernelGetHwModelName(char*)`, which wrote "CFI-2014 A01X" and a terminator (14 bytes). The model string belongs to one console and the buffer size and null behaviour are not known, so it is not implemented - [sceKernelGetCompiledSdkVersion](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - which SDK version the console returns is unmeasured. The [PS5 payload SDK](https://github.com/ps5-payload-dev/sdk/blob/a7685ce8e97ce1f8a4f38253a41d54241c9124f1/crt/kernel.c#L257-L275) declares the process parameters with a 32-bit PS4 SDK version at offset `0x10` and a 32-bit PS5 SDK version at `0x14`; this export returns the field at `0x10`, which [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) reads and [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/615bcba5e21b82dc59588575f7ecbce8da09ca2c/src/emulator.cpp#L110-L138) reads from the eboot. The `int (int32_t*)` signature and `EINVAL` (`0x80020016`) for null follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/615bcba5e21b82dc59588575f7ecbce8da09ca2c/src/core/libraries/kernel/process.cpp#L56-L62); `SCE_KERNEL_ERROR_EFAULT` for an unwritable output pointer is a host-safety fallback so the process does not crash, not a measured PS5 result, and the console's behaviour for such a pointer is unmeasured. Closes when: measured on a PS5 -- [scePthreadCondattrGetclock](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - a null clock pointer with a valid attribute throws `std::invalid_argument`, where a payload process measured on a device faults. A null or destroyed attribute with a null output returns `SCE_KERNEL_ERROR_EINVAL` (`0x80020016`) because [FreeBSD 11.4 libthr](https://github.com/freebsd/freebsd-src/blob/d106002967/lib/libthr/thread/thr_condattr.c#L82-L90) checks the attribute first; that pair was not measured. Inaccessible non-null pointers and stale handles are not validated +- [scePthreadCondattrGetclock](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - a null clock pointer with a valid attribute throws `std::invalid_argument`, where a payload process measured on a device faults. A null or destroyed attribute with a null output returns `SCE_KERNEL_ERROR_EINVAL` (`0x80020016`) because [FreeBSD 11.4 libthr](https://github.com/freebsd/freebsd-src/blob/d106002967/lib/libthr/thread/thr_condattr.c#L82-L90) checks the attribute first; that pair was not measured. an unwritable clock pointer returns `SCE_KERNEL_ERROR_EFAULT` (`0x8002000E`). Stale handles are not validated ### Functional From 73f2c0a2df08575aded9aa26ff305dce1c1bed07 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 14:16:17 +0530 Subject: [PATCH 250/612] fix(libkernel_sys): validate attribute pointer in pthread_attr_setcreatesuspend_np --- core/libs/prx/libkernel_sys/Export.Discovery.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index bfaa0bbeb2..fa4779c39d 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -236,8 +236,10 @@ int APS5_VABI pthread_attr_setaffinity_np_nid_postfix(void) { int APS5_VABI pthread_attr_setcreatesuspend_np_nid_postfix(PthreadAttr* attr) { constexpr int GuestEinval = 22; - if (!attr || !*attr) return GuestEinval; - (*attr)->_createSuspended = true; + if (!attr) return GuestEinval; + PthreadAttr localAttr{}; + if (!GuestCopyIn_nid_no_patch(&localAttr, attr, sizeof(localAttr)) || !localAttr) return GuestEinval; + localAttr->_createSuspended = true; return 0; } From 011297576e9eb5a40b6f5cdecef4a62e1a5dd154 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 14:17:03 +0530 Subject: [PATCH 251/612] fix(libkernel_sys): validate attribute and generation pointers in pthread_mutexattr generation exports --- core/libs/prx/libkernel_sys/Export.Discovery.cpp | 13 +++++++++---- docs/dev/debt/libkernel_sys.md | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index fa4779c39d..3e594955ba 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -284,15 +284,20 @@ int APS5_VABI pthread_mutex_setprioceiling_nid_postfix(void) { } int APS5_VABI pthread_mutexattr_getgen_np_nid_postfix(const PthreadMutexattr* attr, int* generation) { - if (!attr || !*attr || !generation) return PosixThread::GUEST_EINVAL; - *generation = (*attr)->generation & 1; + if (!attr || !generation) return PosixThread::GUEST_EINVAL; + PthreadMutexattr localAttr{}; + if (!GuestCopyIn_nid_no_patch(&localAttr, attr, sizeof(localAttr)) || !localAttr) return PosixThread::GUEST_EINVAL; + const int val = localAttr->generation & 1; + if (!GuestCopyOut_nid_no_patch(generation, &val, sizeof(val))) return PosixThread::GUEST_EFAULT; return 0; } int APS5_VABI pthread_mutexattr_setgen_np_nid_postfix(PthreadMutexattr* attr, int generation) { - if (!attr || !*attr) return PosixThread::GUEST_EINVAL; + if (!attr) return PosixThread::GUEST_EINVAL; + PthreadMutexattr localAttr{}; + if (!GuestCopyIn_nid_no_patch(&localAttr, attr, sizeof(localAttr)) || !localAttr) return PosixThread::GUEST_EINVAL; if (generation != 0 && generation != 1) return PosixThread::GUEST_ENOTSUP; - (*attr)->generation = generation; + localAttr->generation = generation; return 0; } diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index d95e2ebc0b..bdc74827b5 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -379,7 +379,7 @@ - [sceKernelIsNeoMode](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - not measured on a console, and the PS5 module's body is not read. A title with `param.json` throws: what a PS5 application gets is unknown. For a title with `param.sfo` it returns bit 23 (0x00800000) of `ATTRIBUTE`, the title's Neo-mode support, and 0 without the key, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/3eecea94ddaca360b19f4b800523b609a8b2ffcc/src/core/libraries/kernel/process.cpp#L29-L35) answers on a Neo console ([bit 23](https://github.com/shadps4-emu/shadPS4/blob/3eecea94ddaca360b19f4b800523b609a8b2ffcc/src/common/elf_info.h#L50-L51)), which `sceKernelHasNeoMode` and `sceKernelIsAuthenticNeo` already report. A base PS4 (firmware 10.71) answers 0 ([discussions/7941](https://github.com/boykopovar/AnyPS5/discussions/7941)) - [linkat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) resolves `AT_FDCWD` (-100) and absolute paths as `link` does, and flags other than `AT_SYMLINK_FOLLOW` (0x400) return `EINVAL`, as in FreeBSD 11. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Without `AT_SYMLINK_FOLLOW` a symbolic link as the source is linked itself, as in FreeBSD: on Linux and macOS through the host's `linkat` with flag 0, and on Windows through `CreateHardLinkW`, which links the link's reparse point rather than its target, dangling or not. Windows cannot hard-link a directory, so there a symbolic link or junction to a directory as the source throws. Console behaviour is unverified - [pthread_set_defaultstacksize_np and scePthreadSetDefaultstacksize](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) set the stack size that `scePthreadAttrInit` and a thread created without attributes use, as [fpPS4's `pthread_set_defaultstacksize_np`](https://github.com/red-prig/fpPS4/blob/04cefd4/kernel/ps4_pthread_attr.pas#L89-L102) does: a size of 16384 bytes or less fails with `EINVAL` (`0x80020016` from the sce form) and there is no upper limit, so a default the host cannot allocate fails only when a thread is created. Attributes initialised earlier keep their size. Not checked on a PS5 -- [pthread_mutexattr_getgen_np, pthread_mutexattr_setgen_np, scePthreadMutexattrGetgen and scePthreadMutexattrSetgen](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) were reconstructed from the firmware 13.60 exports: generation is a one-bit attribute, set accepts only 0 or 1 and otherwise returns `EOPNOTSUPP`, and the `sce` forms convert the POSIX error to `0x80020000 | errno`. The flag is retained in AnyPS5's attribute state; whether it changes mutex initialization is unmeasured. Runtime behaviour on a PS5 is not independently validated +- [pthread_mutexattr_getgen_np, pthread_mutexattr_setgen_np, scePthreadMutexattrGetgen and scePthreadMutexattrSetgen](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) were reconstructed from the firmware 13.60 exports: generation is a one-bit attribute, set accepts only 0 or 1 and otherwise returns `EOPNOTSUPP`, and the `sce` forms convert the POSIX error to `0x80020000 | errno`. The flag is retained in AnyPS5's attribute state; whether it changes mutex initialization is unmeasured. An unwritable generation pointer returns `EFAULT` (14). Runtime behaviour on a PS5 is not independently validated - [sceKernelTitleWorkaroundIsEnabled](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - the `{version, align, ids[2]}` layout, the bit test and the limit follow PS5 firmware 13.42 libkernel_sys and libkernel, read from disassembly and not measured on a console, where bits 0 to 0x7F are accepted, the bit is compared unsigned, and the misspelled export `sceKernelTitleWorkdaroundIsEnabled` runs the same code. Titles usually pass the `title_workaround` block of `sceKernelGetAppInfo`, which is written as zeros here; where `title_workaround` sits in the 0x58-byte structure is unmeasured, and with the shadPS4 offsets (0x30 to 0x48) it falls in the zeroed bytes, so its bits read 0; an unreadable workaround pointer or an unwritable output pointer returns `SCE_KERNEL_ERROR_EFAULT` (`0x8002000e`) as a host-safety fallback; console behaviour for unwritable memory has not been measured - [sceKernelGetProcessType](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - not measured on a console; as in shadPS4, the title's own pid, -1 and 0 are type 0 (big application) and any other pid is `ESRCH` - [cpuset, cpuset_getid, cpuset_setid](../../../core/libs/prx/libkernel_sys/Cpuset.cpp) (libkernel_sys) - console root IDs and inheritance are unverified; non-process scopes (TID, CPUSET, JAIL) throw as unmodelled as per-thread cpusets and hierarchy visibility are not tracked. CPU set assignment and queries follow FreeBSD 11.4 [kern_cpuset.c](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_cpuset.c): cpuset allocates sequential IDs from 2; cpuset_setid supports CPU_WHICH_PID for known IDs (or root 0, default 1); dynamic sets are single-process referenced and unlinked when vacated, returning ESRCH on lookup; cpuset_getid returns root 0 for CPU_LEVEL_ROOT and the assigned ID for CPU_LEVEL_CPUSET. Output writes use GuestCopyOut_nid_no_patch (EFAULT on bad pointers), with level/which/id errors returned before copyout; levels outside 1 to 3 return EINVAL where FreeBSD returns 0 with the base set ID; other which return EINVAL, unknown pids return ESRCH From 89c9495375770fef5598b2c04984537feb7c18e1 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 14:18:22 +0530 Subject: [PATCH 252/612] fix(libkernel_sys): copy mapper parameters safely in sceKernelMapperGetParam --- core/libs/prx/libkernel_sys/Export.Discovery.cpp | 8 +++++--- docs/dev/debt/libkernel_sys.md | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index 3e594955ba..44ac458820 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -1432,12 +1432,14 @@ int APS5_VABI sceKernelMapTraceMemory(void) { } int APS5_VABI sceKernelMapperGetParam(std::uint64_t* param) { - if (!param || *param > MapperParamBytes) return SCE_KERNEL_ERROR_EINVAL; - const auto bytes = *param; + if (!param) return SCE_KERNEL_ERROR_EINVAL; + std::uint64_t bytes = 0; + if (!GuestCopyIn_nid_no_patch(&bytes, param, sizeof(bytes))) return SCE_KERNEL_ERROR_EFAULT; + if (bytes > MapperParamBytes) return SCE_KERNEL_ERROR_EINVAL; if (bytes > MapperKnownBytes) NotImplemented_nid_no_patch(__func__); std::uint64_t values[5]{MapperParamBytes}; AmmVirtualAddressRanges_nid_no_patch(&values[1], &values[2], &values[3], &values[4]); - std::memcpy(param, values, static_cast(bytes)); + if (!GuestCopyOut_nid_no_patch(param, values, static_cast(bytes))) return SCE_KERNEL_ERROR_EFAULT; return 0; } diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index bdc74827b5..595008346d 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -394,7 +394,7 @@ - [AMM kernel write command checks](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - the map, map direct, unmap, remap, multimap and protection writers apply their libSceAmpr siblings' checks, not those of firmware 13.42's libkernel (compared under a CPU emulator, not measured on a console), so some arguments get a different return code. The console checks the range against the AMM virtual address ranges, accepts a size that wraps past the end of the address space, takes a different set of protection bits and, for maps and memory type changes, accepts only types 11 to 20. Where both accept a call, the recorded address, size, type and protection match - [sceKernelWriteMapCommand2, sceKernelWriteUnmapToPrtCommand, sceKernelWriteWriteAddressCommand, sceKernelWriteWriteCounterCommand, sceKernelWriteWaitOnAddressCommand, sceKernelWriteWaitOnCounterCommand and sceKernelWriteEventQueueOnCompletionCommand](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - they record this project's command layout, not the console's packed commands, so the reported sizes differ and the waits' flush bit is dropped. A PRT map with a non-zero type or protection and a recorded wait on an address that is not 8-byte aligned throw: their results are unknown. The checks follow firmware 13.42's libkernel and libkernel_sys, read from disassembly and compared under a CPU emulator, not measured on a console; without the PRT flag sceKernelWriteMapCommand2 is sceKernelWriteMapCommand. As in the firmware, the PRT range check accepts an end that wraps past the top of the address space, and only size bits 14 to 40 are recorded - [sceKernelWriteWaitOnAddressCommand and sceKernelWriteWaitOnCounterCommand](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - a negative compare passes the firmware's check (above 3 is refused) and is recorded as its low three bits, as in firmware 13.42, so -2 to -4 record modes 6 to 4. The command runner here (`_waitSatisfied` in `Apr.cpp`) implements modes 0 to 6, so a wait recorded with mode 7, for example from a compare of -1, is written successfully but throws when it runs -- [sceKernelCallIndirectBuffer, sceKernelCallIndirectBuffer2, sceKernelCallIndirectBuffer3, sceKernelGiveDirectMemoryToMapper and sceKernelMapperGetParam](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - follow firmware 13.42's libkernel_sys, read from disassembly and not measured on a console. The submit calls hand their arguments to a kernel ioctl, keeping 32 bits of the dword count, so the kernel's checks are unknown: a priority above 2, a null buffer or a dword count of 2^30 or more (the AMM queue here takes a 32-bit byte count) throws. sceKernelGiveDirectMemoryToMapper requires a non-zero size and a 2 MiB aligned size and alignment. sceKernelMapperGetParam copies up to 0x38 requested bytes of a block filled at startup: its size, the four AMM range values (firmware libSceAmpr reads them from there), then two unknown values, so a request past 0x28 bytes throws. The 0x80020006 returned when its device cannot be opened is not modelled +- [sceKernelCallIndirectBuffer, sceKernelCallIndirectBuffer2, sceKernelCallIndirectBuffer3, sceKernelGiveDirectMemoryToMapper and sceKernelMapperGetParam](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - follow firmware 13.42's libkernel_sys disassembly, not measured on a console. Submit calls forward to a kernel ioctl keeping 32 bits of dword count: priority > 2, null buffer or >= 2^30 dwords throws. sceKernelGiveDirectMemoryToMapper requires non-zero size and 2 MiB alignment. sceKernelMapperGetParam copies up to 0x38 bytes: size, four AMM ranges and two unknown values; requests past 0x28 bytes throw. An unwritable or unreadable pointer returns `SCE_KERNEL_ERROR_EFAULT` (`0x8002000E`). Device open failure returning 0x80020006 is not modelled - [sceKernelWaitCommandBufferCompletion](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - signature taken from [ampr_emu's libkernel hook targets](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/include/ampr_libkernel_hook_targets.h), where `sceAmprAmmWaitCommandBufferCompletion` forwards to it; like that sibling it waits for an AMM submission and returns `SCE_KERNEL_ERROR_ESRCH` for an id that was never submitted, not confirmed on a title - [kldsym](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) follows FreeBSD 11.4's [`sys_kldsym`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_linker.c) in the order it checks its arguments: `EFAULT` for an unreadable lookup structure, `EINVAL` for a `version` other than the structure size (32 bytes on amd64) or a command other than `KLDSYM_LOOKUP`, `EFAULT` or `ENAMETOOLONG` for the symbol name, `ENOENT` for a file id that no file has. The linker file list is empty here, as in `kldnext` and `kldfind`, so every file id other than 0 gets `ENOENT`; modules a console preloads are not modelled. A lookup in id 0 (every file) throws, as no symbol table is modelled. Whether a title may query kernel symbols is unverified From 5153d45acbd6434ec8ebdbeeedb12925d56bce7a Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 14:25:42 +0530 Subject: [PATCH 253/612] test(libkernel_sys): cover pointer validation in pthread attributes and mutexattr generation --- core/libs/tests/GuestKernelSysPthread.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/libs/tests/GuestKernelSysPthread.cpp b/core/libs/tests/GuestKernelSysPthread.cpp index 0341955141..73e113d32f 100644 --- a/core/libs/tests/GuestKernelSysPthread.cpp +++ b/core/libs/tests/GuestKernelSysPthread.cpp @@ -55,9 +55,24 @@ static void TestMutexAttributeGeneration() { Require(scePthreadMutexattrGetgen(&attr, &generation) == 0 && generation == 0); Require(scePthreadMutexattrSetgen(&attr, 2) == SCE_KERNEL_ERROR_EOPNOTSUPP); Require(scePthreadMutexattrGetgen(nullptr, &generation) == SCE_KERNEL_ERROR_EINVAL); + Require(pthread_mutexattr_getgen_np_nid_postfix(&attr, reinterpret_cast(0x10)) == 14); + Require(pthread_mutexattr_getgen_np_nid_postfix(reinterpret_cast(0x10), &generation) == 22); + Require(pthread_mutexattr_setgen_np_nid_postfix(reinterpret_cast(0x10), 0) == 22); + Require(scePthreadMutexattrGetgen(&attr, reinterpret_cast(0x10)) == static_cast(0x80020000u | 14u)); + Require(scePthreadMutexattrGetgen(reinterpret_cast(0x10), &generation) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrSetgen(reinterpret_cast(0x10), 0) == SCE_KERNEL_ERROR_EINVAL); Require(pthread_mutexattr_destroy_nid_postfix(&attr) == 0); } +static void TestThreadAttrCreateSuspend() { + Require(pthread_attr_setcreatesuspend_np_nid_postfix(nullptr) == 22); + Require(pthread_attr_setcreatesuspend_np_nid_postfix(reinterpret_cast(0x10)) == 22); + PthreadAttr attr{}; + Require(scePthreadAttrInit(&attr) == 0); + Require(pthread_attr_setcreatesuspend_np_nid_postfix(&attr) == 0); + Require(scePthreadAttrDestroy(&attr) == 0); +} + static void TestConcurrency() { Require(scePthreadSetconcurrency(3) == 0 && scePthreadGetconcurrency() == 3); Require(scePthreadSetconcurrency(-1) == SCE_KERNEL_ERROR_EINVAL && scePthreadGetconcurrency() == 3); @@ -77,6 +92,7 @@ static void TestAtfork() { int main() { TestScope(); TestMutexAttributeGeneration(); + TestThreadAttrCreateSuspend(); TestConcurrency(); TestAtfork(); return 0; From d5935a53cef33e853b9d52235b486a5cebfbd1b7 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 14:26:02 +0530 Subject: [PATCH 254/612] test(libkernel_sys): cover pointer validation in sceKernelIsModuleLoaded --- core/libs/tests/GuestKernelSysModuleInternal.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/core/libs/tests/GuestKernelSysModuleInternal.cpp b/core/libs/tests/GuestKernelSysModuleInternal.cpp index 84dc88f295..a44415d8da 100644 --- a/core/libs/tests/GuestKernelSysModuleInternal.cpp +++ b/core/libs/tests/GuestKernelSysModuleInternal.cpp @@ -104,6 +104,7 @@ static void CheckIsModuleLoaded() { } Require(sceKernelIsModuleLoaded("libSceNoSuchModule.sprx") == SCE_KERNEL_ERROR_ESRCH); Require(sceKernelIsModuleLoaded("") == expected("")); + Require(sceKernelIsModuleLoaded(reinterpret_cast(0x10)) == SCE_KERNEL_ERROR_EFAULT); } int main() { From 01252bf614944b2ec8c4bd8ccf65886de7cfe414 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 14:27:32 +0530 Subject: [PATCH 255/612] test(libkernel_sys): cover pointer validation in chroot, condattr and mapper param --- core/libs/tests/GuestKernelSys.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index 339c1e986e..25cb344ec5 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -306,6 +306,7 @@ void CheckUnreadablePaths(const std::string& renameSource, int directory) { posixFault([&] { return lutimes_nid_postfix(paths.Unreadable(), nullptr); }); posixFault([&] { return readlink_nid_postfix(paths.Unreadable(), linkBuffer, sizeof(linkBuffer)); }); posixFault([&] { return pathconf_nid_postfix(paths.Unreadable(), 4); }); + posixFault([&] { return chroot_nid_postfix(paths.Unreadable()); }); posixFault([&] { return link_nid_postfix(paths.Unreadable(), "unused-link"); }); posixFault([&] { return link_nid_postfix(renameSource.c_str(), paths.Unreadable()); }); posixFault([&] { return symlink_nid_postfix(paths.Unreadable(), "unused-symlink"); }); @@ -489,10 +490,12 @@ void CheckCondattrClock() { Require(scePthreadCondattrGetclock(nullptr, &clock) == SCE_KERNEL_ERROR_EINVAL && clock == -1); Require(scePthreadCondattrGetclock(nullptr, nullptr) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadCondattrGetclock(&attr, &clock) == SCE_KERNEL_ERROR_EINVAL && clock == -1); + Require(scePthreadCondattrGetclock(reinterpret_cast(0x10), &clock) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadCondattrInit(&attr) == 0 && attr != nullptr); Require(scePthreadCondattrGetclock(&attr, &clock) == 0 && clock == Realtime); Require(scePthreadCondattrSetclock(&attr, Monotonic) == 0); Require(scePthreadCondattrGetclock(&attr, &clock) == 0 && clock == Monotonic); + Require(scePthreadCondattrGetclock(&attr, reinterpret_cast(0x10)) == SCE_KERNEL_ERROR_EFAULT); bool getclockThrew = false; try { scePthreadCondattrGetclock(&attr, nullptr); } catch (const std::invalid_argument&) { getclockThrew = true; } Require(getclockThrew); @@ -734,6 +737,7 @@ void CheckAmmMapperCalls() { std::array param{}; param.fill(0xAAAAAAAAAAAAAAAAull); Require(sceKernelMapperGetParam(nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelMapperGetParam(reinterpret_cast(0x10)) == SCE_KERNEL_ERROR_EFAULT); param[0] = 0x39; Require(sceKernelMapperGetParam(param.data()) == SCE_KERNEL_ERROR_EINVAL && param[0] == 0x39 && param[1] == 0xAAAAAAAAAAAAAAAAull); param[0] = 0x28; From 39753d71a9b2dad80bb58e3be243ec6a9ccf832e Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Mon, 2 Nov 2026 01:17:13 -0800 Subject: [PATCH 256/612] docs(debt): recount libSceLibreSsl's pending exports after merging main main's count, 287, was three above its own stubs (284); this branch now leaves 278 exports throwing. --- docs/dev/debt/libSceLibreSsl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 1d2ef9c20e..9797a3f069 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 279 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 278 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. From 5b6175da9bff40f234381ef992e21d2fa97da01b Mon Sep 17 00:00:00 2001 From: xXPBSXx <20614131+xXPBSXx@users.noreply.github.com> Date: Mon, 2 Nov 2026 10:37:02 +0100 Subject: [PATCH 257/612] fix(libSceNpCommon): decode an empty base64url input without passing a null pointer to memcpy Co-Authored-By: Claude Sonnet 5.5 --- core/libs/prx/libSceNpCommon/Strings.cpp | 2 +- core/libs/tests/GuestNpCommonRuntime.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceNpCommon/Strings.cpp b/core/libs/prx/libSceNpCommon/Strings.cpp index d4bbd25053..8b2830f639 100644 --- a/core/libs/prx/libSceNpCommon/Strings.cpp +++ b/core/libs/prx/libSceNpCommon/Strings.cpp @@ -39,7 +39,7 @@ struct Base64Block { int DecodeLastBlock(const char* src, std::size_t length, const char* alphabet, Base64Block* block) { char c[4] = {BASE64_PAD, BASE64_PAD, BASE64_PAD, BASE64_PAD}; - std::memcpy(c, src, length); + if (length != 0) std::memcpy(c, src, length); const int v0 = Base64Value(c[0], alphabet); const int v1 = Base64Value(c[1], alphabet); if (v0 < 0 || v1 < 0) return NP_ERROR_INVALID_CHARACTER; diff --git a/core/libs/tests/GuestNpCommonRuntime.cpp b/core/libs/tests/GuestNpCommonRuntime.cpp index 737602ce32..54cadeb7ff 100644 --- a/core/libs/tests/GuestNpCommonRuntime.cpp +++ b/core/libs/tests/GuestNpCommonRuntime.cpp @@ -856,6 +856,8 @@ void TestBase64() { Require(sceNpBase64UrlGetDecodeSize("+w==", 4) == NpInvalidCharacter, "base64url refuses the standard alphabet"); Require(sceNpBase64UrlDecoder("-wA", 3, bytes, 2) == 2 && bytes[0] == 0xFB && bytes[1] == 0, "base64url decodes a short tail"); Require(sceNpBase64UrlDecoder("-w", 2, bytes, 0) == NpInvalidFormat, "base64url decoding refuses a short buffer"); + Require(sceNpBase64UrlGetDecodeSize(nullptr, 0) == NpInvalidCharacter && sceNpBase64UrlDecoder(nullptr, 0, bytes, sizeof(bytes)) == NpInvalidCharacter, + "base64url decoding of a null source with no length behaves like an empty one"); } void TestNumbers() { From 67a8e941dd57806ab42376cf652731f3dee77d4e Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Mon, 2 Nov 2026 03:03:59 -0800 Subject: [PATCH 258/612] docs(debt): recount libSceLibreSsl's pending exports after merging main #10488 on main implements three more EVP_CIPHER_CTX calls; this branch now leaves 275 exports throwing. --- docs/dev/debt/libSceLibreSsl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 23d1030453..865b50444e 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 278 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 275 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. From 361d502e6be2381b22df71e2764b1852edd75c33 Mon Sep 17 00:00:00 2001 From: Omar Elmahy Date: Mon, 2 Nov 2026 10:49:12 +0000 Subject: [PATCH 259/612] feat(libSceMove): return not-initialized error before initialization --- core/libs/prx/libSceMove/Export.cpp | 20 ++++++++++++++++++++ core/libs/tests/GuestMove.cpp | 10 ++++++++++ docs/dev/debt/libSceMove.md | 1 + 3 files changed, 31 insertions(+) diff --git a/core/libs/prx/libSceMove/Export.cpp b/core/libs/prx/libSceMove/Export.cpp index e55eded120..aeef175127 100644 --- a/core/libs/prx/libSceMove/Export.cpp +++ b/core/libs/prx/libSceMove/Export.cpp @@ -124,51 +124,71 @@ int APS5_VABI sceMoveResetLightSphere(int32_t handle) { return checkHandle(handle); } int APS5_VABI sceMoveGetBluetoothAddress(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveGetCalibrationInfo(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveGetDeviceId(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveIsReproductionModel(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveIsValidHandle(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return 0; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveReadStateRecentForTracker(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveSetCalibrationMode(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveSetExtensionPortOutput(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveSetLightSphereForTracker(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveWriteCalibrationInfo(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/tests/GuestMove.cpp b/core/libs/tests/GuestMove.cpp index a8a16a5ba2..e912f04f60 100644 --- a/core/libs/tests/GuestMove.cpp +++ b/core/libs/tests/GuestMove.cpp @@ -78,6 +78,16 @@ void RequireNotInit(std::int32_t handle) { Require(sceMoveSetVibration(handle, 0xFF) == kNotInit); Require(sceMoveSetLightSphere(handle, 0xFF, 0, 0) == kNotInit); Require(sceMoveResetLightSphere(handle) == kNotInit); + Require(sceMoveGetBluetoothAddress() == kNotInit); + Require(sceMoveGetCalibrationInfo() == kNotInit); + Require(sceMoveGetDeviceId() == kNotInit); + Require(sceMoveIsReproductionModel() == kNotInit); + Require(sceMoveIsValidHandle() == 0); + Require(sceMoveReadStateRecentForTracker() == kNotInit); + Require(sceMoveSetCalibrationMode() == kNotInit); + Require(sceMoveSetExtensionPortOutput() == kNotInit); + Require(sceMoveSetLightSphereForTracker() == kNotInit); + Require(sceMoveWriteCalibrationInfo() == kNotInit); Require(sceMoveTerm() == kNotInit); Require(buffer.Untouched()); Require(count == -7); diff --git a/docs/dev/debt/libSceMove.md b/docs/dev/debt/libSceMove.md index 1d8f953336..0df0126b51 100644 --- a/docs/dev/debt/libSceMove.md +++ b/docs/dev/debt/libSceMove.md @@ -5,3 +5,4 @@ - [libSceMove](../../../core/libs/prx/libSceMove/Export.cpp): the host implements only disconnected controllers. The native module confirms initialization errors, disconnected return 1 without output writes, invalid-handle error 0x80EE0004, and initialization then argument then handle validation for queries. Reset-light-sphere still omits the device ioctl and its failure mapping. - [sceMoveOpen and sceMoveClose](../../../core/libs/prx/libSceMove/Export.cpp): native opening resolves a user/device through the input service, rejects duplicate device handles with 0x80EE0007, limits storage to eight slots with 0x80EE0005, and obtains handles from the device ioctl. The host generates monotonically increasing handles, accepts arbitrary type/index values and returns 0x80EE0007 for duplicate slots and throws for other users. Closing an unknown handle returns the confirmed 0x80EE0004. - [sceMoveReadStateRecent](../../../core/libs/prx/libSceMove/Export.cpp): a null data array is accepted for SDK versions through 0x03ffffff and rejected from 0x04000000 on, as the native module does; the count pointer and a nonnegative timestamp are required for every version. Connected state records and device-service failures remain unimplemented. +- [sceMoveGetBluetoothAddress, sceMoveGetCalibrationInfo, sceMoveGetDeviceId, sceMoveIsReproductionModel, sceMoveIsValidHandle, sceMoveReadStateRecentForTracker, sceMoveSetCalibrationMode, sceMoveSetExtensionPortOutput, sceMoveSetLightSphereForTracker, sceMoveWriteCalibrationInfo](../../../core/libs/prx/libSceMove/Export.cpp): in 13.42 module disassembly (sha256 f528763dd75ee1124b5ae0d8a7d9bd13e6642f964818724877cfbee710ac1851), the initialization flag byte (0x10018) is checked first: nine exports return 0x80EE0001 before argument/handle checks, and sceMoveIsValidHandle returns 0 (false) when uninitialized. Console runtime behavior is not measured, and initialized paths remain unimplemented placeholders throwing NotImplemented_nid_no_patch. From 050fedaa6f49106c715f2cd2288157edea280f5c Mon Sep 17 00:00:00 2001 From: Omar Elmahy Date: Mon, 2 Nov 2026 10:45:09 +0000 Subject: [PATCH 260/612] feat(libSceS3da): validate port bounds and transfer buffer parameters --- core/libs/prx/libSceS3da/CMakeLists.txt | 14 ++++++ core/libs/prx/libSceS3da/Export.cpp | 27 +++++++++-- core/libs/tests/GuestS3da.cpp | 59 +++++++++++++++++++++++++ docs/dev/debt/libSceS3da.md | 6 ++- 4 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 core/libs/prx/libSceS3da/CMakeLists.txt create mode 100644 core/libs/tests/GuestS3da.cpp diff --git a/core/libs/prx/libSceS3da/CMakeLists.txt b/core/libs/prx/libSceS3da/CMakeLists.txt new file mode 100644 index 0000000000..23bb8a49e0 --- /dev/null +++ b/core/libs/prx/libSceS3da/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(libSceS3da SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceS3da PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceS3da) +if(BUILD_TESTING) + add_test_executable(guest_s3da_tests ${LIBS_INCLUDE_DIR}/tests/GuestS3da.cpp) + target_include_directories(guest_s3da_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_s3da_tests PRIVATE libSceS3da libc) + configure_windows_unwind(guest_s3da_tests) + add_test(NAME guest_s3da COMMAND guest_s3da_tests) + if(WIN32) + set_tests_properties(guest_s3da PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceS3da/Export.cpp b/core/libs/prx/libSceS3da/Export.cpp index fcf0f1f9af..459cfd71dd 100644 --- a/core/libs/prx/libSceS3da/Export.cpp +++ b/core/libs/prx/libSceS3da/Export.cpp @@ -1,27 +1,48 @@ #include "prx/libSceS3da/include/Api.hpp" +#include #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int ERROR_INVALID_ARGUMENT = static_cast(0x81600002u); +constexpr int ERROR_ALREADY_OPEN = static_cast(0x81600006u); +constexpr std::uint32_t MAX_SIZE = 0xA000; + +std::atomic g_slots[2]{false, false}; + +} + extern "C" { int APS5_VABI sceS3daClose(std::uint32_t port) { - NotImplemented_nid_no_patch(__func__); + if (port > 1) return ERROR_INVALID_ARGUMENT; + bool expected = true; + if (!g_slots[port].compare_exchange_strong(expected, false)) { + return ERROR_INVALID_ARGUMENT; + } return 0; } int APS5_VABI sceS3daGetInfo(std::uint32_t port, S3daInfo* info) { + if (port > 1) return ERROR_INVALID_ARGUMENT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceS3daOpen(std::uint32_t port) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (port > 1) return ERROR_INVALID_ARGUMENT; + bool expected = false; + if (!g_slots[port].compare_exchange_strong(expected, true)) { + return ERROR_ALREADY_OPEN; + } + return static_cast(port); } int APS5_VABI sceS3daSendData(std::uint32_t port, const void* data, std::uint32_t size) { + if (port > 1 || data == nullptr || size == 0 || size > MAX_SIZE) return ERROR_INVALID_ARGUMENT; NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/tests/GuestS3da.cpp b/core/libs/tests/GuestS3da.cpp new file mode 100644 index 0000000000..a36f635ac5 --- /dev/null +++ b/core/libs/tests/GuestS3da.cpp @@ -0,0 +1,59 @@ +#include "prx/libSceS3da/include/Api.hpp" +#include +#include +#include + +#ifndef _WIN32 +extern "C" [[noreturn]] void NotImplemented_nid_no_patch(const char* name) { + (void)name; + std::abort(); +} +#endif + +namespace { + +void Require(bool value) { + if (!value) { + std::abort(); + } +} + +constexpr int ERROR_INVALID_ARGUMENT = static_cast(0x81600002u); +constexpr int ERROR_ALREADY_OPEN = static_cast(0x81600006u); + +} + +int main() { + std::uint8_t dataBuffer[64]{}; + + for (const std::uint32_t port : {2u, 3u, 32u, 0xFFFFFFFFu}) { + Require(sceS3daOpen(port) == ERROR_INVALID_ARGUMENT); + Require(sceS3daClose(port) == ERROR_INVALID_ARGUMENT); + Require(sceS3daGetInfo(port, nullptr) == ERROR_INVALID_ARGUMENT); + Require(sceS3daSendData(port, dataBuffer, sizeof(dataBuffer)) == ERROR_INVALID_ARGUMENT); + Require(sceS3daSendData(port, nullptr, sizeof(dataBuffer)) == ERROR_INVALID_ARGUMENT); + Require(sceS3daSendData(port, dataBuffer, 0) == ERROR_INVALID_ARGUMENT); + Require(sceS3daSendData(port, dataBuffer, 0xA001u) == ERROR_INVALID_ARGUMENT); + } + + Require(sceS3daClose(0) == ERROR_INVALID_ARGUMENT); + Require(sceS3daClose(1) == ERROR_INVALID_ARGUMENT); + + Require(sceS3daOpen(0) == 0); + Require(sceS3daOpen(0) == ERROR_ALREADY_OPEN); + Require(sceS3daClose(0) == 0); + Require(sceS3daClose(0) == ERROR_INVALID_ARGUMENT); + + Require(sceS3daOpen(1) == 1); + Require(sceS3daOpen(1) == ERROR_ALREADY_OPEN); + Require(sceS3daClose(1) == 0); + Require(sceS3daClose(1) == ERROR_INVALID_ARGUMENT); + + for (const std::uint32_t port : {0u, 1u}) { + Require(sceS3daSendData(port, nullptr, sizeof(dataBuffer)) == ERROR_INVALID_ARGUMENT); + Require(sceS3daSendData(port, dataBuffer, 0) == ERROR_INVALID_ARGUMENT); + Require(sceS3daSendData(port, dataBuffer, 0xA001u) == ERROR_INVALID_ARGUMENT); + } + + return 0; +} diff --git a/docs/dev/debt/libSceS3da.md b/docs/dev/debt/libSceS3da.md index cc6345ecaa..7f392e989a 100644 --- a/docs/dev/debt/libSceS3da.md +++ b/docs/dev/debt/libSceS3da.md @@ -1,5 +1,9 @@ # libSceS3da technical debt +### Functional + +- [sceS3daOpen and sceS3daClose](../../../core/libs/prx/libSceS3da/Export.cpp): in 13.42 module disassembly (sha256 156e8eb0bb7f459006f73908a23054dd2d640cf22b6835f12a467c2818da13e2), ports are bounded to 0 and 1 with no kernel call. `sceS3daOpen` sets slot state and returns `port` or 0x81600006 if already open; `sceS3daClose` clears the slot and returns 0 or 0x81600002 if unopened. `sceS3daSendData` validates port <= 1, non-null data, and size within 1..0xA000 (0x81600002); `sceS3daGetInfo` validates port <= 1 (0x81600002). Both ioctl paths remain unimplemented placeholders throwing NotImplemented_nid_no_patch. + ### Unknown function info -- [sceS3daClose, sceS3daGetInfo, sceS3daOpen and sceS3daSendData](../../../core/libs/prx/libSceS3da/Export.cpp) remain unimplemented. Port arguments and transfer buffer/length are recovered from the native module. `GetInfo` forwards its output pointer to a kernel ioctl without accessing its fields; the information layout requires the kernel handler or a caller that interprets its output. +- [sceS3daGetInfo and sceS3daSendData](../../../core/libs/prx/libSceS3da/Export.cpp): transfer data and info queries forward to kernel ioctls (0x8018a601 and 0x8010a602). Kernel ioctl behavior and output structure layouts remain unmodelled. From 81f82339e47742a4c6f66b7e1ef9dcbd78537d0a Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 17:25:40 +0530 Subject: [PATCH 261/612] test(libSceFiber): use full aligned buffer with sentinel in opt param test --- core/libs/tests/Fiber.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/core/libs/tests/Fiber.cpp b/core/libs/tests/Fiber.cpp index cdf8f30810..7850d50164 100644 --- a/core/libs/tests/Fiber.cpp +++ b/core/libs/tests/Fiber.cpp @@ -568,10 +568,21 @@ int main(int argc, char** argv) { Require(sceFiberGetSelf(&self) == FiberErrorPermission, "no fiber on the thread"); Require(sceFiberOptParamInitialize(nullptr) == FiberErrorNull, "null opt param"); Require(sceFiberOptParamInitialize(reinterpret_cast(0x10)) == FiberErrorInvalid, "unwritable opt param"); - FiberOptParam optParam{}; - optParam.magic = 0x12345678; - Require(sceFiberOptParamInitialize(&optParam) == 0, "initialize opt param"); - Require(optParam.magic == 0, "opt param cleared"); + struct alignas(8) AlignedOptParamBuffer { + std::uint8_t bytes[0x80]; + std::uint32_t sentinel = 0x5a5a5a5a; + } storage; + std::memset(storage.bytes, 0xA5, sizeof(storage.bytes)); + Require(sceFiberOptParamInitialize(reinterpret_cast(storage.bytes)) == 0, "initialize opt param"); + bool allZero = true; + for (std::size_t i = 0; i < sizeof(storage.bytes); ++i) { + if (storage.bytes[i] != 0) { + allZero = false; + break; + } + } + Require(allZero, "opt param cleared"); + Require(storage.sentinel == 0x5a5a5a5a, "sentinel intact"); Require(_sceFiberInitializeImpl_nid_postfix(g_first, "first", FirstEntry, 11, g_firstContext.data(), g_firstContext.size(), nullptr, 0) == 0, "initialize first"); Require(_sceFiberInitializeImpl_nid_postfix(g_second, "second", SecondEntry, 22, g_secondContext.data(), g_secondContext.size(), nullptr, 0) == 0, "initialize second"); const auto threadCsr = _mm_getcsr(); From 1322c0f5d66beba8d07e781188cf1168ace5ed60 Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Mon, 2 Nov 2026 14:14:38 +0100 Subject: [PATCH 262/612] feat(libSceAvSetting): implement the self-contained mode helpers Recover the 11.00 wrappers for sceAvSettingSetAudioOutModeAny_/Invalid_, sceAvSettingSetVideoOutModeAny_/Invalid_ and sceAvSettingSetVideoOutSource. The mode helpers fill the caller's buffer with 0xff or 0 for the requested byte count and then store the count as the first 32-bit value; a null pointer is ignored and a negative size throws because the console would memset a huge range. The source setter writes its second argument to the caller's slot. The rest of the library is an IPMI AV client and keeps throwing. --- core/libs/prx/libSceAvSetting/Export.cpp | 37 +++++++++++-------- core/libs/prx/libSceAvSetting/include/Api.hpp | 10 ++--- .../tests/GuestFirmwareConstantReturns.cpp | 33 +++++++++++++++++ docs/dev/debt/libSceAvSetting.md | 10 +++-- 4 files changed, 67 insertions(+), 23 deletions(-) diff --git a/core/libs/prx/libSceAvSetting/Export.cpp b/core/libs/prx/libSceAvSetting/Export.cpp index de1d4295f5..f881b9193e 100644 --- a/core/libs/prx/libSceAvSetting/Export.cpp +++ b/core/libs/prx/libSceAvSetting/Export.cpp @@ -583,14 +583,18 @@ int APS5_VABI sceAvSettingSet2dVrMode() { return 0; } -int APS5_VABI sceAvSettingSetAudioOutModeAny_() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceAvSettingSetAudioOutModeAny_(void* out, std::int32_t value) { + if (out == nullptr) return; + if (value < 0) NotImplemented_nid_no_patch(__func__); + std::memset(out, 0xff, static_cast(value)); + *static_cast(out) = value; } -int APS5_VABI sceAvSettingSetAudioOutModeInvalid_() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceAvSettingSetAudioOutModeInvalid_(void* out, std::int32_t value) { + if (out == nullptr) return; + if (value < 0) NotImplemented_nid_no_patch(__func__); + std::memset(out, 0, static_cast(value)); + *static_cast(out) = value; } int APS5_VABI sceAvSettingSetAvOutputMode() { @@ -622,19 +626,22 @@ int APS5_VABI sceAvSettingSetHdmiGamutMetadata_() { return 0; } -int APS5_VABI sceAvSettingSetVideoOutModeAny_() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceAvSettingSetVideoOutModeAny_(void* out, std::int32_t value) { + if (out == nullptr) return; + if (value < 0) NotImplemented_nid_no_patch(__func__); + std::memset(out, 0xff, static_cast(value)); + *static_cast(out) = value; } -int APS5_VABI sceAvSettingSetVideoOutModeInvalid_() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceAvSettingSetVideoOutModeInvalid_(void* out, std::int32_t value) { + if (out == nullptr) return; + if (value < 0) NotImplemented_nid_no_patch(__func__); + std::memset(out, 0, static_cast(value)); + *static_cast(out) = value; } -int APS5_VABI sceAvSettingSetVideoOutSource() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceAvSettingSetVideoOutSource(std::int32_t* out, std::int32_t source) { + if (out != nullptr) *out = source; } int APS5_VABI sceAvSettingSetVideoOutputColorEffect() { diff --git a/core/libs/prx/libSceAvSetting/include/Api.hpp b/core/libs/prx/libSceAvSetting/include/Api.hpp index ec0d032743..c0eaffbf72 100644 --- a/core/libs/prx/libSceAvSetting/include/Api.hpp +++ b/core/libs/prx/libSceAvSetting/include/Api.hpp @@ -118,17 +118,17 @@ int APS5_VABI sceAvSettingNotifyVideoOutMode(void); int APS5_VABI sceAvSettingRemoveCallbacks(void); int APS5_VABI sceAvSettingRemoveProcessOutputMode(void); int APS5_VABI sceAvSettingSet2dVrMode(void); -int APS5_VABI sceAvSettingSetAudioOutModeAny_(void); -int APS5_VABI sceAvSettingSetAudioOutModeInvalid_(void); +void APS5_VABI sceAvSettingSetAudioOutModeAny_(void* out, std::int32_t value); +void APS5_VABI sceAvSettingSetAudioOutModeInvalid_(void* out, std::int32_t value); int APS5_VABI sceAvSettingSetAvOutputMode(void); int APS5_VABI sceAvSettingSetDispclk(void); int APS5_VABI sceAvSettingSetDisplayBlackLevel(void); int APS5_VABI sceAvSettingSetHdcpMode(void); int APS5_VABI sceAvSettingSetHdcpStatus(void); int APS5_VABI sceAvSettingSetHdmiGamutMetadata_(void); -int APS5_VABI sceAvSettingSetVideoOutModeAny_(void); -int APS5_VABI sceAvSettingSetVideoOutModeInvalid_(void); -int APS5_VABI sceAvSettingSetVideoOutSource(void); +void APS5_VABI sceAvSettingSetVideoOutModeAny_(void* out, std::int32_t value); +void APS5_VABI sceAvSettingSetVideoOutModeInvalid_(void* out, std::int32_t value); +void APS5_VABI sceAvSettingSetVideoOutSource(std::int32_t* out, std::int32_t source); int APS5_VABI sceAvSettingSetVideoOutputColorEffect(void); int APS5_VABI sceAvSettingSimulateProcessOutputModeArbitration_(void); int APS5_VABI sceAvSettingTerm(void); diff --git a/core/libs/tests/GuestFirmwareConstantReturns.cpp b/core/libs/tests/GuestFirmwareConstantReturns.cpp index d63502eb3f..92331731f5 100644 --- a/core/libs/tests/GuestFirmwareConstantReturns.cpp +++ b/core/libs/tests/GuestFirmwareConstantReturns.cpp @@ -45,6 +45,38 @@ void CheckAvSettingCallbackFuncs() { CheckAvSettingCallbackFuncsInit(sceAvSettingCallbackFuncsInitForLnc_, 0x10); } +void CheckAvSettingModeHelpers() { + constexpr std::uint8_t Fill = 0xaa; + const auto check = [Fill](void (APS5_VABI* helper)(void*, std::int32_t), std::uint8_t fill) { + std::array buffer{}; + buffer.fill(Fill); + helper(buffer.data(), 8); + Require(buffer[0] == 8 && buffer[1] == 0 && buffer[2] == 0 && buffer[3] == 0); + for (std::size_t i = 4; i < buffer.size(); ++i) Require(buffer[i] == fill); + buffer.fill(Fill); + helper(buffer.data(), 4); + Require(buffer[0] == 4 && buffer[1] == 0 && buffer[2] == 0 && buffer[3] == 0); + for (std::size_t i = 4; i < buffer.size(); ++i) Require(buffer[i] == Fill); + buffer.fill(Fill); + helper(buffer.data(), 0); + for (std::size_t i = 0; i < 4; ++i) Require(buffer[i] == 0); + for (std::size_t i = 4; i < buffer.size(); ++i) Require(buffer[i] == Fill); + buffer.fill(Fill); + helper(nullptr, 8); + for (const auto value : buffer) Require(value == Fill); + }; + check(sceAvSettingSetAudioOutModeAny_, 0xff); + check(sceAvSettingSetAudioOutModeInvalid_, 0x00); + check(sceAvSettingSetVideoOutModeAny_, 0xff); + check(sceAvSettingSetVideoOutModeInvalid_, 0x00); + std::int32_t source = 0x12345678; + sceAvSettingSetVideoOutSource(&source, 7); + Require(source == 7); + source = 0x12345678; + sceAvSettingSetVideoOutSource(nullptr, 7); + Require(source == 0x12345678); +} + void CheckComposite() { constexpr int Unsupported = static_cast(0x80d40006); Require(sceCompositorCreateIndirectRenderTarget() == Unsupported); @@ -171,6 +203,7 @@ void CheckDebugSyscore() { int main() { CheckAvSetting(); CheckAvSettingCallbackFuncs(); + CheckAvSettingModeHelpers(); CheckComposite(); CheckFsInternal(); CheckHttpCacheAndPlayReady(); diff --git a/docs/dev/debt/libSceAvSetting.md b/docs/dev/debt/libSceAvSetting.md index 1171471326..7694948a6a 100644 --- a/docs/dev/debt/libSceAvSetting.md +++ b/docs/dev/debt/libSceAvSetting.md @@ -21,9 +21,9 @@ - [sceAvSettingGetHdmiMonitorInfo, sceAvSettingGetHdmiRawEdid, sceAvSettingGetMorpheusPuStatus, sceAvSettingGetNativeHdmiMonitorInfo, sceAvSettingGetRawHdmiMonitorInfo](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. - [sceAvSettingInit, sceAvSettingIsSupportedAudioOutModeByHdmiMonitorInfo, sceAvSettingIsSupportedHdcpVersionByHdmiMonitorInfo, sceAvSettingIsSupportedVideoOutModeByHdmiMonitorInfo, sceAvSettingIsSuspendedProcessOutputModeAvailable](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. - [sceAvSettingIsSuspendedProcessOutputModeAvailable2, sceAvSettingIsVrSupportedByHdmiMonitorInfo2, sceAvSettingNotifyAudioOutMode, sceAvSettingNotifyResponseForSocialScreen, sceAvSettingNotifyUmdEvent](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. -- [sceAvSettingNotifyVideoOutMode, sceAvSettingRemoveCallbacks, sceAvSettingRemoveProcessOutputMode, sceAvSettingSet2dVrMode, sceAvSettingSetAudioOutModeAny_](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. -- [sceAvSettingSetAudioOutModeInvalid_, sceAvSettingSetAvOutputMode, sceAvSettingSetDispclk, sceAvSettingSetHdcpMode](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. -- [sceAvSettingSetHdcpStatus, sceAvSettingSetHdmiGamutMetadata_, sceAvSettingSetVideoOutModeAny_, sceAvSettingSetVideoOutModeInvalid_, sceAvSettingSetVideoOutSource](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. +- [sceAvSettingNotifyVideoOutMode, sceAvSettingRemoveCallbacks, sceAvSettingRemoveProcessOutputMode, sceAvSettingSet2dVrMode](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. +- [sceAvSettingSetAvOutputMode, sceAvSettingSetDispclk, sceAvSettingSetHdcpMode](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. +- [sceAvSettingSetHdcpStatus, sceAvSettingSetHdmiGamutMetadata_](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. - [sceAvSettingSetVideoOutputColorEffect, sceAvSettingSimulateProcessOutputModeArbitration_, sceAvSettingWaitCallback](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. - [sceAvControlGetPuTypeByMonitorInfo](../../../core/libs/prx/libSceAvSetting/Export.cpp) (libSceAvSetting) - unknown signature @@ -88,3 +88,7 @@ - [sceAvControlSetSystemVideoMode](../../../core/libs/prx/libSceAvSetting/Export.cpp) (libSceAvSetting) - unknown signature - [sceAvControlDebugAddCallbacks](../../../core/libs/prx/libSceAvSetting/Export.cpp) (libSceAvSetting) - unknown signature - [sceAvSettingSetDisplayBlackLevel, sceAvSettingTerm](../../../core/libs/prx/libSceAvSetting/Export.cpp) - on firmware 13.42 both return `0x802A00FF` and do nothing else, measured in an emulator twice with null, small and all-ones arguments ([BunnyFl on #9449](https://github.com/boykopovar/AnyPS5/pull/9449#issuecomment-6401358322)); they return that code here. Signatures and behavior on other firmware are unverified. + +### Functional + +- [sceAvSettingSetAudioOutModeAny_, sceAvSettingSetAudioOutModeInvalid_, sceAvSettingSetVideoOutModeAny_, sceAvSettingSetVideoOutModeInvalid_ and sceAvSettingSetVideoOutSource](../../../core/libs/prx/libSceAvSetting/Export.cpp) - the 11.00 wrappers at `0x2d90`, `0x2dc0`, `0x2df0` and `0x2e20` fill the caller buffer with `0xff` (`Any_`) or 0 (`Invalid_`) for the requested byte count and then store the count as the first 32-bit value; a null pointer is ignored. The source setter at `0x17b0` writes its second argument to the caller's 32-bit slot. A negative mode size would make the console memset a huge range, so the host throws for it. The meaning of the buffers and the rest of the IPMI-backed AV client are unmeasured. From c3cb4d0f8eb1b9649d4c82859c7ac64e85982b76 Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Mon, 2 Nov 2026 14:20:09 +0100 Subject: [PATCH 263/612] feat(libSceBemp2sys): implement the logging and memory manager configuration Recover the 11.00 wrappers for bemp2sys_logprintf_getlevel, setfunc and setlevel and bemp2sys_memmanager_set. The host keeps a process-global log level (0 by default), log function pointer and three memory-manager pointers; setlevel accepts 0..3 with the wrapper's unsigned compare, so larger and negative values are ignored. The pointers have no consumer until the parsers are implemented, and the rest of the library keeps throwing. --- core/libs/CMakeLists.txt | 10 ++++++ core/libs/prx/libSceBemp2sys/Export.cpp | 32 +++++++++++++------- core/libs/prx/libSceBemp2sys/include/Api.hpp | 6 ++-- core/libs/tests/GuestBemp2sys.cpp | 27 +++++++++++++++++ docs/dev/debt/libSceBemp2sys.md | 6 +++- 5 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 core/libs/tests/GuestBemp2sys.cpp diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index d132628e46..d7c8966d2d 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -529,6 +529,16 @@ if(BUILD_TESTING) set_tests_properties(guest_firmware_constant_returns PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$") endif() + add_test_executable(guest_bemp2sys_tests tests/GuestBemp2sys.cpp) + target_include_directories(guest_bemp2sys_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_bemp2sys_tests PRIVATE libSceBemp2sys libkernel libc) + configure_windows_unwind(guest_bemp2sys_tests) + add_test(NAME guest_bemp2sys COMMAND guest_bemp2sys_tests) + set_tests_properties(guest_bemp2sys PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_bemp2sys PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_playready_tests tests/GuestPlayReady.cpp) target_include_directories(guest_playready_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_playready_tests PRIVATE libScePlayReady libkernel libc) diff --git a/core/libs/prx/libSceBemp2sys/Export.cpp b/core/libs/prx/libSceBemp2sys/Export.cpp index 072a28cede..6efe86a036 100644 --- a/core/libs/prx/libSceBemp2sys/Export.cpp +++ b/core/libs/prx/libSceBemp2sys/Export.cpp @@ -6,27 +6,37 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +void* LogFunction = nullptr; +int LogLevel = 0; + +struct MemoryManager { + void* allocate = nullptr; + void* deallocate = nullptr; + void* reallocate = nullptr; +}; +MemoryManager memoryManager; + +} + extern "C" { APS5_DUMMY_FUN int APS5_VABI bemp2sys_logprintf_getlevel_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return LogLevel; } -int APS5_VABI bemp2sys_logprintf_setfunc_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI bemp2sys_logprintf_setfunc_nid_postfix(void* function) { + LogFunction = function; } -int APS5_VABI bemp2sys_logprintf_setlevel_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI bemp2sys_logprintf_setlevel_nid_postfix(int level) { + if (static_cast(level) <= 3u) LogLevel = level; } -int APS5_VABI bemp2sys_memmanager_set_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI bemp2sys_memmanager_set_nid_postfix(void* allocate, void* deallocate, void* reallocate) { + memoryManager = {allocate, deallocate, reallocate}; } int APS5_VABI bemp2sys_packetizer_create_nid_postfix(void) { diff --git a/core/libs/prx/libSceBemp2sys/include/Api.hpp b/core/libs/prx/libSceBemp2sys/include/Api.hpp index 82e55243e5..db3b28669c 100644 --- a/core/libs/prx/libSceBemp2sys/include/Api.hpp +++ b/core/libs/prx/libSceBemp2sys/include/Api.hpp @@ -5,9 +5,9 @@ extern "C" { int APS5_VABI bemp2sys_logprintf_getlevel_nid_postfix(void); -int APS5_VABI bemp2sys_logprintf_setfunc_nid_postfix(void); -int APS5_VABI bemp2sys_logprintf_setlevel_nid_postfix(void); -int APS5_VABI bemp2sys_memmanager_set_nid_postfix(void); +void APS5_VABI bemp2sys_logprintf_setfunc_nid_postfix(void* function); +void APS5_VABI bemp2sys_logprintf_setlevel_nid_postfix(int level); +void APS5_VABI bemp2sys_memmanager_set_nid_postfix(void* allocate, void* deallocate, void* reallocate); int APS5_VABI bemp2sys_packetizer_create_nid_postfix(void); int APS5_VABI bemp2sys_packetizer_destroy_nid_postfix(void); int APS5_VABI bemp2sys_packetizer_init_nid_postfix(void); diff --git a/core/libs/tests/GuestBemp2sys.cpp b/core/libs/tests/GuestBemp2sys.cpp new file mode 100644 index 0000000000..66f679336b --- /dev/null +++ b/core/libs/tests/GuestBemp2sys.cpp @@ -0,0 +1,27 @@ +#include "prx/libSceBemp2sys/libSceBemp2sys.hpp" +#include +#include + +static void Require(bool value) { if (!value) std::abort(); } + +namespace { + +void LogFunction(int, const char*) {} +void* Allocate(std::size_t) { return nullptr; } +void Deallocate(void*) {} +void* Reallocate(void*, std::size_t) { return nullptr; } + +} + +int main() { + Require(bemp2sys_logprintf_getlevel_nid_postfix() == 0); + bemp2sys_logprintf_setlevel_nid_postfix(3); + Require(bemp2sys_logprintf_getlevel_nid_postfix() == 3); + bemp2sys_logprintf_setlevel_nid_postfix(4); + Require(bemp2sys_logprintf_getlevel_nid_postfix() == 3); + bemp2sys_logprintf_setlevel_nid_postfix(-1); + Require(bemp2sys_logprintf_getlevel_nid_postfix() == 3); + bemp2sys_logprintf_setfunc_nid_postfix(reinterpret_cast(&LogFunction)); + bemp2sys_memmanager_set_nid_postfix(reinterpret_cast(&Allocate), reinterpret_cast(&Deallocate), reinterpret_cast(&Reallocate)); + return 0; +} diff --git a/docs/dev/debt/libSceBemp2sys.md b/docs/dev/debt/libSceBemp2sys.md index e719f90a7b..9a93ffc4d4 100644 --- a/docs/dev/debt/libSceBemp2sys.md +++ b/docs/dev/debt/libSceBemp2sys.md @@ -2,7 +2,11 @@ ### Unknown function info -- [libSceBemp2sys pending exports](../../../core/libs/prx/libSceBemp2sys/Export.cpp) - 31 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceBemp2sys.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceBemp2sys pending exports](../../../core/libs/prx/libSceBemp2sys/Export.cpp) - 27 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceBemp2sys.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. + +### Functional + +- [bemp2sys_logprintf_getlevel, bemp2sys_logprintf_setfunc, bemp2sys_logprintf_setlevel and bemp2sys_memmanager_set](../../../core/libs/prx/libSceBemp2sys/Export.cpp) - the 11.00 wrappers at `0x5cc0`, `0x5ca0`, `0x5cb0` and `0x5c90` keep a process-global log level, log function pointer and three memory-manager pointers. `setlevel` accepts 0..3 with an unsigned compare, so larger and negative values are ignored; `getlevel` starts at 0. The pointers are stored but have no consumer until the parsers are implemented. The rest of the library keeps throwing. ### Additional callable exports From db133e5b2fe22e5d4a8457a929dd21e9db4cf6bf Mon Sep 17 00:00:00 2001 From: Bipin Date: Mon, 2 Nov 2026 19:22:26 +0530 Subject: [PATCH 264/612] fix(libkernel_sys): deny adjtime adjustments for jailed callers --- .../prx/libkernel_sys/AdditionalExports.cpp | 6 ++++- core/libs/tests/GuestKernelSysAdjtime.cpp | 27 ++++++++++--------- docs/dev/debt/libkernel_sys.md | 2 +- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/core/libs/prx/libkernel_sys/AdditionalExports.cpp b/core/libs/prx/libkernel_sys/AdditionalExports.cpp index e64ee9bc6d..0978cd97a5 100644 --- a/core/libs/prx/libkernel_sys/AdditionalExports.cpp +++ b/core/libs/prx/libkernel_sys/AdditionalExports.cpp @@ -205,13 +205,17 @@ int APS5_VABI _wait4_nid_postfix() { } int APS5_VABI adjtime_nid_postfix(const KernelTimeval* delta, KernelTimeval* oldDelta) { + constexpr int GuestEperm = 1; constexpr int GuestEfault = 14; KernelTimeval adjustment{}; if (delta != nullptr && !GuestCopyIn_nid_no_patch(&adjustment, delta, sizeof(adjustment))) { *__error_nid_postfix() = GuestEfault; return -1; } - if (delta != nullptr) NotImplemented_nid_no_patch(__func__); + if (delta != nullptr) { + *__error_nid_postfix() = GuestEperm; + return -1; + } if (oldDelta != nullptr) { const KernelTimeval pending{}; if (!GuestCopyOut_nid_no_patch(oldDelta, &pending, sizeof(pending))) { diff --git a/core/libs/tests/GuestKernelSysAdjtime.cpp b/core/libs/tests/GuestKernelSysAdjtime.cpp index f935f74e13..9d968247e3 100644 --- a/core/libs/tests/GuestKernelSysAdjtime.cpp +++ b/core/libs/tests/GuestKernelSysAdjtime.cpp @@ -1,14 +1,15 @@ #include "prx/libkernel_sys/include/AdditionalExports.hpp" -#include "prx/libc/include/General.hpp" #include "prx/libc/include/Api.hpp" +#include #include #include #include -#include +#include namespace { +constexpr int GuestEperm = 1; constexpr int GuestEfault = 14; void Require(bool condition, const char* message) { @@ -18,23 +19,19 @@ void Require(bool condition, const char* message) { } } -void RequireAdjustment(const KernelTimeval& delta) { +void RequirePermissionDenied(const KernelTimeval& delta) { KernelTimeval remaining{-3, -2}; - const auto before = NotImplementedCalls_nid_no_patch(); - try { - adjtime_nid_postfix(&delta, &remaining); - } catch (const std::runtime_error&) { - Require(NotImplementedCalls_nid_no_patch() == before + 1, "unsupported adjustment bypassed the unimplemented-call counter"); - Require(remaining.tv_sec == -3 && remaining.tv_usec == -2, "unsupported adjustment modified oldDelta"); - return; - } - Require(false, "unsupported adjustment returned success"); + Require(adjtime_nid_postfix(&delta, &remaining) == -1 && *__error_nid_postfix() == GuestEperm, "jailed clock adjustment did not fail with EPERM"); + Require(remaining.tv_sec == -3 && remaining.tv_usec == -2, "denied adjustment modified oldDelta"); + Require(adjtime_nid_postfix(&delta, reinterpret_cast(1)) == -1 && *__error_nid_postfix() == GuestEperm, "adjtime checked oldDelta before denying the adjustment"); } } // namespace int main() { + *__error_nid_postfix() = 123; Require(adjtime_nid_postfix(nullptr, nullptr) == 0, "adjtime with both pointers null did not succeed"); + Require(*__error_nid_postfix() == 123, "successful adjtime query changed guest errno"); KernelTimeval remaining{-3, -2}; Require(adjtime_nid_postfix(nullptr, &remaining) == 0, "adjtime with a null delta did not succeed"); Require(remaining.tv_sec == 0 && remaining.tv_usec == 0, "adjtime did not report a zero pending adjustment"); @@ -42,7 +39,11 @@ int main() { remaining = {-3, -2}; Require(adjtime_nid_postfix(reinterpret_cast(1), &remaining) == -1 && *__error_nid_postfix() == GuestEfault, "adjtime with an unreadable delta did not fail with EFAULT"); Require(remaining.tv_sec == -3 && remaining.tv_usec == -2, "adjtime wrote oldDelta before reading delta"); - for (const KernelTimeval delta : {KernelTimeval{2, 0}, KernelTimeval{0, -1}, KernelTimeval{0, 0}}) RequireAdjustment(delta); + Require(adjtime_nid_postfix(reinterpret_cast(1), reinterpret_cast(1)) == -1 && *__error_nid_postfix() == GuestEfault, "adjtime checked privileges before reading delta"); + for (const KernelTimeval delta : {KernelTimeval{2, 0}, KernelTimeval{0, -1}, KernelTimeval{0, 0}, + KernelTimeval{std::numeric_limits::max(), std::numeric_limits::max()}, + KernelTimeval{std::numeric_limits::min(), std::numeric_limits::min()}}) RequirePermissionDenied(delta); + Require(adjtime_nid_postfix(nullptr, &remaining) == 0 && remaining.tv_sec == 0 && remaining.tv_usec == 0, "denied adjustment changed the pending query result"); std::puts("guest_kernel_sys_adjtime passed"); return 0; } diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index b31b079052..809f916aea 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -383,7 +383,7 @@ - [sceKernelGetProcessType](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - not measured on a console; as in shadPS4, the title's own pid, -1 and 0 are type 0 (big application) and any other pid is `ESRCH` - [cpuset, cpuset_getid, cpuset_setid](../../../core/libs/prx/libkernel_sys/Cpuset.cpp) (libkernel_sys) - console root IDs and inheritance are unverified; non-process scopes (TID, CPUSET, JAIL) throw as unmodelled as per-thread cpusets and hierarchy visibility are not tracked. CPU set assignment and queries follow FreeBSD 11.4 [kern_cpuset.c](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_cpuset.c): cpuset allocates sequential IDs from 2; cpuset_setid supports CPU_WHICH_PID for known IDs (or root 0, default 1); dynamic sets are single-process referenced and unlinked when vacated, returning ESRCH on lookup; cpuset_getid returns root 0 for CPU_LEVEL_ROOT and the assigned ID for CPU_LEVEL_CPUSET. Output writes use GuestCopyOut_nid_no_patch (EFAULT on bad pointers), with level/which/id errors returned before copyout; levels outside 1 to 3 return EINVAL where FreeBSD returns 0 with the base set ID; other which return EINVAL, unknown pids return ESRCH - [sceKernelSettimeofday](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - forwards to `settimeofday` and returns its result, or `0x80020000` plus `errno` for a failure, as shadPS4's [`sceKernelSettimeofday`](https://github.com/shadps4-emu/shadPS4/blob/7ea677e56d75bf3d90ace2b0a5144a665d1f38b6/src/core/libraries/kernel/time.cpp#L565-L571) maps its call through `ErrnoToSceKernelError`; that branch passes the call's return value, and shadPS4's own `settimeofday` never fails, so the errno form the other sce wrappers here use is taken. Nothing is measured on the console, and a valid time inherits `settimeofday`'s refusal to set the host clock -- [adjtime](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) does not model clock slewing or adjustment privileges; every readable non-null `delta`, including zero, calls `NotImplemented_nid_no_patch`. A null `delta` reports zero pending adjustment because adjustments are never accepted. Checked guest reads and writes return `EFAULT`, with input read before output, following FreeBSD 11.4 [`sys_adjtime` and `kern_adjtime`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_ntptime.c#L961-L1010). Console behavior and whether a title has `PRIV_ADJTIME` are unverified. +- [adjtime](../../../core/libs/prx/libkernel_sys/AdditionalExports.cpp) (libkernel_sys) assumes a jailed caller under the model agreed on [#11216](https://github.com/boykopovar/AnyPS5/pull/11216); console privileges and clock slewing are unverified. FreeBSD 11.4 [`sys_adjtime`/`kern_adjtime`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_ntptime.c#L961-L1010) reads `delta` before checking `PRIV_ADJTIME`, which [`prison_priv_check`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_jail.c#L3060-L3385) denies. Every readable non-null delta returns `EPERM` without writing `oldDelta`; bad input returns `EFAULT` first. A null delta reports zero pending adjustment through checked copyout. Privileged unjailed callers and nonzero pending corrections are not modelled; the host clock is never adjusted. ### Unknown function info From fe065ca17f575c4f1c1b07bfa32a78e3e4430f07 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 19:26:38 +0530 Subject: [PATCH 265/612] docs(libSceRandom): document Firmware 13.42 store behavior for unwritable buffer pointers --- docs/dev/debt/libSceRandom.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceRandom.md b/docs/dev/debt/libSceRandom.md index 889fdd2b1e..7019aaa170 100644 --- a/docs/dev/debt/libSceRandom.md +++ b/docs/dev/debt/libSceRandom.md @@ -2,4 +2,4 @@ ### Functional -- [sceRandomGetRandomNumber](../../../core/libs/prx/libSceRandom/Export.cpp) (libSceRandom) - a null buffer returns `SCE_RANDOM_ERROR_INVALID` (0x817C0016) even when the size is 0; that is an assumption, not measured on hardware. shadPS4 (PS4, `src/core/libraries/random/random.cpp`, commit 0614ce324be092791b758d79f93d9f1ca5b10a81) does not check the buffer, and KytyPS5 (PS5, `src/libs/libNet.cpp`, commit 5e837a9beac82c031868eaad9348d6a48245fb99) rejects a null buffer only for a size above 0, so both return success for a null buffer of size 0. fpPS4 (PS4, `src/ps4_libscerandom.pas`, commit 04cefd43e6fddd1ab033e7980cd356d14c964905) rejects it, with `EINVAL` (22) where this library returns the SCE code. An unwritable buffer pointer also returns SCE_RANDOM_ERROR_INVALID as a defensive host-protection fallback; console behavior on unmapped or read-only memory is not measured. +- [sceRandomGetRandomNumber](../../../core/libs/prx/libSceRandom/Export.cpp) (libSceRandom) - a null buffer returns `SCE_RANDOM_ERROR_INVALID` (0x817C0016) even when the size is 0; that is an assumption, not measured on hardware. shadPS4 (PS4, `src/core/libraries/random/random.cpp`, commit 0614ce324be092791b758d79f93d9f1ca5b10a81) does not check the buffer, and KytyPS5 (PS5, `src/libs/libNet.cpp`, commit 5e837a9beac82c031868eaad9348d6a48245fb99) rejects a null buffer only for a size above 0, so both return success for a null buffer of size 0. fpPS4 (PS4, `src/ps4_libscerandom.pas`, commit 04cefd43e6fddd1ab033e7980cd356d14c964905) rejects it, with `EINVAL` (22) where this library returns the SCE code. Firmware 13.42 checks only for null and stores directly (read from disassembly, not measured on a console), so the console faults on an unwritable non-null pointer; AnyPS5 returns INVALID (0x817C0016) instead. From 7d2ff76de09a31c9033bb397512c39e8d8e4c3f0 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 19:28:15 +0530 Subject: [PATCH 266/612] docs(libSceEditMp4): document Firmware 13.42 access behavior for unwritable parameter pointers --- docs/dev/debt/libSceEditMp4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceEditMp4.md b/docs/dev/debt/libSceEditMp4.md index d7f13cc074..58c43788d1 100644 --- a/docs/dev/debt/libSceEditMp4.md +++ b/docs/dev/debt/libSceEditMp4.md @@ -7,4 +7,4 @@ ### Unknown function info - [sceEditMp4Abort, sceEditMp4Edit, sceEditMp4GetStatus, sceEditMp4Initialize, sceEditMp4SetFinishCallback, sceEditMp4Terminate and sceEditMp4Unite](../../../core/libs/prx/libSceEditMp4/Export.cpp) (libSceEditMp4) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceEditMp4.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified -- [sceEditMp4InitializeParam](../../../core/libs/prx/libSceEditMp4/Export.cpp) (libSceEditMp4) - the size of the parameter structure and the meaning of its fields are unknown. It follows firmware 13.42's libSceEditMp4.sprx, read from its disassembly and run natively on x86-64 (not a console): it returns nothing, ignores a null pointer and otherwise clears bytes 0 to 15, stores the 64-bit value 0x3f at offset 16 and the 32-bit value 700 at offset 24, and leaves the bytes from offset 28 unchanged; ignoring an unmapped or unwritable pointer is a defensive host-protection fallback, and console behavior for unwritable memory has not been measured. +- [sceEditMp4InitializeParam](../../../core/libs/prx/libSceEditMp4/Export.cpp) (libSceEditMp4) - the size of the parameter structure and the meaning of its fields are unknown. It follows firmware 13.42's libSceEditMp4.sprx, read from its disassembly and run natively on x86-64 (not a console): it returns nothing, ignores a null pointer and otherwise clears bytes 0 to 15, stores the 64-bit value 0x3f at offset 16 and the 32-bit value 700 at offset 24, and leaves the bytes from offset 28 unchanged; Firmware 13.42 checks only for null and accesses the structure directly (read from disassembly, not measured on a console), so the console faults on an inaccessible non-null pointer; AnyPS5 skips the write and returns normally instead. From 0d864d92897058800082db1b37d0ca9bade1d376 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 19:29:57 +0530 Subject: [PATCH 267/612] docs(libSceHmd): document Firmware 13.42 store behavior for unwritable device query pointers --- docs/dev/debt/libSceHmd.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceHmd.md b/docs/dev/debt/libSceHmd.md index b26b01e896..d3b96d9387 100644 --- a/docs/dev/debt/libSceHmd.md +++ b/docs/dev/debt/libSceHmd.md @@ -2,7 +2,7 @@ ### Silent stubs -- [libSceHmd](../../../core/libs/prx/libSceHmd/Export.cpp) implements only the disconnected-headset path: initialization succeeds, device queries report `NotDetected`, and opening a device returns `DeviceDisconnected`. Returning ParameterNull for unwritable pointers in sceHmdGetDeviceInformation and sceHmdInternalGetDeviceStatus is a defensive host-protection fallback and console behavior is unmeasured. Headset support, tracking, rendering and additional HMD exports are not implemented; SDK-level ABI compatibility and in-game behaviour remain unverified. +- [libSceHmd](../../../core/libs/prx/libSceHmd/Export.cpp) implements only the disconnected-headset path: initialization succeeds, device queries report `NotDetected`, and opening a device returns `DeviceDisconnected`. Firmware 13.42 checks only for null and stores directly (read from disassembly, not measured on a console), so the console faults on an unwritable non-null pointer in sceHmdGetDeviceInformation and sceHmdInternalGetDeviceStatus; AnyPS5 returns ParameterNull (0x81110008) instead. Headset support, tracking, rendering and additional HMD exports are not implemented; SDK-level ABI compatibility and in-game behaviour remain unverified. ### Unknown function info From 9d31b51b4b91a9fc233cbd02f9268969bf320d55 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 19:31:46 +0530 Subject: [PATCH 268/612] docs(libSceAudioIn): document Firmware 13.42 store behavior for unwritable gain pointers --- docs/dev/debt/libSceAudioIn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceAudioIn.md b/docs/dev/debt/libSceAudioIn.md index 87b49e81a9..bdaecbd688 100644 --- a/docs/dev/debt/libSceAudioIn.md +++ b/docs/dev/debt/libSceAudioIn.md @@ -49,4 +49,4 @@ - [sceAudioInIsPadEmbeddedMic](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - returns 0 for every open port: on the console it returns 1 when the port captures from the controller's built-in microphone and 0 for another input device, but ports here capture from the host's default input device - [sceAudioInHqOpen, sceAudioInAsyncOpen](../../../core/libs/prx/libSceAudioIn/Export.cpp) - PS5 wrapper validation requires 48000 Hz followed by exactly 128 samples for both calls; HqOpen then requires format 2. The host uses the same SDL capture path for both, so native asynchronous scheduling and high-quality processing are not reproduced. Common port validation, device selection and host capture pacing remain incomplete. One title (PPSA03355, in its libfmod plugin) disassembled for this entry calls `sceAudioInAsyncOpen` from one call site (0xe5972) with index 0, 128 samples, 48000 Hz and a last argument of 2, plus a type argument of 0 or 1, which agrees with the 48000 Hz and 128 samples above; it stores the result in a slot of its own and branches on a negative result. What the last argument 2 means is not shown. -- [sceAudioInGetGain, sceAudioInGetRerouteCount](../../../core/libs/prx/libSceAudioIn/Export.cpp) - GetGain writes 1.0f before handle validation and returns 0x80260105 for a null or unwritable output, as confirmed by the PS5 module for null; returning 0x80260105 for an unwritable pointer is a defensive host-protection fallback and console behavior is unmeasured. Native handle bit checks and uninitialized/inactive port errors differ from the host port table. Native reroute counters follow device reassignment; the host opens each SDL device once and reports zero. +- [sceAudioInGetGain, sceAudioInGetRerouteCount](../../../core/libs/prx/libSceAudioIn/Export.cpp) - GetGain writes 1.0f before handle validation and returns 0x80260105 for a null or unwritable output, as confirmed by the PS5 module for null; Firmware 13.42 checks only for null and stores directly (read from disassembly, not measured on a console), so the console faults on an unwritable non-null pointer; AnyPS5 returns INVALID_POINTER (0x80260105) instead. Native handle bit checks and uninitialized/inactive port errors differ from the host port table. Native reroute counters follow device reassignment; the host opens each SDL device once and reports zero. From cb85065a26524f842c363979ac60e841edd1898f Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 19:33:18 +0530 Subject: [PATCH 269/612] docs(libSceUsbStorageDialog): document Firmware 13.42 access behavior for inaccessible result pointers --- docs/dev/debt/libSceUsbStorageDialog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceUsbStorageDialog.md b/docs/dev/debt/libSceUsbStorageDialog.md index a4554af141..f228f25df9 100644 --- a/docs/dev/debt/libSceUsbStorageDialog.md +++ b/docs/dev/debt/libSceUsbStorageDialog.md @@ -2,7 +2,7 @@ ### Silent stubs -- [libSceUsbStorageDialog](../../../core/libs/prx/libSceUsbStorageDialog/Export.cpp) (libSceUsbStorageDialog) - the USB storage dialog runs the common-dialog state machine without showing a UI and reports the dialog as canceled by the user (`COMMON_DIALOG_RESULT_USER_CANCELED = 1`). `sceUsbStorageDialogOpen` transitions to `RUNNING` (2), while `sceUsbStorageDialogUpdateStatus` and `sceUsbStorageDialogClose` transition to `FINISHED` (3) before returning results, matching other common dialogs in the repository; returning `COMMON_DIALOG_ERROR_INVALID_ARGUMENT` for an unreadable or unwritable result is a host-safety fallback as console behavior for unwritable memory has not been measured. +- [libSceUsbStorageDialog](../../../core/libs/prx/libSceUsbStorageDialog/Export.cpp) (libSceUsbStorageDialog) - the USB storage dialog runs the common-dialog state machine without showing a UI and reports the dialog as canceled by the user (`COMMON_DIALOG_RESULT_USER_CANCELED = 1`). `sceUsbStorageDialogOpen` transitions to `RUNNING` (2), while `sceUsbStorageDialogUpdateStatus` and `sceUsbStorageDialogClose` transition to `FINISHED` (3) before returning results, matching other common dialogs in the repository; Firmware 13.42 checks only for null and accesses the structure directly (read from disassembly, not measured on a console), so the console faults on an inaccessible non-null pointer; AnyPS5 returns COMMON_DIALOG_ERROR_INVALID_ARGUMENT instead. ### Unknown function info From 54f9bdb4c2f54959bbf6e4f9903c13dee95026a5 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 19:35:06 +0530 Subject: [PATCH 270/612] docs(libSceNpCommerce): document Firmware 13.42 access behavior for inaccessible result pointers --- docs/dev/debt/libSceNpCommerce.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceNpCommerce.md b/docs/dev/debt/libSceNpCommerce.md index 79e2d1976f..396198322f 100644 --- a/docs/dev/debt/libSceNpCommerce.md +++ b/docs/dev/debt/libSceNpCommerce.md @@ -2,7 +2,7 @@ ### Silent stubs -- [sceNpCommerceDialogGetResult](../../../core/libs/prx/libSceNpCommerce/Export.cpp) (libSceNpCommerce) - returning `COMMON_DIALOG_ERROR_INVALID_ARGUMENT` for an unwritable result is a host-safety fallback as console behavior for unwritable memory has not been measured. +- [sceNpCommerceDialogGetResult](../../../core/libs/prx/libSceNpCommerce/Export.cpp) (libSceNpCommerce) - Firmware 13.42 checks only for null and accesses the structure directly (read from disassembly, not measured on a console), so the console faults on an inaccessible non-null pointer; AnyPS5 returns COMMON_DIALOG_ERROR_INVALID_ARGUMENT instead. - [sceNpCommerceDialogOpen, sceNpCommerceDialogOpen2](../../../core/libs/prx/libSceNpCommerce/Export.cpp) (libSceNpCommerce) - with `ANYPS5_PSN_DIALOGS=show`, a successful open shows AnyPS5's notice that the feature needs PlayStation Network; the console's own display without PlayStation Network is not measured, and the notice wording is AnyPS5's. - [libSceNpCommerce](../../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls and `sceNpCommerceSetPsStoreIconLayout` do nothing From ebd650ca693b6e346303f7d5691fb923068473d5 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 19:44:08 +0530 Subject: [PATCH 271/612] test(libSceVrHand): add page-boundary coverage and qualify partial copyout in debt doc --- core/libs/tests/GuestVrHand.cpp | 58 +++++++++++++++++++++++++++++++++ docs/dev/debt/libSceVrHand.md | 2 +- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/core/libs/tests/GuestVrHand.cpp b/core/libs/tests/GuestVrHand.cpp index ff21ab3290..f3f9b168c7 100644 --- a/core/libs/tests/GuestVrHand.cpp +++ b/core/libs/tests/GuestVrHand.cpp @@ -3,6 +3,13 @@ #include #include #include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#include +#endif namespace { @@ -11,6 +18,39 @@ void Require(bool value) { if (!value) std::abort(); } constexpr int kErrInvalidArg = static_cast(0x8a870006); constexpr std::uint8_t kFill = 0xaa; +struct GuardedRegion { + std::size_t pageSize{0}; + unsigned char* memory{nullptr}; + + GuardedRegion() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + pageSize = static_cast(info.dwPageSize); + memory = static_cast(VirtualAlloc(nullptr, pageSize * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + Require(memory != nullptr); + DWORD previous; + Require(VirtualProtect(memory + pageSize, pageSize, PAGE_NOACCESS, &previous) != 0); +#else + const long nativePageSize = sysconf(_SC_PAGESIZE); + Require(nativePageSize > 0); + pageSize = static_cast(nativePageSize); + void* allocation = mmap(nullptr, pageSize * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(allocation != MAP_FAILED); + memory = static_cast(allocation); + Require(mprotect(memory + pageSize, pageSize, PROT_NONE) == 0); +#endif + } + + ~GuardedRegion() { +#ifdef _WIN32 + VirtualFree(memory, 0, MEM_RELEASE); +#else + munmap(memory, pageSize * 2); +#endif + } +}; + std::array ValidParam() { std::array param{}; param[0] = 0x20; @@ -40,6 +80,24 @@ int main() { Require(sceVrHandQueryMemory(nullptr, out.data()) == kErrInvalidArg && Untouched(out)); Require(sceVrHandQueryMemory(param.data(), nullptr) == kErrInvalidArg); + GuardedRegion region; + auto* const boundaryParam = region.memory + region.pageSize - 0x20; + std::memset(boundaryParam, 0, 0x20); + boundaryParam[0] = 0x20; + out.fill(kFill); + Require(sceVrHandQueryMemory(boundaryParam, out.data()) == 0); + + auto* const spanningParam = region.memory + region.pageSize - 0x10; + out.fill(kFill); + Require(sceVrHandQueryMemory(spanningParam, out.data()) == kErrInvalidArg && Untouched(out)); + + auto* const boundaryOut = region.memory + region.pageSize - 28; + std::memset(boundaryOut, kFill, 28); + Require(sceVrHandQueryMemory(param.data(), boundaryOut) == 0); + + auto* const spanningOut = region.memory + region.pageSize - 8; + Require(sceVrHandQueryMemory(param.data(), spanningOut) == kErrInvalidArg); + for (const std::uint32_t size : {0u, 0x1fu, 0x21u, 0x40u, 0x120u, 0x10020u, 0x1000020u}) { param = ValidParam(); std::memcpy(param.data(), &size, sizeof(size)); diff --git a/docs/dev/debt/libSceVrHand.md b/docs/dev/debt/libSceVrHand.md index c91567c01d..e0348a8e35 100644 --- a/docs/dev/debt/libSceVrHand.md +++ b/docs/dev/debt/libSceVrHand.md @@ -3,4 +3,4 @@ ### Unknown function info - [sceVrHandFinalize, sceVrHandGetMesh, sceVrHandGetMeshSize, sceVrHandGetResult, sceVrHandInitialize, sceVrHandStart, sceVrHandStop](../../../core/libs/prx/libSceVrHand/Export.cpp) (libSceVrHand) - names and NIDs verified from pinned [ps5rs stubs](https://github.com/claimore22/ps5rs/tree/5f252a658ff470bcb2b6cd15205fd8ca64ae4477/data/stubs/by_library) (`libSceVrHand.txt`), with NID match confirmed via `tools/nid_names.py` `compute_nid`. Signatures, parameter layouts, and runtime behavior remain unverified; all paths throw through `NotImplemented_nid_no_patch`. -- [sceVrHandQueryMemory](../../../core/libs/prx/libSceVrHand/Export.cpp) (libSceVrHand) - the meaning of the parameter and output fields is unknown. It follows firmware 13.42's libSceVrHand.sprx, read from its disassembly and run natively on x86-64 (not a console): a null pointer, unreadable or unwritable pointers, a first 32-bit parameter word other than 0x20, or any nonzero parameter byte from offset 4 to 31 returns `0x8a870006` and writes nothing; otherwise it stores the 32-bit values 0x3c00000 and 0x10000 at output offsets 0 and 4, clears output bytes 8 to 27 and returns 0. Returning `0x8a870006` for unwritable or unreadable pointers is a defensive host-protection fallback; console behavior is unmeasured. +- [sceVrHandQueryMemory](../../../core/libs/prx/libSceVrHand/Export.cpp) (libSceVrHand) - the meaning of the parameter and output fields is unknown. It follows firmware 13.42's libSceVrHand.sprx, read from its disassembly and run natively on x86-64 (not a console): a null pointer, an unreadable parameter, a first 32-bit parameter word other than 0x20, or any nonzero parameter byte from offset 4 to 31 returns `0x8a870006` and writes nothing; an unwritable result pointer returns `0x8a870006` (with possible partial output on copy-out failure). Otherwise it stores the 32-bit values 0x3c00000 and 0x10000 at output offsets 0 and 4, clears output bytes 8 to 27 and returns 0. Returning `0x8a870006` for unwritable or unreadable pointers is a defensive host-protection fallback; console behavior is unmeasured. From 5608180dabd18c2c1311f193d0f560888427ec14 Mon Sep 17 00:00:00 2001 From: zaryabzip <110787259+zaryabzip@users.noreply.github.com> Date: Mon, 2 Nov 2026 20:27:46 +0500 Subject: [PATCH 272/612] feat(libSceMouse): answer the handle, extension and driver calls as firmware 13.42 does From @BunnyFl's disassembly of the 13.42 libSceMouse.sprx (#15088): sceMouseGetHandle returns the handle sceMouseOpen gave a user and type, sceMouseExtensionSetConfig checks and stores its 32-byte config, and sceMouseExtensionScalePosition rounds position * (scale - 1) / 65535. GetDeviceInfo, SetHandType, SetPointerSpeed, ExtensionRead, ExtensionReadState, ExtensionSetCursor and ExtensionGetCursor get their argument lists and checks, and throw where the console asks the mouse driver or the extension record layout is unknown. --- core/libs/prx/libSceMouse/CMakeLists.txt | 1 - core/libs/prx/libSceMouse/Export.cpp | 65 +++++++++++++++++-- .../libs/prx/libSceMouse/InventoryExports.cpp | 34 ---------- core/libs/prx/libSceMouse/include/Api.hpp | 13 +++- .../libSceMouse/include/InventoryExports.hpp | 15 ----- .../prx/libSceMouse/include/MouseState.hpp | 5 ++ .../prx/libSceMouse/include/mouse_structs.h | 6 ++ core/libs/prx/libSceMouse/libSceMouse.hpp | 1 - core/libs/prx/libSceMouse/src/mouse_impl.cpp | 50 +++++++++++++- docs/dev/debt/libSceMouse.md | 21 +++--- 10 files changed, 137 insertions(+), 74 deletions(-) delete mode 100644 core/libs/prx/libSceMouse/InventoryExports.cpp delete mode 100644 core/libs/prx/libSceMouse/include/InventoryExports.hpp diff --git a/core/libs/prx/libSceMouse/CMakeLists.txt b/core/libs/prx/libSceMouse/CMakeLists.txt index 0942cb64eb..c7752999a1 100644 --- a/core/libs/prx/libSceMouse/CMakeLists.txt +++ b/core/libs/prx/libSceMouse/CMakeLists.txt @@ -1,5 +1,4 @@ add_library(libSceMouse SHARED EXCLUDE_FROM_ALL - ${CMAKE_CURRENT_SOURCE_DIR}/InventoryExports.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/mouse_impl.cpp ) diff --git a/core/libs/prx/libSceMouse/Export.cpp b/core/libs/prx/libSceMouse/Export.cpp index 7fc6bae2e4..a473646e65 100644 --- a/core/libs/prx/libSceMouse/Export.cpp +++ b/core/libs/prx/libSceMouse/Export.cpp @@ -2,6 +2,9 @@ #include "prx/libSceMouse/include/MouseState.hpp" #include "prx/libc/include/General.hpp" +#include +#include + extern "C" { int APS5_VABI sceMouseInit() { @@ -57,9 +60,11 @@ int APS5_VABI sceMouseDisconnectPort() { return 0; } -int APS5_VABI sceMouseGetDeviceInfo() { +int APS5_VABI sceMouseGetDeviceInfo(std::int32_t device, void* info) { + static_cast(device); + if (!Mouse::IsInitialized()) return MOUSE_ERROR_NOT_INITIALIZED; + if (info == nullptr) return MOUSE_ERROR_INVALID_ARG; NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI sceMouseMbusInit() { @@ -67,14 +72,18 @@ int APS5_VABI sceMouseMbusInit() { return 0; } -int APS5_VABI sceMouseSetHandType() { +int APS5_VABI sceMouseSetHandType(std::int32_t handle, std::uint32_t type) { + if (!Mouse::IsInitialized()) return MOUSE_ERROR_NOT_INITIALIZED; + if (type > MOUSE_MAX_HAND_TYPE) return MOUSE_ERROR_INVALID_ARG; + static_cast(handle); NotImplemented_nid_no_patch(__func__); - return 0; } -int APS5_VABI sceMouseSetPointerSpeed() { +int APS5_VABI sceMouseSetPointerSpeed(std::int32_t handle, std::uint32_t speed) { + if (!Mouse::IsInitialized()) return MOUSE_ERROR_NOT_INITIALIZED; + if (speed > MOUSE_MAX_POINTER_SPEED) return MOUSE_ERROR_INVALID_ARG; + static_cast(handle); NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI sceMouseSetProcessPrivilege() { @@ -94,4 +103,48 @@ int APS5_VABI sceMouseSetAbsoluteVirtualScreenSize(std::int32_t handle, std::int return Mouse::SetAbsoluteVirtualScreenSize(handle, size); } +int APS5_VABI sceMouseGetHandle(int userId, std::int32_t type, std::int32_t index) { + static_cast(index); + return Mouse::GetHandle(userId, type); +} + +int APS5_VABI sceMouseExtensionRead(std::int32_t handle, void* data, std::uint32_t size, std::int32_t num) { + if (size != MOUSE_EXTENSION_DATA_SIZE) return MOUSE_ERROR_INVALID_ARG; + if (const int error = Mouse::CheckRead(handle, data, num)) return error; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceMouseExtensionReadState(std::int32_t handle, void* state, std::uint32_t size) { + if (size != MOUSE_EXTENSION_STATE_SIZE || state == nullptr) return MOUSE_ERROR_INVALID_ARG; + std::memset(state, 0, MOUSE_EXTENSION_STATE_SIZE); + if (const int error = Mouse::CheckRead(handle, state, MOUSE_MAX_DATA_NUM)) return error; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceMouseExtensionSetConfig(std::int32_t handle, const void* config) { + return Mouse::SetExtensionConfig(handle, static_cast(config)); +} + +int APS5_VABI sceMouseExtensionSetCursor(std::int32_t handle, std::int32_t cursor) { + if (!Mouse::IsInitialized()) return MOUSE_ERROR_NOT_INITIALIZED; + if (const int error = Mouse::CheckHandle(handle)) return error; + static_cast(cursor); + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceMouseExtensionGetCursor(std::int32_t handle, void* cursor) { + if (!Mouse::IsInitialized()) return MOUSE_ERROR_NOT_INITIALIZED; + if (cursor == nullptr) return MOUSE_ERROR_INVALID_ARG; + if (const int error = Mouse::CheckHandle(handle)) return error; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceMouseExtensionScalePosition(std::uint16_t* position, std::uint16_t scale) { + if (!Mouse::IsInitialized()) return MOUSE_ERROR_NOT_INITIALIZED; + if (position == nullptr || scale == 0) return MOUSE_ERROR_INVALID_ARG; + const std::uint32_t scaled = static_cast(*position) * (scale - 1u); + *position = static_cast((scaled + 0x7fffu) / 0xffffu); + return MOUSE_OK; +} + } diff --git a/core/libs/prx/libSceMouse/InventoryExports.cpp b/core/libs/prx/libSceMouse/InventoryExports.cpp deleted file mode 100644 index d035ccbd52..0000000000 --- a/core/libs/prx/libSceMouse/InventoryExports.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "prx/libSceMouse/include/InventoryExports.hpp" -#include "prx/libc/include/General.hpp" - -extern "C" { - -int APS5_VABI sceMouseExtensionGetCursor() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceMouseExtensionRead() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceMouseExtensionReadState() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceMouseExtensionScalePosition() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceMouseExtensionSetConfig() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceMouseExtensionSetCursor() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceMouseGetHandle() { - NotImplemented_nid_no_patch(__func__); -} - -} diff --git a/core/libs/prx/libSceMouse/include/Api.hpp b/core/libs/prx/libSceMouse/include/Api.hpp index 6ed1540333..7bdb53f3ca 100644 --- a/core/libs/prx/libSceMouse/include/Api.hpp +++ b/core/libs/prx/libSceMouse/include/Api.hpp @@ -15,13 +15,20 @@ int APS5_VABI sceMouseDebugGetDeviceId(); int APS5_VABI sceMouseDeviceOpen(); int APS5_VABI sceMouseDisconnectDevice(); int APS5_VABI sceMouseDisconnectPort(); -int APS5_VABI sceMouseGetDeviceInfo(); +int APS5_VABI sceMouseGetDeviceInfo(std::int32_t device, void* info); int APS5_VABI sceMouseMbusInit(); -int APS5_VABI sceMouseSetHandType(); -int APS5_VABI sceMouseSetPointerSpeed(); +int APS5_VABI sceMouseSetHandType(std::int32_t handle, std::uint32_t type); +int APS5_VABI sceMouseSetPointerSpeed(std::int32_t handle, std::uint32_t speed); int APS5_VABI sceMouseSetProcessPrivilege(); int APS5_VABI sceMouseReadAbsolutePosition(std::int32_t handle, MouseAbsoluteData* data, std::int32_t num); int APS5_VABI sceMouseSetAbsoluteHighPrecisionMode(std::int32_t handle, std::int32_t mode); int APS5_VABI sceMouseSetAbsoluteVirtualScreenSize(std::int32_t handle, std::int32_t size); +int APS5_VABI sceMouseGetHandle(int userId, std::int32_t type, std::int32_t index); +int APS5_VABI sceMouseExtensionRead(std::int32_t handle, void* data, std::uint32_t size, std::int32_t num); +int APS5_VABI sceMouseExtensionReadState(std::int32_t handle, void* state, std::uint32_t size); +int APS5_VABI sceMouseExtensionSetConfig(std::int32_t handle, const void* config); +int APS5_VABI sceMouseExtensionSetCursor(std::int32_t handle, std::int32_t cursor); +int APS5_VABI sceMouseExtensionGetCursor(std::int32_t handle, void* cursor); +int APS5_VABI sceMouseExtensionScalePosition(std::uint16_t* position, std::uint16_t scale); } diff --git a/core/libs/prx/libSceMouse/include/InventoryExports.hpp b/core/libs/prx/libSceMouse/include/InventoryExports.hpp deleted file mode 100644 index cc642d4bb4..0000000000 --- a/core/libs/prx/libSceMouse/include/InventoryExports.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "SceTypes.hpp" - -extern "C" { - -int APS5_VABI sceMouseExtensionGetCursor(); -int APS5_VABI sceMouseExtensionRead(); -int APS5_VABI sceMouseExtensionReadState(); -int APS5_VABI sceMouseExtensionScalePosition(); -int APS5_VABI sceMouseExtensionSetConfig(); -int APS5_VABI sceMouseExtensionSetCursor(); -int APS5_VABI sceMouseGetHandle(); - -} diff --git a/core/libs/prx/libSceMouse/include/MouseState.hpp b/core/libs/prx/libSceMouse/include/MouseState.hpp index b6a161f68d..25e219071f 100644 --- a/core/libs/prx/libSceMouse/include/MouseState.hpp +++ b/core/libs/prx/libSceMouse/include/MouseState.hpp @@ -23,6 +23,11 @@ int Initialize(); int Open(int userId, std::int32_t type, std::int32_t index, const MouseOpenParam* param); int Close(std::int32_t handle); int Read(std::int32_t handle, MouseData* data, std::int32_t num); +int CheckRead(std::int32_t handle, const void* data, std::int32_t num); +int CheckHandle(std::int32_t handle); +bool IsInitialized(); +int GetHandle(int userId, std::int32_t type); +int SetExtensionConfig(std::int32_t handle, const std::uint8_t* config); int ResetAbsolutePosition(std::int32_t handle, std::uint16_t x, std::uint16_t y); int SetAbsoluteVirtualScreenSize(std::int32_t handle, std::int32_t size); int SetAbsoluteHighPrecisionMode(std::int32_t handle, std::int32_t mode); diff --git a/core/libs/prx/libSceMouse/include/mouse_structs.h b/core/libs/prx/libSceMouse/include/mouse_structs.h index a1da9bb48d..f1280a351c 100644 --- a/core/libs/prx/libSceMouse/include/mouse_structs.h +++ b/core/libs/prx/libSceMouse/include/mouse_structs.h @@ -27,8 +27,14 @@ constexpr int MOUSE_ERROR_INVALID_ARG = static_cast(0x80df0001u); constexpr int MOUSE_ERROR_INVALID_HANDLE = static_cast(0x80df0003u); constexpr int MOUSE_ERROR_ALREADY_OPENED = static_cast(0x80df0004u); constexpr int MOUSE_ERROR_NOT_INITIALIZED = static_cast(0x80df0005u); +constexpr int MOUSE_ERROR_NO_HANDLE = static_cast(0x80df0008u); constexpr int MOUSE_MAX_DATA_NUM = 64; constexpr int MOUSE_HANDLE = 1; +constexpr std::uint32_t MOUSE_MAX_HAND_TYPE = 1; +constexpr std::uint32_t MOUSE_MAX_POINTER_SPEED = 8; +constexpr std::uint32_t MOUSE_EXTENSION_DATA_SIZE = 0x30; +constexpr std::uint32_t MOUSE_EXTENSION_STATE_SIZE = 0x40; +constexpr std::size_t MOUSE_EXTENSION_CONFIG_SIZE = 0x20; constexpr std::uint8_t MOUSE_OPEN_PARAM_MERGED = 1; static_assert(sizeof(MouseOpenParam) == 8); diff --git a/core/libs/prx/libSceMouse/libSceMouse.hpp b/core/libs/prx/libSceMouse/libSceMouse.hpp index 9c094bb6b4..512051b814 100644 --- a/core/libs/prx/libSceMouse/libSceMouse.hpp +++ b/core/libs/prx/libSceMouse/libSceMouse.hpp @@ -1,4 +1,3 @@ #pragma once #include "prx/libSceMouse/include/Api.hpp" -#include "prx/libSceMouse/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceMouse/src/mouse_impl.cpp b/core/libs/prx/libSceMouse/src/mouse_impl.cpp index ac1b19e9d8..c7fe5f6c78 100644 --- a/core/libs/prx/libSceMouse/src/mouse_impl.cpp +++ b/core/libs/prx/libSceMouse/src/mouse_impl.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace { @@ -14,6 +15,9 @@ int head = 0; int count = 0; bool initialized = false; bool opened = false; +int openedUser = 0; +std::uint32_t extensionConfig = 0; +std::uint8_t extensionFlags = 0; bool connected = true; std::uint32_t buttons = 0; constexpr std::array SCREEN_WIDTHS{1280, 1920, 2560, 3840}; @@ -135,6 +139,9 @@ int Open(int userId, std::int32_t type, std::int32_t index, const MouseOpenParam } if (opened) return MOUSE_ERROR_ALREADY_OPENED; opened = true; + openedUser = userId; + extensionConfig = 0; + extensionFlags = 0; buttons = 0; head = 0; count = 0; @@ -167,11 +174,50 @@ int Close(std::int32_t handle) { return MOUSE_OK; } -int Read(std::int32_t handle, MouseData* data, std::int32_t num) { - std::lock_guard lock(mouseMutex); +int checkRead(std::int32_t handle, const void* data, std::int32_t num) { if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE; if (data == nullptr || num <= 0 || num > MOUSE_MAX_DATA_NUM) return MOUSE_ERROR_INVALID_ARG; + return MOUSE_OK; +} + +int CheckRead(std::int32_t handle, const void* data, std::int32_t num) { + std::lock_guard lock(mouseMutex); + return checkRead(handle, data, num); +} + +int CheckHandle(std::int32_t handle) { + std::lock_guard lock(mouseMutex); + return handle == MOUSE_HANDLE && opened ? MOUSE_OK : MOUSE_ERROR_INVALID_HANDLE; +} + +bool IsInitialized() { + std::lock_guard lock(mouseMutex); + return initialized; +} + +int GetHandle(int userId, std::int32_t type) { + std::lock_guard lock(mouseMutex); + if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; + return opened && openedUser == userId && type == 0 ? MOUSE_HANDLE : MOUSE_ERROR_NO_HANDLE; +} + +int SetExtensionConfig(std::int32_t handle, const std::uint8_t* config) { + std::lock_guard lock(mouseMutex); + if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; + if (config == nullptr) return MOUSE_ERROR_INVALID_ARG; + for (std::size_t offset = 5; offset < MOUSE_EXTENSION_CONFIG_SIZE; ++offset) { + if (config[offset] != 0) return MOUSE_ERROR_INVALID_ARG; + } + if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE; + std::memcpy(&extensionConfig, config, sizeof(extensionConfig)); + extensionFlags = config[4]; + return MOUSE_OK; +} + +int Read(std::int32_t handle, MouseData* data, std::int32_t num) { + std::lock_guard lock(mouseMutex); + if (const int error = checkRead(handle, data, num)) return error; const int available = std::min(count, num); for (int i = 0; i < available; ++i) { data[i] = history[head]; diff --git a/docs/dev/debt/libSceMouse.md b/docs/dev/debt/libSceMouse.md index dd7008853c..bf95d9a293 100644 --- a/docs/dev/debt/libSceMouse.md +++ b/docs/dev/debt/libSceMouse.md @@ -2,22 +2,19 @@ ### Unknown function info -- [libSceMouse additional exports](../../../core/libs/prx/libSceMouse/InventoryExports.cpp) - these name/NID pairs are present in the 13.60 export inventory. Parameter lists, return types and behavior are unverified. Parameterless `int` declarations are import-resolution placeholders, not verified ABI prototypes; every entry throws through `NotImplemented_nid_no_patch`. No runtime functionality is implemented. +- [sceMouseGetDeviceInfo, sceMouseSetHandType, sceMouseSetPointerSpeed and the extension calls](../../../core/libs/prx/libSceMouse/Export.cpp) (libSceMouse) - argument lists and checks follow @BunnyFl's [firmware 13.42 disassembly](https://github.com/boykopovar/AnyPS5/issues/15088#issuecomment-6580877653) of `libSceMouse.sprx` (SHA-256 `e07cd04766ea48f5216e40fd4397758f640a7200cfa7edb6a00255326bd33c61`), not a console. Past their checks they throw: + - `GetDeviceInfo(int32, void*)`, `SetHandType(handle, uint32)`, `SetPointerSpeed(handle, uint32)`, `ExtensionSetCursor(handle, int32)` and `ExtensionGetCursor(handle, void*)` end in a mouse driver ioctl, which isn't modelled. `GetDeviceInfo`'s first check, a closed device fd, is taken as not initialised here. + - `ExtensionRead(handle, void*, uint32 size, int32 num)` and `ExtensionReadState(handle, void*, uint32 size)` run `sceMouseRead`'s checks with the extension flag, after their size checks (0x30 and 0x40) and `ReadState`'s 64-byte clear; the 0x30-byte extension record and the state the slot keeps at +0x100 are unknown. -| Name | NID | -| --- | --- | -| `sceMouseExtensionGetCursor` | `UBO+tFHvcyE` | -| `sceMouseExtensionRead` | `doRbAW89lZE` | -| `sceMouseExtensionReadState` | `CvUnq1gsWMg` | -| `sceMouseExtensionScalePosition` | `GE+ZU2Y9pEs` | -| `sceMouseExtensionSetConfig` | `T4BHUjH773k` | -| `sceMouseExtensionSetCursor` | `2KVa2-HqIRs` | -| `sceMouseGetHandle` | `P+9UQOe1F2g` | - -- [sceMouseConnectPort, sceMouseDebugGetDeviceId, sceMouseDeviceOpen, sceMouseDisconnectDevice, sceMouseDisconnectPort, sceMouseGetDeviceInfo, sceMouseMbusInit, sceMouseSetHandType, sceMouseSetPointerSpeed and sceMouseSetProcessPrivilege](../../../core/libs/prx/libSceMouse/Export.cpp) (libSceMouse) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceMouse.c); the no-argument prototypes follow [shadPS4's PS4 stubs](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/mouse/mouse.cpp#L34-L146), not measured on a console. These declarations have no parameters solely to resolve known imports and make no ABI claim. +- [sceMouseConnectPort, sceMouseDebugGetDeviceId, sceMouseDeviceOpen, sceMouseDisconnectDevice, sceMouseDisconnectPort, sceMouseMbusInit and sceMouseSetProcessPrivilege](../../../core/libs/prx/libSceMouse/Export.cpp) (libSceMouse) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceMouse.c); the no-argument prototypes follow [shadPS4's PS4 stubs](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/mouse/mouse.cpp#L34-L146), not measured on a console. These declarations have no parameters solely to resolve known imports and make no ABI claim. ### Functional +- [sceMouseGetHandle, sceMouseExtensionSetConfig and sceMouseExtensionScalePosition](../../../core/libs/prx/libSceMouse/src/mouse_impl.cpp) (libSceMouse) - follow the same [13.42 disassembly](https://github.com/boykopovar/AnyPS5/issues/15088#issuecomment-6580877653), not a console; none reaches the driver. + - `GetHandle(userId, type, index)` returns the handle `sceMouseOpen` gave that user and type, or 0x80df0008. The console skips slots whose +0xc8 field is 1 when `index` is non-zero; that field's meaning is unknown, and no slot here has it. + - `ExtensionSetConfig(handle, config)` refuses a null or a non-zero byte in 5..0x1f, then stores the u32 at 0 and the byte at 4; nothing here reads them yet. + - `ExtensionScalePosition(uint16* position, uint16 scale)` rounds `*position * (scale - 1) / 65535` to the nearest integer, which has no ties since 65535 is odd. The console divides with a multiply by 0x80008001 and a shift by 47, which equals that floor division for every product of two 16-bit values, then rounds with a compare whose exact form wasn't checked. + - [Publish](../../../core/libs/prx/libSceMouse/src/mouse_impl.cpp) (libSceMouse) - consecutive motion-only reports with the same buttons are summed into the newest unread entry, so a title that reads a few entries per frame gets current motion instead of a full queue of old reports. Reports with no motion and no button, wheel, tilt or connection change are dropped. Per-report granularity and the console's own report rate are not reproduced - [sceMouseResetAbsolutePosition, sceMouseSetAbsoluteVirtualScreenSize, sceMouseSetAbsoluteHighPrecisionMode, sceMouseReadAbsolutePosition](../../../core/libs/prx/libSceMouse/src/mouse_impl.cpp) (libSceMouse) - follow firmware 13.42 [run natively](https://github.com/boykopovar/AnyPS5/issues/11477#issuecomment-6502712241) with the driver stubbed: checks and order, screen sizes and rescale, the mode byte, the curve with its fraction, the 40-byte record. Host choices, after [XatiX's title-tested version](https://github.com/boykopovar/AnyPS5/issues/11477#issuecomment-6503782633): each host event is one sample with unscaled counts, in its own queue, so `sceMouseRead`'s motion merging does not feed the curve. A read takes all pending samples; past `num`, same-button neighbours share a record, each still moving the pointer, and the last `num` records are kept. Speed is taken as 4, the hand swap and flagged bit never apply, and a disconnected mouse stands in for a zero driver status From 4bb8e183e82e019f2831858ce90807e02ef40e82 Mon Sep 17 00:00:00 2001 From: zaryabzip <110787259+zaryabzip@users.noreply.github.com> Date: Mon, 2 Nov 2026 20:27:58 +0500 Subject: [PATCH 273/612] test(libSceMouse): check the handle, extension and driver calls' answers and checks --- core/libs/prx/libSceMouse/CMakeLists.txt | 9 ++ .../prx/libSceMouse/tests/MouseQueries.cpp | 111 ++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 core/libs/prx/libSceMouse/tests/MouseQueries.cpp diff --git a/core/libs/prx/libSceMouse/CMakeLists.txt b/core/libs/prx/libSceMouse/CMakeLists.txt index c7752999a1..4747f96970 100644 --- a/core/libs/prx/libSceMouse/CMakeLists.txt +++ b/core/libs/prx/libSceMouse/CMakeLists.txt @@ -16,6 +16,15 @@ if(BUILD_TESTING) set_tests_properties(mouse_absolute_position PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(mouse_query_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/MouseQueries.cpp) + target_include_directories(mouse_query_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(mouse_query_tests PRIVATE libSceMouse libkernel libc) + configure_windows_unwind(mouse_query_tests) + add_test(NAME mouse_queries COMMAND mouse_query_tests) + if(WIN32) + set_tests_properties(mouse_queries PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() if(BUILD_TESTING) diff --git a/core/libs/prx/libSceMouse/tests/MouseQueries.cpp b/core/libs/prx/libSceMouse/tests/MouseQueries.cpp new file mode 100644 index 0000000000..d9cab431f7 --- /dev/null +++ b/core/libs/prx/libSceMouse/tests/MouseQueries.cpp @@ -0,0 +1,111 @@ +#include "prx/libSceMouse/libSceMouse.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include +#include + +namespace { + +constexpr int Ok = 0; +constexpr int InvalidArg = static_cast(0x80df0001u); +constexpr int InvalidHandle = static_cast(0x80df0003u); +constexpr int NotInitialized = static_cast(0x80df0005u); +constexpr int NoHandle = static_cast(0x80df0008u); + +using GetHandleFn = int (APS5_VABI*)(int, std::int32_t, std::int32_t); +using DeviceInfoFn = int (APS5_VABI*)(std::int32_t, void*); +using HandleValueFn = int (APS5_VABI*)(std::int32_t, std::uint32_t); +using ReadFn = int (APS5_VABI*)(std::int32_t, void*, std::uint32_t, std::int32_t); +using ReadStateFn = int (APS5_VABI*)(std::int32_t, void*, std::uint32_t); +using SetConfigFn = int (APS5_VABI*)(std::int32_t, const void*); +using SetCursorFn = int (APS5_VABI*)(std::int32_t, std::int32_t); +using GetCursorFn = int (APS5_VABI*)(std::int32_t, void*); +using ScaleFn = int (APS5_VABI*)(std::uint16_t*, std::uint16_t); + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "Mouse queries: %s\n", message); + std::abort(); +} + +template +Function As(Export function) { + return reinterpret_cast(function); +} + +} + +int main() { + const auto getHandle = As(&sceMouseGetHandle); + const auto getDeviceInfo = As(&sceMouseGetDeviceInfo); + const auto setHandType = As(&sceMouseSetHandType); + const auto setPointerSpeed = As(&sceMouseSetPointerSpeed); + const auto extensionRead = As(&sceMouseExtensionRead); + const auto extensionReadState = As(&sceMouseExtensionReadState); + const auto setConfig = As(&sceMouseExtensionSetConfig); + const auto setCursor = As(&sceMouseExtensionSetCursor); + const auto getCursor = As(&sceMouseExtensionGetCursor); + const auto scale = As(&sceMouseExtensionScalePosition); + + std::array buffer{}; + std::array config{}; + std::uint16_t position = 100; + + Require(getHandle(1, 0, 0) == NotInitialized, "GetHandle answered before sceMouseInit"); + Require(getDeviceInfo(0, buffer.data()) == NotInitialized, "GetDeviceInfo answered before sceMouseInit"); + Require(setHandType(1, 0) == NotInitialized && setPointerSpeed(1, 0) == NotInitialized, "SetHandType or SetPointerSpeed answered before sceMouseInit"); + Require(setConfig(1, config.data()) == NotInitialized, "ExtensionSetConfig answered before sceMouseInit"); + Require(setCursor(1, 0) == NotInitialized && getCursor(1, buffer.data()) == NotInitialized, "the cursor calls answered before sceMouseInit"); + Require(scale(&position, 1920) == NotInitialized && position == 100, "ExtensionScalePosition scaled before sceMouseInit"); + + Require(sceMouseInit() == Ok, "sceMouseInit failed"); + Require(getHandle(1, 0, 0) == NoHandle, "GetHandle found a handle before sceMouseOpen"); + const int handle = sceMouseOpen(1, 0, 0, nullptr); + Require(handle > 0, "sceMouseOpen failed"); + Require(getHandle(1, 0, 0) == handle && getHandle(1, 0, 1) == handle, "GetHandle didn't return the open handle for its user and type"); + Require(getHandle(2, 0, 0) == NoHandle && getHandle(1, 1, 0) == NoHandle, "GetHandle matched another user or type"); + + Require(scale(nullptr, 1920) == InvalidArg, "ExtensionScalePosition took a null position"); + Require(scale(&position, 0) == InvalidArg && position == 100, "ExtensionScalePosition took a scale of 0"); + const std::array, 6> rows{{{65535, 1920, 1919}, {32768, 1920, 960}, {0, 1920, 0}, {65535, 1, 0}, {1, 65535, 1}, {40000, 1080, 659}}}; + for (const auto& [input, size, expected] : rows) { + position = input; + Require(scale(&position, size) == Ok && position == expected, "ExtensionScalePosition didn't round position * (scale - 1) / 65535"); + } + + Require(setConfig(handle, nullptr) == InvalidArg, "ExtensionSetConfig took a null config"); + config[5] = 1; + Require(setConfig(handle, config.data()) == InvalidArg, "ExtensionSetConfig took a non-zero byte 5"); + config[5] = 0; + config[0x1f] = 1; + Require(setConfig(handle, config.data()) == InvalidArg, "ExtensionSetConfig took a non-zero byte 0x1f"); + config[0x1f] = 0; + config[0] = 0x12; + config[4] = 1; + Require(setConfig(handle + 1, config.data()) == InvalidHandle, "ExtensionSetConfig took an unopened handle"); + Require(setConfig(handle, config.data()) == Ok, "ExtensionSetConfig refused a valid config"); + + Require(setHandType(handle, 2) == InvalidArg, "SetHandType took a type above 1"); + Require(setPointerSpeed(handle, 9) == InvalidArg, "SetPointerSpeed took a speed above 8"); + Require(getDeviceInfo(0, nullptr) == InvalidArg, "GetDeviceInfo took a null output"); + + Require(extensionRead(handle, buffer.data(), 0x20, 1) == InvalidArg, "ExtensionRead took a record size other than 0x30"); + Require(extensionRead(handle + 1, buffer.data(), 0x30, 1) == InvalidHandle, "ExtensionRead took an unopened handle"); + Require(extensionRead(handle, nullptr, 0x30, 1) == InvalidArg && extensionRead(handle, buffer.data(), 0x30, 0) == InvalidArg, "ExtensionRead took a null buffer or a count of 0"); + Require(extensionReadState(handle, buffer.data(), 0x30) == InvalidArg && extensionReadState(handle, nullptr, 0x40) == InvalidArg, "ExtensionReadState took a size other than 0x40 or a null state"); + std::memset(buffer.data(), 0xaa, buffer.size()); + Require(extensionReadState(handle + 1, buffer.data(), 0x40) == InvalidHandle, "ExtensionReadState took an unopened handle"); + for (const auto byte : buffer) Require(byte == 0, "ExtensionReadState didn't clear the state before the handle check"); + + Require(setCursor(handle + 1, 0) == InvalidHandle, "ExtensionSetCursor took an unopened handle"); + Require(getCursor(handle, nullptr) == InvalidArg && getCursor(handle + 1, nullptr) == InvalidArg, "ExtensionGetCursor didn't check its output before the handle"); + Require(getCursor(handle + 1, buffer.data()) == InvalidHandle, "ExtensionGetCursor took an unopened handle"); + + Require(sceMouseClose(handle) == Ok, "sceMouseClose failed"); + Require(getHandle(1, 0, 0) == NoHandle, "GetHandle returned a closed handle"); + std::puts("Mouse query tests passed"); + return 0; +} From 2a970b5ebf6838c962fea63cc1662009d9bb0389 Mon Sep 17 00:00:00 2001 From: zaryabzip <110787259+zaryabzip@users.noreply.github.com> Date: Mon, 2 Nov 2026 21:03:33 +0500 Subject: [PATCH 274/612] fix(libSceMouse): keep the shared read check out of the exported symbols checkRead is a helper of CheckRead and Read, so it goes into an unnamed namespace instead of becoming a NID-patched export of the module. --- core/libs/prx/libSceMouse/src/mouse_impl.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/libs/prx/libSceMouse/src/mouse_impl.cpp b/core/libs/prx/libSceMouse/src/mouse_impl.cpp index c7fe5f6c78..101a4fafb1 100644 --- a/core/libs/prx/libSceMouse/src/mouse_impl.cpp +++ b/core/libs/prx/libSceMouse/src/mouse_impl.cpp @@ -174,12 +174,14 @@ int Close(std::int32_t handle) { return MOUSE_OK; } +namespace { int checkRead(std::int32_t handle, const void* data, std::int32_t num) { if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE; if (data == nullptr || num <= 0 || num > MOUSE_MAX_DATA_NUM) return MOUSE_ERROR_INVALID_ARG; return MOUSE_OK; } +} int CheckRead(std::int32_t handle, const void* data, std::int32_t num) { std::lock_guard lock(mouseMutex); From 669ad12e632709bdf01f92be5636935b89f77799 Mon Sep 17 00:00:00 2001 From: zaryabzip <110787259+zaryabzip@users.noreply.github.com> Date: Mon, 2 Nov 2026 21:10:54 +0500 Subject: [PATCH 275/612] refactor(libSceMouse): keep the new mouse constants after the existing ones They move to the end of mouse_structs.h, away from the lines #14831 also adds to. --- core/libs/prx/libSceMouse/include/mouse_structs.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceMouse/include/mouse_structs.h b/core/libs/prx/libSceMouse/include/mouse_structs.h index f1280a351c..d9944793e0 100644 --- a/core/libs/prx/libSceMouse/include/mouse_structs.h +++ b/core/libs/prx/libSceMouse/include/mouse_structs.h @@ -27,14 +27,8 @@ constexpr int MOUSE_ERROR_INVALID_ARG = static_cast(0x80df0001u); constexpr int MOUSE_ERROR_INVALID_HANDLE = static_cast(0x80df0003u); constexpr int MOUSE_ERROR_ALREADY_OPENED = static_cast(0x80df0004u); constexpr int MOUSE_ERROR_NOT_INITIALIZED = static_cast(0x80df0005u); -constexpr int MOUSE_ERROR_NO_HANDLE = static_cast(0x80df0008u); constexpr int MOUSE_MAX_DATA_NUM = 64; constexpr int MOUSE_HANDLE = 1; -constexpr std::uint32_t MOUSE_MAX_HAND_TYPE = 1; -constexpr std::uint32_t MOUSE_MAX_POINTER_SPEED = 8; -constexpr std::uint32_t MOUSE_EXTENSION_DATA_SIZE = 0x30; -constexpr std::uint32_t MOUSE_EXTENSION_STATE_SIZE = 0x40; -constexpr std::size_t MOUSE_EXTENSION_CONFIG_SIZE = 0x20; constexpr std::uint8_t MOUSE_OPEN_PARAM_MERGED = 1; static_assert(sizeof(MouseOpenParam) == 8); @@ -56,4 +50,11 @@ static_assert(offsetof(MouseData, wheel) == 24); static_assert(offsetof(MouseData, tilt) == 28); static_assert(offsetof(MouseData, reserved) == 32); +constexpr int MOUSE_ERROR_NO_HANDLE = static_cast(0x80df0008u); +constexpr std::uint32_t MOUSE_MAX_HAND_TYPE = 1; +constexpr std::uint32_t MOUSE_MAX_POINTER_SPEED = 8; +constexpr std::uint32_t MOUSE_EXTENSION_DATA_SIZE = 0x30; +constexpr std::uint32_t MOUSE_EXTENSION_STATE_SIZE = 0x40; +constexpr std::size_t MOUSE_EXTENSION_CONFIG_SIZE = 0x20; + #endif From b44e1a7f54ea690374d1fc71c52d6f49044854bf Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:23:18 +0200 Subject: [PATCH 276/612] feat(libSceLibreSsl3): implement X509_get0_extensions --- core/libs/prx/libSceLibreSsl3/CMakeLists.txt | 13 +++- core/libs/prx/libSceLibreSsl3/Export.cpp | 5 -- .../libSceLibreSsl3/src/X509Extensions.cpp | 47 ++++++++++++++ .../tests/GuestLibreSsl3X509Extensions.cpp | 63 +++++++++++++++++++ docs/dev/debt/libSceLibreSsl3.md | 3 +- 5 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp create mode 100644 core/libs/tests/GuestLibreSsl3X509Extensions.cpp diff --git a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt index 41c6247a2a..23dbd3a9f0 100644 --- a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl3 SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/IntegerDecoder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Key.cpp) +add_library(libSceLibreSsl3 SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/IntegerDecoder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Key.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509Extensions.cpp) target_include_directories(libSceLibreSsl3 PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl3 PRIVATE libSceLibreSsl libc) configure_windows_unwind(libSceLibreSsl3) @@ -97,4 +97,15 @@ if(BUILD_TESTING) set_tests_properties(guest_crypto_memcmp guest_libressl3_digest guest_libressl3_asn1 guest_libressl3_key_reference guest_libressl3_version PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl3_x509_extensions_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSsl3X509Extensions.cpp) + target_include_directories(guest_libressl3_x509_extensions_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl3_x509_extensions_tests PRIVATE libSceLibreSsl3) + configure_windows_unwind(guest_libressl3_x509_extensions_tests) + add_test(NAME guest_libressl3_x509_extensions COMMAND guest_libressl3_x509_extensions_tests) + set_tests_properties(guest_libressl3_x509_extensions PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl3_x509_extensions PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceLibreSsl3/Export.cpp b/core/libs/prx/libSceLibreSsl3/Export.cpp index d327cfb245..733aa4f896 100644 --- a/core/libs/prx/libSceLibreSsl3/Export.cpp +++ b/core/libs/prx/libSceLibreSsl3/Export.cpp @@ -321,11 +321,6 @@ int APS5_VABI X509_STORE_up_ref_nid_postfix(void) { return 0; } -int APS5_VABI X509_get0_extensions_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_get0_notAfter_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp b/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp new file mode 100644 index 0000000000..f0af08e59a --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSsl3Stack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct LibreSsl3ExtensionsCertInfo { + unsigned char beforeExtensions[72]; + const LibreSsl3Stack* extensions; + unsigned char rest[24]; +}; + +struct LibreSsl3ExtensionsX509 { + const LibreSsl3ExtensionsCertInfo* certInfo; + unsigned char rest[208]; +}; + +static_assert(sizeof(LibreSsl3Stack) == 32); +static_assert(offsetof(LibreSsl3Stack, data) == 8); +static_assert(sizeof(LibreSsl3ExtensionsCertInfo) == 104); +static_assert(offsetof(LibreSsl3ExtensionsCertInfo, extensions) == 72); +static_assert(sizeof(LibreSsl3ExtensionsX509) == 216); + +namespace { + +const LibreSsl3Stack* Extensions(const LibreSsl3ExtensionsX509* certificate, const char* function) { + if (!certificate) throw std::invalid_argument(std::string(function) + ": null certificate"); + if (!certificate->certInfo) throw std::invalid_argument(std::string(function) + ": null certificate info"); + return certificate->certInfo->extensions; +} + +} + +extern "C" { + +const LibreSsl3Stack* APS5_VABI X509_get0_extensions_nid_postfix(const LibreSsl3ExtensionsX509* certificate) { + return Extensions(certificate, __func__); +} + +} diff --git a/core/libs/tests/GuestLibreSsl3X509Extensions.cpp b/core/libs/tests/GuestLibreSsl3X509Extensions.cpp new file mode 100644 index 0000000000..8a9faee78e --- /dev/null +++ b/core/libs/tests/GuestLibreSsl3X509Extensions.cpp @@ -0,0 +1,63 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct TestStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct TestCertInfo { + void* before[9]; + TestStack* extensions; + void* rest[3]; +}; + +struct TestX509 { + TestCertInfo* certInfo; + void* rest[26]; +}; + +static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); +static_assert(sizeof(TestCertInfo) == 104 && offsetof(TestCertInfo, extensions) == 72); +static_assert(sizeof(TestX509) == 216); + +extern "C" { +const TestStack* APS5_VABI X509_get0_extensions_nid_postfix(const TestX509*); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL X509 extension check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGet0Extensions() { + TestStack extensions {}; + TestCertInfo info {}; + TestX509 certificate {}; + RequireThrows([] { X509_get0_extensions_nid_postfix(nullptr); }); + RequireThrows([&] { X509_get0_extensions_nid_postfix(&certificate); }); + certificate.certInfo = &info; + Require(X509_get0_extensions_nid_postfix(&certificate) == nullptr); + info.before[8] = &info; + info.extensions = &extensions; + Require(X509_get0_extensions_nid_postfix(&certificate) == &extensions); +} + +int main() { + TestGet0Extensions(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl3.md b/docs/dev/debt/libSceLibreSsl3.md index fb73c625b7..42f6b12148 100644 --- a/docs/dev/debt/libSceLibreSsl3.md +++ b/docs/dev/debt/libSceLibreSsl3.md @@ -8,7 +8,7 @@ ### Unknown function info -- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 76 named callable exports, excluding `CRYPTO_memcmp`, `MD5`, `SHA1`, `d2i_ASN1_INTEGER`, `EVP_PKEY_up_ref`, `EC_KEY_up_ref`, `RSA_up_ref`, `EVP_PKEY_get0_RSA`, `EVP_PKEY_get0_DSA`, `EVP_PKEY_get0_DH` and `OpenSSL_version`, remain unimplemented from the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl3.c), cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 73 named callable exports, excluding `CRYPTO_memcmp`, `MD5`, `SHA1`, `d2i_ASN1_INTEGER`, `EVP_PKEY_up_ref`, `EC_KEY_up_ref`, `RSA_up_ref`, `EVP_PKEY_get0_RSA`, `EVP_PKEY_get0_DSA`, `EVP_PKEY_get0_DH`, `OpenSSL_version` and `X509_get0_extensions`, remain unimplemented from the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl3.c), cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. ### Functional @@ -23,6 +23,7 @@ - [EVP_CIPHER_key_length and EVP_CIPHER_iv_length](../../../core/libs/prx/libSceLibreSsl3/src/Cipher.cpp) (libSceLibreSsl3) - nothing is checked on the console. They return `key_len` and `iv_len` from the 80-byte `EVP_CIPHER` of [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/evp) `evp_local.h` and `evp_cipher.c`. The fields are at offsets 8 and 12, as in the [2.5.4 layout](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp/evp.h) of the ciphers the forwarded libSceLibreSsl exports handle. A null cipher, which LibreSSL dereferences, throws. Closes when: compared with the console. - [X509_VERIFY_PARAM_set_flags](../../../core/libs/prx/libSceLibreSsl3/src/X509VerifyParam.cpp) (libSceLibreSsl3) - nothing is checked on the console. It ORs the flags into `flags` and returns 1, as both [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_vpm.c) and [4.0.0](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/x509/x509_vpm.c) `x509_vpm.c` do. They differ when `EXPLICIT_POLICY`, `INHIBIT_ANY` or `INHIBIT_MAP` is set while `POLICY_CHECK` is neither set nor requested: 2.5.4 also adds `POLICY_CHECK` and 4.0.0 does not. The console's choice is unknown, so that case throws through `NotImplemented_nid_no_patch` and leaves the parameters unchanged. It writes libSceLibreSsl's 2.5.4 `LibreSslX509VerifyParam`, whose `flags` is at offset 24 as in 4.0.0. Null parameters throw. Closes when: the console's policy behaviour is measured. - [sk_new, sk_push and sk_free](../../../core/libs/prx/libSceLibreSsl3/src/Stack.cpp) (libSceLibreSsl3) - follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/stack/stack.c) `stack.c` with libSceLibreSsl's 32-byte `_STACK` layout, which `stack_local.h` keeps there. Stacks and element arrays come from the guest heap, so the forwarded `sk_pop_free` can release them. Nothing is checked on the console. Where LibreSSL faults or overflows, they throw: `sk_push` on a null stack, on a negative element count, on a null element array it does not regrow, or when the element array cannot double without overflowing `int` +- [X509_get0_extensions](../../../core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp) (libSceLibreSsl3) - nothing is checked on the console. The `X509`, `X509_CINF` and stack layouts and the results follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto) `x509_local.h`, `stack_local.h`, `x509_set.c`, `x509_ext.c`, `x509_v3.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a certificate yet, so only certificates the caller builds reach it. A null certificate or certificate info, and an extension stack with a positive count and null entries, which LibreSSL dereferences, throw ### Additional callable export inventory From 07dd9507cb0f987d5a3947cfa1c74f5b328a4335 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:24:19 +0200 Subject: [PATCH 277/612] feat(libSceLibreSsl3): implement X509_get_ext_count --- core/libs/prx/libSceLibreSsl3/Export.cpp | 5 ----- .../prx/libSceLibreSsl3/src/X509Extensions.cpp | 5 +++++ .../libs/tests/GuestLibreSsl3X509Extensions.cpp | 17 +++++++++++++++++ docs/dev/debt/libSceLibreSsl3.md | 4 ++-- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl3/Export.cpp b/core/libs/prx/libSceLibreSsl3/Export.cpp index 733aa4f896..17181ef745 100644 --- a/core/libs/prx/libSceLibreSsl3/Export.cpp +++ b/core/libs/prx/libSceLibreSsl3/Export.cpp @@ -341,11 +341,6 @@ int APS5_VABI X509_get_ext_nid_postfix(void) { return 0; } -int APS5_VABI X509_get_ext_count_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_get_version_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp b/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp index f0af08e59a..d9e74cc04d 100644 --- a/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp +++ b/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp @@ -44,4 +44,9 @@ const LibreSsl3Stack* APS5_VABI X509_get0_extensions_nid_postfix(const LibreSsl3 return Extensions(certificate, __func__); } +int APS5_VABI X509_get_ext_count_nid_postfix(const LibreSsl3ExtensionsX509* certificate) { + const LibreSsl3Stack* extensions = Extensions(certificate, __func__); + return extensions ? extensions->num : 0; +} + } diff --git a/core/libs/tests/GuestLibreSsl3X509Extensions.cpp b/core/libs/tests/GuestLibreSsl3X509Extensions.cpp index 8a9faee78e..950cc4ea5e 100644 --- a/core/libs/tests/GuestLibreSsl3X509Extensions.cpp +++ b/core/libs/tests/GuestLibreSsl3X509Extensions.cpp @@ -31,6 +31,7 @@ static_assert(sizeof(TestX509) == 216); extern "C" { const TestStack* APS5_VABI X509_get0_extensions_nid_postfix(const TestX509*); +int APS5_VABI X509_get_ext_count_nid_postfix(const TestX509*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -57,7 +58,23 @@ static void TestGet0Extensions() { Require(X509_get0_extensions_nid_postfix(&certificate) == &extensions); } +static void TestGetExtCount() { + TestStack extensions {}; + TestCertInfo info {}; + TestX509 certificate {}; + RequireThrows([] { X509_get_ext_count_nid_postfix(nullptr); }); + RequireThrows([&] { X509_get_ext_count_nid_postfix(&certificate); }); + certificate.certInfo = &info; + Require(X509_get_ext_count_nid_postfix(&certificate) == 0); + info.extensions = &extensions; + Require(X509_get_ext_count_nid_postfix(&certificate) == 0); + extensions.num = 3; + extensions.numAlloc = 8; + Require(X509_get_ext_count_nid_postfix(&certificate) == 3); +} + int main() { TestGet0Extensions(); + TestGetExtCount(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl3.md b/docs/dev/debt/libSceLibreSsl3.md index 42f6b12148..aa87504881 100644 --- a/docs/dev/debt/libSceLibreSsl3.md +++ b/docs/dev/debt/libSceLibreSsl3.md @@ -8,7 +8,7 @@ ### Unknown function info -- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 73 named callable exports, excluding `CRYPTO_memcmp`, `MD5`, `SHA1`, `d2i_ASN1_INTEGER`, `EVP_PKEY_up_ref`, `EC_KEY_up_ref`, `RSA_up_ref`, `EVP_PKEY_get0_RSA`, `EVP_PKEY_get0_DSA`, `EVP_PKEY_get0_DH`, `OpenSSL_version` and `X509_get0_extensions`, remain unimplemented from the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl3.c), cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 72 named callable exports, excluding `CRYPTO_memcmp`, `MD5`, `SHA1`, `d2i_ASN1_INTEGER`, `EVP_PKEY_up_ref`, `EC_KEY_up_ref`, `RSA_up_ref`, `EVP_PKEY_get0_RSA`, `EVP_PKEY_get0_DSA`, `EVP_PKEY_get0_DH`, `OpenSSL_version`, `X509_get0_extensions` and `X509_get_ext_count`, remain unimplemented from the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl3.c), cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. ### Functional @@ -23,7 +23,7 @@ - [EVP_CIPHER_key_length and EVP_CIPHER_iv_length](../../../core/libs/prx/libSceLibreSsl3/src/Cipher.cpp) (libSceLibreSsl3) - nothing is checked on the console. They return `key_len` and `iv_len` from the 80-byte `EVP_CIPHER` of [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/evp) `evp_local.h` and `evp_cipher.c`. The fields are at offsets 8 and 12, as in the [2.5.4 layout](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp/evp.h) of the ciphers the forwarded libSceLibreSsl exports handle. A null cipher, which LibreSSL dereferences, throws. Closes when: compared with the console. - [X509_VERIFY_PARAM_set_flags](../../../core/libs/prx/libSceLibreSsl3/src/X509VerifyParam.cpp) (libSceLibreSsl3) - nothing is checked on the console. It ORs the flags into `flags` and returns 1, as both [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_vpm.c) and [4.0.0](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/x509/x509_vpm.c) `x509_vpm.c` do. They differ when `EXPLICIT_POLICY`, `INHIBIT_ANY` or `INHIBIT_MAP` is set while `POLICY_CHECK` is neither set nor requested: 2.5.4 also adds `POLICY_CHECK` and 4.0.0 does not. The console's choice is unknown, so that case throws through `NotImplemented_nid_no_patch` and leaves the parameters unchanged. It writes libSceLibreSsl's 2.5.4 `LibreSslX509VerifyParam`, whose `flags` is at offset 24 as in 4.0.0. Null parameters throw. Closes when: the console's policy behaviour is measured. - [sk_new, sk_push and sk_free](../../../core/libs/prx/libSceLibreSsl3/src/Stack.cpp) (libSceLibreSsl3) - follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/stack/stack.c) `stack.c` with libSceLibreSsl's 32-byte `_STACK` layout, which `stack_local.h` keeps there. Stacks and element arrays come from the guest heap, so the forwarded `sk_pop_free` can release them. Nothing is checked on the console. Where LibreSSL faults or overflows, they throw: `sk_push` on a null stack, on a negative element count, on a null element array it does not regrow, or when the element array cannot double without overflowing `int` -- [X509_get0_extensions](../../../core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp) (libSceLibreSsl3) - nothing is checked on the console. The `X509`, `X509_CINF` and stack layouts and the results follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto) `x509_local.h`, `stack_local.h`, `x509_set.c`, `x509_ext.c`, `x509_v3.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a certificate yet, so only certificates the caller builds reach it. A null certificate or certificate info, and an extension stack with a positive count and null entries, which LibreSSL dereferences, throw +- [X509_get0_extensions, X509_get_ext_count](../../../core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp) (libSceLibreSsl3) - nothing is checked on the console. The `X509`, `X509_CINF` and stack layouts and the results follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto) `x509_local.h`, `stack_local.h`, `x509_set.c`, `x509_ext.c`, `x509_v3.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a certificate yet, so only certificates the caller builds reach them. A null certificate or certificate info, and an extension stack with a positive count and null entries, which LibreSSL dereferences, throw ### Additional callable export inventory From 042ef4b83410387ba37f0d00426f084d101d6902 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:24:29 +0200 Subject: [PATCH 278/612] feat(libSceLibreSsl3): implement X509_get_ext --- core/libs/prx/libSceLibreSsl3/Export.cpp | 5 ---- .../libSceLibreSsl3/src/X509Extensions.cpp | 7 +++++ .../tests/GuestLibreSsl3X509Extensions.cpp | 29 +++++++++++++++++++ docs/dev/debt/libSceLibreSsl3.md | 4 +-- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl3/Export.cpp b/core/libs/prx/libSceLibreSsl3/Export.cpp index 17181ef745..296813b8c3 100644 --- a/core/libs/prx/libSceLibreSsl3/Export.cpp +++ b/core/libs/prx/libSceLibreSsl3/Export.cpp @@ -336,11 +336,6 @@ int APS5_VABI X509_get0_signature_nid_postfix(void) { return 0; } -int APS5_VABI X509_get_ext_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_get_version_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp b/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp index d9e74cc04d..bd0f61562e 100644 --- a/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp +++ b/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp @@ -49,4 +49,11 @@ int APS5_VABI X509_get_ext_count_nid_postfix(const LibreSsl3ExtensionsX509* cert return extensions ? extensions->num : 0; } +void* APS5_VABI X509_get_ext_nid_postfix(const LibreSsl3ExtensionsX509* certificate, int loc) { + const LibreSsl3Stack* extensions = Extensions(certificate, __func__); + if (!extensions || loc < 0 || loc >= extensions->num) return nullptr; + if (!extensions->data) throw std::invalid_argument(std::string(__func__) + ": null extension array"); + return extensions->data[loc]; +} + } diff --git a/core/libs/tests/GuestLibreSsl3X509Extensions.cpp b/core/libs/tests/GuestLibreSsl3X509Extensions.cpp index 950cc4ea5e..20a26b4023 100644 --- a/core/libs/tests/GuestLibreSsl3X509Extensions.cpp +++ b/core/libs/tests/GuestLibreSsl3X509Extensions.cpp @@ -32,6 +32,7 @@ static_assert(sizeof(TestX509) == 216); extern "C" { const TestStack* APS5_VABI X509_get0_extensions_nid_postfix(const TestX509*); int APS5_VABI X509_get_ext_count_nid_postfix(const TestX509*); +void* APS5_VABI X509_get_ext_nid_postfix(const TestX509*, int); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -73,8 +74,36 @@ static void TestGetExtCount() { Require(X509_get_ext_count_nid_postfix(&certificate) == 3); } +static void TestGetExt() { + int first = 0; + int second = 0; + char* slots[] {reinterpret_cast(&first), reinterpret_cast(&second), nullptr}; + TestStack extensions {}; + TestCertInfo info {}; + TestX509 certificate {}; + RequireThrows([] { X509_get_ext_nid_postfix(nullptr, 0); }); + RequireThrows([&] { X509_get_ext_nid_postfix(&certificate, 0); }); + certificate.certInfo = &info; + Require(X509_get_ext_nid_postfix(&certificate, 0) == nullptr); + info.extensions = &extensions; + Require(X509_get_ext_nid_postfix(&certificate, 0) == nullptr); + extensions = {2, slots, 0, 3, nullptr}; + Require(X509_get_ext_nid_postfix(&certificate, 0) == &first); + Require(X509_get_ext_nid_postfix(&certificate, 1) == &second); + Require(X509_get_ext_nid_postfix(&certificate, 2) == nullptr); + Require(X509_get_ext_nid_postfix(&certificate, -1) == nullptr); + Require(X509_get_ext_nid_postfix(&certificate, INT_MIN) == nullptr); + Require(X509_get_ext_nid_postfix(&certificate, INT_MAX) == nullptr); + extensions.num = 3; + Require(X509_get_ext_nid_postfix(&certificate, 2) == nullptr); + extensions.data = nullptr; + RequireThrows([&] { X509_get_ext_nid_postfix(&certificate, 0); }); + Require(X509_get_ext_nid_postfix(&certificate, 3) == nullptr); +} + int main() { TestGet0Extensions(); TestGetExtCount(); + TestGetExt(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl3.md b/docs/dev/debt/libSceLibreSsl3.md index aa87504881..1e649b4c79 100644 --- a/docs/dev/debt/libSceLibreSsl3.md +++ b/docs/dev/debt/libSceLibreSsl3.md @@ -8,7 +8,7 @@ ### Unknown function info -- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 72 named callable exports, excluding `CRYPTO_memcmp`, `MD5`, `SHA1`, `d2i_ASN1_INTEGER`, `EVP_PKEY_up_ref`, `EC_KEY_up_ref`, `RSA_up_ref`, `EVP_PKEY_get0_RSA`, `EVP_PKEY_get0_DSA`, `EVP_PKEY_get0_DH`, `OpenSSL_version`, `X509_get0_extensions` and `X509_get_ext_count`, remain unimplemented from the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl3.c), cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 71 named callable exports, excluding `CRYPTO_memcmp`, `MD5`, `SHA1`, `d2i_ASN1_INTEGER`, `EVP_PKEY_up_ref`, `EC_KEY_up_ref`, `RSA_up_ref`, `EVP_PKEY_get0_RSA`, `EVP_PKEY_get0_DSA`, `EVP_PKEY_get0_DH`, `OpenSSL_version`, `X509_get0_extensions`, `X509_get_ext_count` and `X509_get_ext`, remain unimplemented from the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl3.c), cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. ### Functional @@ -23,7 +23,7 @@ - [EVP_CIPHER_key_length and EVP_CIPHER_iv_length](../../../core/libs/prx/libSceLibreSsl3/src/Cipher.cpp) (libSceLibreSsl3) - nothing is checked on the console. They return `key_len` and `iv_len` from the 80-byte `EVP_CIPHER` of [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/evp) `evp_local.h` and `evp_cipher.c`. The fields are at offsets 8 and 12, as in the [2.5.4 layout](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp/evp.h) of the ciphers the forwarded libSceLibreSsl exports handle. A null cipher, which LibreSSL dereferences, throws. Closes when: compared with the console. - [X509_VERIFY_PARAM_set_flags](../../../core/libs/prx/libSceLibreSsl3/src/X509VerifyParam.cpp) (libSceLibreSsl3) - nothing is checked on the console. It ORs the flags into `flags` and returns 1, as both [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_vpm.c) and [4.0.0](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/x509/x509_vpm.c) `x509_vpm.c` do. They differ when `EXPLICIT_POLICY`, `INHIBIT_ANY` or `INHIBIT_MAP` is set while `POLICY_CHECK` is neither set nor requested: 2.5.4 also adds `POLICY_CHECK` and 4.0.0 does not. The console's choice is unknown, so that case throws through `NotImplemented_nid_no_patch` and leaves the parameters unchanged. It writes libSceLibreSsl's 2.5.4 `LibreSslX509VerifyParam`, whose `flags` is at offset 24 as in 4.0.0. Null parameters throw. Closes when: the console's policy behaviour is measured. - [sk_new, sk_push and sk_free](../../../core/libs/prx/libSceLibreSsl3/src/Stack.cpp) (libSceLibreSsl3) - follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/stack/stack.c) `stack.c` with libSceLibreSsl's 32-byte `_STACK` layout, which `stack_local.h` keeps there. Stacks and element arrays come from the guest heap, so the forwarded `sk_pop_free` can release them. Nothing is checked on the console. Where LibreSSL faults or overflows, they throw: `sk_push` on a null stack, on a negative element count, on a null element array it does not regrow, or when the element array cannot double without overflowing `int` -- [X509_get0_extensions, X509_get_ext_count](../../../core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp) (libSceLibreSsl3) - nothing is checked on the console. The `X509`, `X509_CINF` and stack layouts and the results follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto) `x509_local.h`, `stack_local.h`, `x509_set.c`, `x509_ext.c`, `x509_v3.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a certificate yet, so only certificates the caller builds reach them. A null certificate or certificate info, and an extension stack with a positive count and null entries, which LibreSSL dereferences, throw +- [X509_get0_extensions, X509_get_ext_count, X509_get_ext](../../../core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp) (libSceLibreSsl3) - nothing is checked on the console. The `X509`, `X509_CINF` and stack layouts and the results follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto) `x509_local.h`, `stack_local.h`, `x509_set.c`, `x509_ext.c`, `x509_v3.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a certificate yet, so only certificates the caller builds reach them. A null certificate or certificate info, and an extension stack with a positive count and null entries, which LibreSSL dereferences, throw ### Additional callable export inventory From 9420289623998692db901f0bea53406231e83cfd Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 06:00:29 +0100 Subject: [PATCH 279/612] refactor(libSceLibreSsl3): declare the X509 extension exports in the public header --- core/libs/prx/libSceLibreSsl3/include/Api.hpp | 25 +++++++- .../libSceLibreSsl3/src/X509Extensions.cpp | 20 +----- .../tests/GuestLibreSsl3X509Extensions.cpp | 63 ++++++++----------- 3 files changed, 50 insertions(+), 58 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl3/include/Api.hpp b/core/libs/prx/libSceLibreSsl3/include/Api.hpp index 2eed537f89..7eecf6bbd2 100644 --- a/core/libs/prx/libSceLibreSsl3/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl3/include/Api.hpp @@ -5,6 +5,25 @@ #include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libSceLibreSsl3/include/Cipher.hpp" +struct LibreSsl3Stack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct LibreSsl3ExtensionsCertInfo { + unsigned char beforeExtensions[72]; + const LibreSsl3Stack* extensions; + unsigned char rest[24]; +}; + +struct LibreSsl3ExtensionsX509 { + const LibreSsl3ExtensionsCertInfo* certInfo; + unsigned char rest[208]; +}; + extern "C" { int APS5_VABI ASN1_INTEGER_free_nid_postfix(void); @@ -74,12 +93,12 @@ int APS5_VABI X509_STORE_CTX_free_nid_postfix(void); int APS5_VABI X509_STORE_CTX_init_nid_postfix(void); int APS5_VABI X509_STORE_CTX_new_nid_postfix(void); int APS5_VABI X509_STORE_up_ref_nid_postfix(void); -int APS5_VABI X509_get0_extensions_nid_postfix(void); +const LibreSsl3Stack* APS5_VABI X509_get0_extensions_nid_postfix(const LibreSsl3ExtensionsX509* certificate); int APS5_VABI X509_get0_notAfter_nid_postfix(void); int APS5_VABI X509_get0_notBefore_nid_postfix(void); int APS5_VABI X509_get0_signature_nid_postfix(void); -int APS5_VABI X509_get_ext_nid_postfix(void); -int APS5_VABI X509_get_ext_count_nid_postfix(void); +void* APS5_VABI X509_get_ext_nid_postfix(const LibreSsl3ExtensionsX509* certificate, int loc); +int APS5_VABI X509_get_ext_count_nid_postfix(const LibreSsl3ExtensionsX509* certificate); int APS5_VABI X509_get_version_nid_postfix(void); int APS5_VABI X509_getm_notAfter_nid_postfix(void); int APS5_VABI X509_getm_notBefore_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp b/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp index bd0f61562e..d9aa68ab68 100644 --- a/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp +++ b/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp @@ -1,27 +1,9 @@ +#include "prx/libSceLibreSsl3/include/Api.hpp" #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSsl3Stack { - int num; - char** data; - int sorted; - int numAlloc; - void* comp; -}; - -struct LibreSsl3ExtensionsCertInfo { - unsigned char beforeExtensions[72]; - const LibreSsl3Stack* extensions; - unsigned char rest[24]; -}; - -struct LibreSsl3ExtensionsX509 { - const LibreSsl3ExtensionsCertInfo* certInfo; - unsigned char rest[208]; -}; - static_assert(sizeof(LibreSsl3Stack) == 32); static_assert(offsetof(LibreSsl3Stack, data) == 8); static_assert(sizeof(LibreSsl3ExtensionsCertInfo) == 104); diff --git a/core/libs/tests/GuestLibreSsl3X509Extensions.cpp b/core/libs/tests/GuestLibreSsl3X509Extensions.cpp index 20a26b4023..679138528e 100644 --- a/core/libs/tests/GuestLibreSsl3X509Extensions.cpp +++ b/core/libs/tests/GuestLibreSsl3X509Extensions.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl3/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -6,17 +7,9 @@ #include #include -struct TestStack { - int num; - char** data; - int sorted; - int numAlloc; - void* comp; -}; - struct TestCertInfo { void* before[9]; - TestStack* extensions; + LibreSsl3Stack* extensions; void* rest[3]; }; @@ -25,14 +18,12 @@ struct TestX509 { void* rest[26]; }; -static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); +static_assert(sizeof(LibreSsl3Stack) == 32 && offsetof(LibreSsl3Stack, data) == 8); static_assert(sizeof(TestCertInfo) == 104 && offsetof(TestCertInfo, extensions) == 72); static_assert(sizeof(TestX509) == 216); -extern "C" { -const TestStack* APS5_VABI X509_get0_extensions_nid_postfix(const TestX509*); -int APS5_VABI X509_get_ext_count_nid_postfix(const TestX509*); -void* APS5_VABI X509_get_ext_nid_postfix(const TestX509*, int); +static const LibreSsl3ExtensionsX509* Certificate(const TestX509* certificate) { + return reinterpret_cast(certificate); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -47,58 +38,58 @@ template static void RequireThrows(TAction action, std::source } static void TestGet0Extensions() { - TestStack extensions {}; + LibreSsl3Stack extensions {}; TestCertInfo info {}; TestX509 certificate {}; RequireThrows([] { X509_get0_extensions_nid_postfix(nullptr); }); - RequireThrows([&] { X509_get0_extensions_nid_postfix(&certificate); }); + RequireThrows([&] { X509_get0_extensions_nid_postfix(Certificate(&certificate)); }); certificate.certInfo = &info; - Require(X509_get0_extensions_nid_postfix(&certificate) == nullptr); + Require(X509_get0_extensions_nid_postfix(Certificate(&certificate)) == nullptr); info.before[8] = &info; info.extensions = &extensions; - Require(X509_get0_extensions_nid_postfix(&certificate) == &extensions); + Require(X509_get0_extensions_nid_postfix(Certificate(&certificate)) == &extensions); } static void TestGetExtCount() { - TestStack extensions {}; + LibreSsl3Stack extensions {}; TestCertInfo info {}; TestX509 certificate {}; RequireThrows([] { X509_get_ext_count_nid_postfix(nullptr); }); - RequireThrows([&] { X509_get_ext_count_nid_postfix(&certificate); }); + RequireThrows([&] { X509_get_ext_count_nid_postfix(Certificate(&certificate)); }); certificate.certInfo = &info; - Require(X509_get_ext_count_nid_postfix(&certificate) == 0); + Require(X509_get_ext_count_nid_postfix(Certificate(&certificate)) == 0); info.extensions = &extensions; - Require(X509_get_ext_count_nid_postfix(&certificate) == 0); + Require(X509_get_ext_count_nid_postfix(Certificate(&certificate)) == 0); extensions.num = 3; extensions.numAlloc = 8; - Require(X509_get_ext_count_nid_postfix(&certificate) == 3); + Require(X509_get_ext_count_nid_postfix(Certificate(&certificate)) == 3); } static void TestGetExt() { int first = 0; int second = 0; char* slots[] {reinterpret_cast(&first), reinterpret_cast(&second), nullptr}; - TestStack extensions {}; + LibreSsl3Stack extensions {}; TestCertInfo info {}; TestX509 certificate {}; RequireThrows([] { X509_get_ext_nid_postfix(nullptr, 0); }); - RequireThrows([&] { X509_get_ext_nid_postfix(&certificate, 0); }); + RequireThrows([&] { X509_get_ext_nid_postfix(Certificate(&certificate), 0); }); certificate.certInfo = &info; - Require(X509_get_ext_nid_postfix(&certificate, 0) == nullptr); + Require(X509_get_ext_nid_postfix(Certificate(&certificate), 0) == nullptr); info.extensions = &extensions; - Require(X509_get_ext_nid_postfix(&certificate, 0) == nullptr); + Require(X509_get_ext_nid_postfix(Certificate(&certificate), 0) == nullptr); extensions = {2, slots, 0, 3, nullptr}; - Require(X509_get_ext_nid_postfix(&certificate, 0) == &first); - Require(X509_get_ext_nid_postfix(&certificate, 1) == &second); - Require(X509_get_ext_nid_postfix(&certificate, 2) == nullptr); - Require(X509_get_ext_nid_postfix(&certificate, -1) == nullptr); - Require(X509_get_ext_nid_postfix(&certificate, INT_MIN) == nullptr); - Require(X509_get_ext_nid_postfix(&certificate, INT_MAX) == nullptr); + Require(X509_get_ext_nid_postfix(Certificate(&certificate), 0) == &first); + Require(X509_get_ext_nid_postfix(Certificate(&certificate), 1) == &second); + Require(X509_get_ext_nid_postfix(Certificate(&certificate), 2) == nullptr); + Require(X509_get_ext_nid_postfix(Certificate(&certificate), -1) == nullptr); + Require(X509_get_ext_nid_postfix(Certificate(&certificate), INT_MIN) == nullptr); + Require(X509_get_ext_nid_postfix(Certificate(&certificate), INT_MAX) == nullptr); extensions.num = 3; - Require(X509_get_ext_nid_postfix(&certificate, 2) == nullptr); + Require(X509_get_ext_nid_postfix(Certificate(&certificate), 2) == nullptr); extensions.data = nullptr; - RequireThrows([&] { X509_get_ext_nid_postfix(&certificate, 0); }); - Require(X509_get_ext_nid_postfix(&certificate, 3) == nullptr); + RequireThrows([&] { X509_get_ext_nid_postfix(Certificate(&certificate), 0); }); + Require(X509_get_ext_nid_postfix(Certificate(&certificate), 3) == nullptr); } int main() { From fc9b469048b79fe5886b8663aaa75ed2a7c6a811 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 17:57:22 +0100 Subject: [PATCH 280/612] test(libSceLibreSsl3): use the public X509 structs and drop the unsupported-input throws in the extension tests --- .../tests/GuestLibreSsl3X509Extensions.cpp | 80 ++++++------------- 1 file changed, 24 insertions(+), 56 deletions(-) diff --git a/core/libs/tests/GuestLibreSsl3X509Extensions.cpp b/core/libs/tests/GuestLibreSsl3X509Extensions.cpp index 679138528e..0bb11f3422 100644 --- a/core/libs/tests/GuestLibreSsl3X509Extensions.cpp +++ b/core/libs/tests/GuestLibreSsl3X509Extensions.cpp @@ -1,30 +1,10 @@ #include "prx/libSceLibreSsl3/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include -#include #include #include +#include #include -#include - -struct TestCertInfo { - void* before[9]; - LibreSsl3Stack* extensions; - void* rest[3]; -}; - -struct TestX509 { - TestCertInfo* certInfo; - void* rest[26]; -}; - -static_assert(sizeof(LibreSsl3Stack) == 32 && offsetof(LibreSsl3Stack, data) == 8); -static_assert(sizeof(TestCertInfo) == 104 && offsetof(TestCertInfo, extensions) == 72); -static_assert(sizeof(TestX509) == 216); - -static const LibreSsl3ExtensionsX509* Certificate(const TestX509* certificate) { - return reinterpret_cast(certificate); -} static void Require(bool condition, std::source_location location = std::source_location::current()) { if (condition) return; @@ -32,37 +12,29 @@ static void Require(bool condition, std::source_location location = std::source_ std::abort(); } -template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { - try { action(); } catch (const std::exception&) { return; } - Require(false, location); -} - static void TestGet0Extensions() { LibreSsl3Stack extensions {}; - TestCertInfo info {}; - TestX509 certificate {}; - RequireThrows([] { X509_get0_extensions_nid_postfix(nullptr); }); - RequireThrows([&] { X509_get0_extensions_nid_postfix(Certificate(&certificate)); }); + LibreSsl3ExtensionsCertInfo info {}; + LibreSsl3ExtensionsX509 certificate {}; certificate.certInfo = &info; - Require(X509_get0_extensions_nid_postfix(Certificate(&certificate)) == nullptr); - info.before[8] = &info; + Require(X509_get0_extensions_nid_postfix(&certificate) == nullptr); + std::memset(info.beforeExtensions, 0xFF, sizeof(info.beforeExtensions)); + std::memset(info.rest, 0xFF, sizeof(info.rest)); info.extensions = &extensions; - Require(X509_get0_extensions_nid_postfix(Certificate(&certificate)) == &extensions); + Require(X509_get0_extensions_nid_postfix(&certificate) == &extensions); } static void TestGetExtCount() { LibreSsl3Stack extensions {}; - TestCertInfo info {}; - TestX509 certificate {}; - RequireThrows([] { X509_get_ext_count_nid_postfix(nullptr); }); - RequireThrows([&] { X509_get_ext_count_nid_postfix(Certificate(&certificate)); }); + LibreSsl3ExtensionsCertInfo info {}; + LibreSsl3ExtensionsX509 certificate {}; certificate.certInfo = &info; - Require(X509_get_ext_count_nid_postfix(Certificate(&certificate)) == 0); + Require(X509_get_ext_count_nid_postfix(&certificate) == 0); info.extensions = &extensions; - Require(X509_get_ext_count_nid_postfix(Certificate(&certificate)) == 0); + Require(X509_get_ext_count_nid_postfix(&certificate) == 0); extensions.num = 3; extensions.numAlloc = 8; - Require(X509_get_ext_count_nid_postfix(Certificate(&certificate)) == 3); + Require(X509_get_ext_count_nid_postfix(&certificate) == 3); } static void TestGetExt() { @@ -70,26 +42,22 @@ static void TestGetExt() { int second = 0; char* slots[] {reinterpret_cast(&first), reinterpret_cast(&second), nullptr}; LibreSsl3Stack extensions {}; - TestCertInfo info {}; - TestX509 certificate {}; - RequireThrows([] { X509_get_ext_nid_postfix(nullptr, 0); }); - RequireThrows([&] { X509_get_ext_nid_postfix(Certificate(&certificate), 0); }); + LibreSsl3ExtensionsCertInfo info {}; + LibreSsl3ExtensionsX509 certificate {}; certificate.certInfo = &info; - Require(X509_get_ext_nid_postfix(Certificate(&certificate), 0) == nullptr); + Require(X509_get_ext_nid_postfix(&certificate, 0) == nullptr); info.extensions = &extensions; - Require(X509_get_ext_nid_postfix(Certificate(&certificate), 0) == nullptr); + Require(X509_get_ext_nid_postfix(&certificate, 0) == nullptr); extensions = {2, slots, 0, 3, nullptr}; - Require(X509_get_ext_nid_postfix(Certificate(&certificate), 0) == &first); - Require(X509_get_ext_nid_postfix(Certificate(&certificate), 1) == &second); - Require(X509_get_ext_nid_postfix(Certificate(&certificate), 2) == nullptr); - Require(X509_get_ext_nid_postfix(Certificate(&certificate), -1) == nullptr); - Require(X509_get_ext_nid_postfix(Certificate(&certificate), INT_MIN) == nullptr); - Require(X509_get_ext_nid_postfix(Certificate(&certificate), INT_MAX) == nullptr); + Require(X509_get_ext_nid_postfix(&certificate, 0) == &first); + Require(X509_get_ext_nid_postfix(&certificate, 1) == &second); + Require(X509_get_ext_nid_postfix(&certificate, 2) == nullptr); + Require(X509_get_ext_nid_postfix(&certificate, -1) == nullptr); + Require(X509_get_ext_nid_postfix(&certificate, INT_MIN) == nullptr); + Require(X509_get_ext_nid_postfix(&certificate, INT_MAX) == nullptr); extensions.num = 3; - Require(X509_get_ext_nid_postfix(Certificate(&certificate), 2) == nullptr); - extensions.data = nullptr; - RequireThrows([&] { X509_get_ext_nid_postfix(Certificate(&certificate), 0); }); - Require(X509_get_ext_nid_postfix(Certificate(&certificate), 3) == nullptr); + Require(X509_get_ext_nid_postfix(&certificate, 2) == nullptr); + Require(X509_get_ext_nid_postfix(&certificate, 3) == nullptr); } int main() { From c21ce49e513d7f40a0866efdbc6b22f2d67daa2a Mon Sep 17 00:00:00 2001 From: Alvaro Date: Mon, 26 Oct 2026 07:16:33 +0100 Subject: [PATCH 281/612] test(libSceLibreSsl3): pin the extension count bound with a stale entry past it --- core/libs/tests/GuestLibreSsl3X509Extensions.cpp | 5 +++-- docs/dev/debt/libSceLibreSsl3.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/libs/tests/GuestLibreSsl3X509Extensions.cpp b/core/libs/tests/GuestLibreSsl3X509Extensions.cpp index 0bb11f3422..a89f8c328e 100644 --- a/core/libs/tests/GuestLibreSsl3X509Extensions.cpp +++ b/core/libs/tests/GuestLibreSsl3X509Extensions.cpp @@ -40,7 +40,8 @@ static void TestGetExtCount() { static void TestGetExt() { int first = 0; int second = 0; - char* slots[] {reinterpret_cast(&first), reinterpret_cast(&second), nullptr}; + int third = 0; + char* slots[] {reinterpret_cast(&first), reinterpret_cast(&second), reinterpret_cast(&third)}; LibreSsl3Stack extensions {}; LibreSsl3ExtensionsCertInfo info {}; LibreSsl3ExtensionsX509 certificate {}; @@ -56,7 +57,7 @@ static void TestGetExt() { Require(X509_get_ext_nid_postfix(&certificate, INT_MIN) == nullptr); Require(X509_get_ext_nid_postfix(&certificate, INT_MAX) == nullptr); extensions.num = 3; - Require(X509_get_ext_nid_postfix(&certificate, 2) == nullptr); + Require(X509_get_ext_nid_postfix(&certificate, 2) == &third); Require(X509_get_ext_nid_postfix(&certificate, 3) == nullptr); } diff --git a/docs/dev/debt/libSceLibreSsl3.md b/docs/dev/debt/libSceLibreSsl3.md index 1e649b4c79..41f5d16e8e 100644 --- a/docs/dev/debt/libSceLibreSsl3.md +++ b/docs/dev/debt/libSceLibreSsl3.md @@ -23,7 +23,7 @@ - [EVP_CIPHER_key_length and EVP_CIPHER_iv_length](../../../core/libs/prx/libSceLibreSsl3/src/Cipher.cpp) (libSceLibreSsl3) - nothing is checked on the console. They return `key_len` and `iv_len` from the 80-byte `EVP_CIPHER` of [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/evp) `evp_local.h` and `evp_cipher.c`. The fields are at offsets 8 and 12, as in the [2.5.4 layout](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp/evp.h) of the ciphers the forwarded libSceLibreSsl exports handle. A null cipher, which LibreSSL dereferences, throws. Closes when: compared with the console. - [X509_VERIFY_PARAM_set_flags](../../../core/libs/prx/libSceLibreSsl3/src/X509VerifyParam.cpp) (libSceLibreSsl3) - nothing is checked on the console. It ORs the flags into `flags` and returns 1, as both [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_vpm.c) and [4.0.0](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/x509/x509_vpm.c) `x509_vpm.c` do. They differ when `EXPLICIT_POLICY`, `INHIBIT_ANY` or `INHIBIT_MAP` is set while `POLICY_CHECK` is neither set nor requested: 2.5.4 also adds `POLICY_CHECK` and 4.0.0 does not. The console's choice is unknown, so that case throws through `NotImplemented_nid_no_patch` and leaves the parameters unchanged. It writes libSceLibreSsl's 2.5.4 `LibreSslX509VerifyParam`, whose `flags` is at offset 24 as in 4.0.0. Null parameters throw. Closes when: the console's policy behaviour is measured. - [sk_new, sk_push and sk_free](../../../core/libs/prx/libSceLibreSsl3/src/Stack.cpp) (libSceLibreSsl3) - follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/stack/stack.c) `stack.c` with libSceLibreSsl's 32-byte `_STACK` layout, which `stack_local.h` keeps there. Stacks and element arrays come from the guest heap, so the forwarded `sk_pop_free` can release them. Nothing is checked on the console. Where LibreSSL faults or overflows, they throw: `sk_push` on a null stack, on a negative element count, on a null element array it does not regrow, or when the element array cannot double without overflowing `int` -- [X509_get0_extensions, X509_get_ext_count, X509_get_ext](../../../core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp) (libSceLibreSsl3) - nothing is checked on the console. The `X509`, `X509_CINF` and stack layouts and the results follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto) `x509_local.h`, `stack_local.h`, `x509_set.c`, `x509_ext.c`, `x509_v3.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a certificate yet, so only certificates the caller builds reach them. A null certificate or certificate info, and an extension stack with a positive count and null entries, which LibreSSL dereferences, throw +- [X509_get0_extensions, X509_get_ext_count, X509_get_ext](../../../core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp) (libSceLibreSsl3) - nothing is checked on the console. The `X509`, `X509_CINF` and stack layouts and the results follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto) `x509_local.h`, `stack_local.h`, `x509_set.c`, `x509_ext.c`, `x509_v3.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a certificate yet, so only certificates the caller builds reach them. A null certificate or certificate info, and an extension stack with a positive count and a null entry array, which LibreSSL dereferences, throw ### Additional callable export inventory From 30a2d86fb619a67ce3d4ff0900d1642e4a0855bf Mon Sep 17 00:00:00 2001 From: zaryabzip <110787259+zaryabzip@users.noreply.github.com> Date: Mon, 2 Nov 2026 21:35:21 +0500 Subject: [PATCH 282/612] fix(libSceMouse): round ExtensionScalePosition up, as the module does @BunnyFl's emulation of the 13.42 instructions shows a ceiling, not rounding to nearest: the floor from the 0x80008001 multiply gets 1 added whenever the remainder isn't 0. The test adds (1, 2) and (32767, 2), which give 1 by ceiling and 0 by rounding. --- core/libs/prx/libSceMouse/Export.cpp | 2 +- core/libs/prx/libSceMouse/tests/MouseQueries.cpp | 4 ++-- docs/dev/debt/libSceMouse.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceMouse/Export.cpp b/core/libs/prx/libSceMouse/Export.cpp index a473646e65..b7e5fdb127 100644 --- a/core/libs/prx/libSceMouse/Export.cpp +++ b/core/libs/prx/libSceMouse/Export.cpp @@ -143,7 +143,7 @@ int APS5_VABI sceMouseExtensionScalePosition(std::uint16_t* position, std::uint1 if (!Mouse::IsInitialized()) return MOUSE_ERROR_NOT_INITIALIZED; if (position == nullptr || scale == 0) return MOUSE_ERROR_INVALID_ARG; const std::uint32_t scaled = static_cast(*position) * (scale - 1u); - *position = static_cast((scaled + 0x7fffu) / 0xffffu); + *position = static_cast((scaled + 0xfffeu) / 0xffffu); return MOUSE_OK; } diff --git a/core/libs/prx/libSceMouse/tests/MouseQueries.cpp b/core/libs/prx/libSceMouse/tests/MouseQueries.cpp index d9cab431f7..da9a34068d 100644 --- a/core/libs/prx/libSceMouse/tests/MouseQueries.cpp +++ b/core/libs/prx/libSceMouse/tests/MouseQueries.cpp @@ -70,10 +70,10 @@ int main() { Require(scale(nullptr, 1920) == InvalidArg, "ExtensionScalePosition took a null position"); Require(scale(&position, 0) == InvalidArg && position == 100, "ExtensionScalePosition took a scale of 0"); - const std::array, 6> rows{{{65535, 1920, 1919}, {32768, 1920, 960}, {0, 1920, 0}, {65535, 1, 0}, {1, 65535, 1}, {40000, 1080, 659}}}; + const std::array, 9> rows{{{65535, 1920, 1919}, {32768, 1920, 960}, {0, 1920, 0}, {65535, 1, 0}, {1, 65535, 1}, {40000, 1080, 659}, {1, 2, 1}, {32767, 2, 1}, {100, 1920, 3}}}; for (const auto& [input, size, expected] : rows) { position = input; - Require(scale(&position, size) == Ok && position == expected, "ExtensionScalePosition didn't round position * (scale - 1) / 65535"); + Require(scale(&position, size) == Ok && position == expected, "ExtensionScalePosition didn't round position * (scale - 1) / 65535 up"); } Require(setConfig(handle, nullptr) == InvalidArg, "ExtensionSetConfig took a null config"); diff --git a/docs/dev/debt/libSceMouse.md b/docs/dev/debt/libSceMouse.md index de9528c893..e38e0607fe 100644 --- a/docs/dev/debt/libSceMouse.md +++ b/docs/dev/debt/libSceMouse.md @@ -15,7 +15,7 @@ - [sceMouseGetHandle, sceMouseExtensionSetConfig and sceMouseExtensionScalePosition](../../../core/libs/prx/libSceMouse/src/mouse_impl.cpp) (libSceMouse) - follow the same [13.42 disassembly](https://github.com/boykopovar/AnyPS5/issues/15088#issuecomment-6580877653), not a console; none reaches the driver. - `GetHandle(userId, type, index)` returns the handle `sceMouseOpen` gave that user and type, or 0x80df0008. The console skips slots whose +0xc8 field is 1 when `index` is non-zero; that field's meaning is unknown, and no slot here has it. - `ExtensionSetConfig(handle, config)` refuses a null or a non-zero byte in 5..0x1f, then stores the u32 at 0 and the byte at 4; nothing here reads them yet. - - `ExtensionScalePosition(uint16* position, uint16 scale)` rounds `*position * (scale - 1) / 65535` to the nearest integer, which has no ties since 65535 is odd. The console divides with a multiply by 0x80008001 and a shift by 47, which equals that floor division for every product of two 16-bit values, then rounds with a compare whose exact form wasn't checked. + - `ExtensionScalePosition(uint16* position, uint16 scale)` rounds `*position * (scale - 1) / 65535` up. The console divides with a multiply by 0x80008001 and a shift by 47, which is the floor, and adds 1 when the remainder isn't 0, through a compare of `-(product * 65537)` with 0x10002 ([@BunnyFl's emulation]()). - [Publish](../../../core/libs/prx/libSceMouse/src/mouse_impl.cpp) (libSceMouse) - consecutive motion-only reports with the same buttons are summed into the newest unread entry, so a title that reads a few entries per frame gets current motion instead of a full queue of old reports. Reports with no motion and no button, wheel, tilt or connection change are dropped. Per-report granularity and the console's own report rate are not reproduced From 11fa2b988a7bbdb07e5211ece445211abf691681 Mon Sep 17 00:00:00 2001 From: zaryabzip <110787259+zaryabzip@users.noreply.github.com> Date: Mon, 2 Nov 2026 21:35:39 +0500 Subject: [PATCH 283/612] docs(libSceMouse): link the review that shows ExtensionScalePosition's ceiling --- docs/dev/debt/libSceMouse.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceMouse.md b/docs/dev/debt/libSceMouse.md index e38e0607fe..2bef6f5b84 100644 --- a/docs/dev/debt/libSceMouse.md +++ b/docs/dev/debt/libSceMouse.md @@ -15,7 +15,7 @@ - [sceMouseGetHandle, sceMouseExtensionSetConfig and sceMouseExtensionScalePosition](../../../core/libs/prx/libSceMouse/src/mouse_impl.cpp) (libSceMouse) - follow the same [13.42 disassembly](https://github.com/boykopovar/AnyPS5/issues/15088#issuecomment-6580877653), not a console; none reaches the driver. - `GetHandle(userId, type, index)` returns the handle `sceMouseOpen` gave that user and type, or 0x80df0008. The console skips slots whose +0xc8 field is 1 when `index` is non-zero; that field's meaning is unknown, and no slot here has it. - `ExtensionSetConfig(handle, config)` refuses a null or a non-zero byte in 5..0x1f, then stores the u32 at 0 and the byte at 4; nothing here reads them yet. - - `ExtensionScalePosition(uint16* position, uint16 scale)` rounds `*position * (scale - 1) / 65535` up. The console divides with a multiply by 0x80008001 and a shift by 47, which is the floor, and adds 1 when the remainder isn't 0, through a compare of `-(product * 65537)` with 0x10002 ([@BunnyFl's emulation]()). + - `ExtensionScalePosition(uint16* position, uint16 scale)` rounds `*position * (scale - 1) / 65535` up. The console divides with a multiply by 0x80008001 and a shift by 47, which is the floor, and adds 1 when the remainder isn't 0, through a compare of `-(product * 65537)` with 0x10002 ([@BunnyFl's emulation](https://github.com/boykopovar/AnyPS5/pull/15106#pullrequestreview-5739861373)). - [Publish](../../../core/libs/prx/libSceMouse/src/mouse_impl.cpp) (libSceMouse) - consecutive motion-only reports with the same buttons are summed into the newest unread entry, so a title that reads a few entries per frame gets current motion instead of a full queue of old reports. Reports with no motion and no button, wheel, tilt or connection change are dropped. Per-report granularity and the console's own report rate are not reproduced From 7e51a92a9451189c75b091ed0df60b837bc4cfe8 Mon Sep 17 00:00:00 2001 From: Noxy-HNR Date: Mon, 2 Nov 2026 17:22:07 +0000 Subject: [PATCH 284/612] feat(libSceNKWebKitRequirements): implement the SQLite exports Build SQLite 3.54.1 from the 3rdparty/sqlite submodule, generating its headers and parser with the bundled jimsh, lemon and helper tools, and route the 50 sqlite3_* exports to it through thin guest adapters. Guest callbacks for busy handlers, authorizers, WAL hooks, collations and SQL functions are called through SysV trampolines, and file names are mapped through the guest path resolver. --- .gitmodules | 3 + 3rdparty/sqlite | 1 + CMakeLists.txt | 1 + cmake/Sqlite.cmake | 68 ++++ .../libSceNKWebKitRequirements/CMakeLists.txt | 21 + .../prx/libSceNKWebKitRequirements/Export.cpp | 200 --------- .../include/Identified.hpp | 50 --- .../include/Sqlite.hpp | 79 ++++ .../libSceNKWebKitRequirements/src/Sqlite.cpp | 380 ++++++++++++++++++ core/libs/tests/GuestNKWebKitSqlite.cpp | 348 ++++++++++++++++ docs/dev/debt/libSceNKWebKitRequirements.md | 11 +- 11 files changed, 910 insertions(+), 252 deletions(-) create mode 160000 3rdparty/sqlite create mode 100644 cmake/Sqlite.cmake create mode 100644 core/libs/prx/libSceNKWebKitRequirements/CMakeLists.txt create mode 100644 core/libs/prx/libSceNKWebKitRequirements/include/Sqlite.hpp create mode 100644 core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp create mode 100644 core/libs/tests/GuestNKWebKitSqlite.cpp diff --git a/.gitmodules b/.gitmodules index e811a81b6c..34556e3e22 100644 --- a/.gitmodules +++ b/.gitmodules @@ -76,3 +76,6 @@ [submodule "3rdparty/quickjs"] path = 3rdparty/quickjs url = https://github.com/quickjs-ng/quickjs.git +[submodule "3rdparty/sqlite"] + path = 3rdparty/sqlite + url = https://github.com/sqlite/sqlite.git diff --git a/3rdparty/sqlite b/3rdparty/sqlite new file mode 160000 index 0000000000..57520227c9 --- /dev/null +++ b/3rdparty/sqlite @@ -0,0 +1 @@ +Subproject commit 57520227c95396ce39ba6ecaac8d475b78dd3964 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d49b335d9..befd313ed8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -610,6 +610,7 @@ set_target_properties(usb-1.0 PROPERTIES POSITION_INDEPENDENT_CODE ON) include(cmake/ZArchive.cmake) include(cmake/Opus.cmake) +include(cmake/Sqlite.cmake) if(WIN32 OR APPLE) set(MI_BUILD_SHARED ON CACHE BOOL "" FORCE) set(MI_BUILD_STATIC OFF CACHE BOOL "" FORCE) diff --git a/cmake/Sqlite.cmake b/cmake/Sqlite.cmake new file mode 100644 index 0000000000..f5a1e05e64 --- /dev/null +++ b/cmake/Sqlite.cmake @@ -0,0 +1,68 @@ +enable_language(C) + +set(SQLITE_SOURCE_DIR ${CMAKE_SOURCE_DIR}/3rdparty/sqlite) +set(sqliteGen ${CMAKE_BINARY_DIR}/sqlite) +file(MAKE_DIRECTORY ${sqliteGen}) + +foreach(tool IN ITEMS lemon mkkeywordhash mksourceid) + add_executable(sqlite_${tool} EXCLUDE_FROM_ALL ${SQLITE_SOURCE_DIR}/tool/${tool}.c) + set_target_properties(sqlite_${tool} PROPERTIES OUTPUT_NAME ${tool} RUNTIME_OUTPUT_DIRECTORY ${sqliteGen}) +endforeach() +add_executable(sqlite_jimsh EXCLUDE_FROM_ALL ${SQLITE_SOURCE_DIR}/autosetup/jimsh0.c) +if(WIN32) + target_compile_definitions(sqlite_jimsh PRIVATE HAVE__FULLPATH) +else() + target_compile_definitions(sqlite_jimsh PRIVATE HAVE_REALPATH) +endif() +set_target_properties(sqlite_jimsh PROPERTIES OUTPUT_NAME jimsh RUNTIME_OUTPUT_DIRECTORY ${sqliteGen}) +set(sqliteTcl $) + +add_custom_command(OUTPUT ${sqliteGen}/sqlite3.h + COMMAND ${sqliteTcl} ${SQLITE_SOURCE_DIR}/tool/mksqlite3h.tcl ${SQLITE_SOURCE_DIR} -o ${sqliteGen}/sqlite3.h + WORKING_DIRECTORY ${sqliteGen} + DEPENDS sqlite_jimsh sqlite_mksourceid ${SQLITE_SOURCE_DIR}/src/sqlite.h.in ${SQLITE_SOURCE_DIR}/VERSION) +add_custom_command(OUTPUT ${sqliteGen}/parse.c ${sqliteGen}/parse.h + COMMAND ${CMAKE_COMMAND} -E copy ${SQLITE_SOURCE_DIR}/src/parse.y ${SQLITE_SOURCE_DIR}/tool/lempar.c ${sqliteGen} + COMMAND $ -S parse.y + WORKING_DIRECTORY ${sqliteGen} + DEPENDS sqlite_lemon ${SQLITE_SOURCE_DIR}/src/parse.y ${SQLITE_SOURCE_DIR}/tool/lempar.c) +add_custom_command(OUTPUT ${sqliteGen}/opcodes.h + COMMAND ${CMAKE_COMMAND} -E cat ${sqliteGen}/parse.h ${SQLITE_SOURCE_DIR}/src/vdbe.c > ${sqliteGen}/opcodes.in + COMMAND ${sqliteTcl} ${SQLITE_SOURCE_DIR}/tool/mkopcodeh.tcl < ${sqliteGen}/opcodes.in > ${sqliteGen}/opcodes.h + WORKING_DIRECTORY ${sqliteGen} + DEPENDS sqlite_jimsh ${sqliteGen}/parse.h ${SQLITE_SOURCE_DIR}/src/vdbe.c) +add_custom_command(OUTPUT ${sqliteGen}/opcodes.c + COMMAND ${sqliteTcl} ${SQLITE_SOURCE_DIR}/tool/mkopcodec.tcl ${sqliteGen}/opcodes.h > ${sqliteGen}/opcodes.c + WORKING_DIRECTORY ${sqliteGen} + DEPENDS sqlite_jimsh ${sqliteGen}/opcodes.h) +add_custom_command(OUTPUT ${sqliteGen}/keywordhash.h + COMMAND $ > ${sqliteGen}/keywordhash.h + WORKING_DIRECTORY ${sqliteGen} + DEPENDS sqlite_mkkeywordhash) +add_custom_command(OUTPUT ${sqliteGen}/pragma.h + COMMAND ${sqliteTcl} ${SQLITE_SOURCE_DIR}/tool/mkpragmatab.tcl ${sqliteGen}/pragma.h + WORKING_DIRECTORY ${sqliteGen} + DEPENDS sqlite_jimsh ${SQLITE_SOURCE_DIR}/tool/mkpragmatab.tcl) +add_custom_command(OUTPUT ${sqliteGen}/ctime.c + COMMAND ${sqliteTcl} ${SQLITE_SOURCE_DIR}/tool/mkctimec.tcl ${sqliteGen}/ctime.c + WORKING_DIRECTORY ${sqliteGen} + DEPENDS sqlite_jimsh ${SQLITE_SOURCE_DIR}/tool/mkctimec.tcl) + +set(sqliteSources) +foreach(name IN ITEMS alter analyze attach auth backup bitvec btmutex btree build callback carray complete date dbpage + dbstat delete expr fault fkey func global hash insert json legacy loadext main malloc mem0 mem1 mem2 mem3 + mem5 memdb memjournal mutex mutex_noop mutex_unix mutex_w32 notify os os_kv os_unix os_win pager pcache + pcache1 pragma prepare printf random resolve rowset select status table threads tokenize treeview trigger + utf update upsert util vacuum vdbe vdbeapi vdbeaux vdbeblob vdbemem vdbesort vdbetrace vdbevtab vtab wal + walker where wherecode whereexpr window) + list(APPEND sqliteSources ${SQLITE_SOURCE_DIR}/src/${name}.c) +endforeach() +add_library(sqlite3 STATIC EXCLUDE_FROM_ALL ${sqliteSources} ${sqliteGen}/parse.c ${sqliteGen}/opcodes.c + ${sqliteGen}/ctime.c ${sqliteGen}/sqlite3.h ${sqliteGen}/keywordhash.h ${sqliteGen}/pragma.h ${sqliteGen}/opcodes.h) +target_include_directories(sqlite3 PRIVATE ${sqliteGen} ${SQLITE_SOURCE_DIR}/src) +target_include_directories(sqlite3 SYSTEM INTERFACE ${sqliteGen}) +target_compile_definitions(sqlite3 PRIVATE SQLITE_THREADSAFE=1 SQLITE_OMIT_LOAD_EXTENSION SQLITE_CORE) +set_target_properties(sqlite3 PROPERTIES C_STANDARD 99 POSITION_INDEPENDENT_CODE ON C_VISIBILITY_PRESET hidden) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_libraries(sqlite3 PRIVATE m pthread) +endif() diff --git a/core/libs/prx/libSceNKWebKitRequirements/CMakeLists.txt b/core/libs/prx/libSceNKWebKitRequirements/CMakeLists.txt new file mode 100644 index 0000000000..250909b8a9 --- /dev/null +++ b/core/libs/prx/libSceNKWebKitRequirements/CMakeLists.txt @@ -0,0 +1,21 @@ +add_library(libSceNKWebKitRequirements SHARED EXCLUDE_FROM_ALL Export.cpp src/Sqlite.cpp) +target_include_directories(libSceNKWebKitRequirements PRIVATE ${LIBS_INCLUDE_DIR}) +set_target_properties(libSceNKWebKitRequirements PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(libSceNKWebKitRequirements) +target_link_libraries(libSceNKWebKitRequirements PRIVATE sqlite3) +if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_options(libSceNKWebKitRequirements PRIVATE LINKER:--exclude-libs,ALL) +endif() + +if(BUILD_TESTING) + add_test_executable(guest_nk_req_sqlite_tests ${LIBS_INCLUDE_DIR}/tests/GuestNKWebKitSqlite.cpp) + target_include_directories(guest_nk_req_sqlite_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_nk_req_sqlite_tests PRIVATE libSceNKWebKitRequirements libc) + configure_windows_unwind(guest_nk_req_sqlite_tests) + add_test(NAME guest_nk_req_sqlite COMMAND guest_nk_req_sqlite_tests) + set_tests_properties(guest_nk_req_sqlite PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_nk_req_sqlite PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNKWebKitRequirements/Export.cpp b/core/libs/prx/libSceNKWebKitRequirements/Export.cpp index a395929a02..9cc9e9ee50 100644 --- a/core/libs/prx/libSceNKWebKitRequirements/Export.cpp +++ b/core/libs/prx/libSceNKWebKitRequirements/Export.cpp @@ -201,206 +201,6 @@ int APS5_VABI psl_unregistrable_domain_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sqlite3_bind_blob_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_bind_double_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_bind_int_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_bind_int64_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_bind_null_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_bind_parameter_count_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_bind_text16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_busy_handler_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_busy_timeout_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_clear_bindings_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_close_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_blob_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_bytes_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_bytes16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_decltype_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_double_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_int_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_int64_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_name16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_text16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_type_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_value_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_create_collation_v2_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_create_function_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_data_count_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_errcode_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_errmsg_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_errstr_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_extended_result_codes_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_finalize_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_get_autocommit_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_initialize_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_interrupt_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_last_insert_rowid_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_next_stmt_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_open_v2_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_prepare_v2_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_reset_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_result_error_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_set_authorizer_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_sql_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_step_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_stmt_busy_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_total_changes_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_user_data_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_value_double_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_value_text16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_value_type_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_wal_checkpoint_v2_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_wal_hook_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI uncompress_nid_postfix() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceNKWebKitRequirements/include/Identified.hpp b/core/libs/prx/libSceNKWebKitRequirements/include/Identified.hpp index 6a2a2dc188..5184f54665 100644 --- a/core/libs/prx/libSceNKWebKitRequirements/include/Identified.hpp +++ b/core/libs/prx/libSceNKWebKitRequirements/include/Identified.hpp @@ -50,56 +50,6 @@ int APS5_VABI psl_suffix_count_nid_postfix(); int APS5_VABI psl_suffix_exception_count_nid_postfix(); int APS5_VABI psl_suffix_wildcard_count_nid_postfix(); int APS5_VABI psl_unregistrable_domain_nid_postfix(); -int APS5_VABI sqlite3_bind_blob_nid_postfix(); -int APS5_VABI sqlite3_bind_double_nid_postfix(); -int APS5_VABI sqlite3_bind_int_nid_postfix(); -int APS5_VABI sqlite3_bind_int64_nid_postfix(); -int APS5_VABI sqlite3_bind_null_nid_postfix(); -int APS5_VABI sqlite3_bind_parameter_count_nid_postfix(); -int APS5_VABI sqlite3_bind_text16_nid_postfix(); -int APS5_VABI sqlite3_busy_handler_nid_postfix(); -int APS5_VABI sqlite3_busy_timeout_nid_postfix(); -int APS5_VABI sqlite3_clear_bindings_nid_postfix(); -int APS5_VABI sqlite3_close_nid_postfix(); -int APS5_VABI sqlite3_column_blob_nid_postfix(); -int APS5_VABI sqlite3_column_bytes_nid_postfix(); -int APS5_VABI sqlite3_column_bytes16_nid_postfix(); -int APS5_VABI sqlite3_column_decltype_nid_postfix(); -int APS5_VABI sqlite3_column_double_nid_postfix(); -int APS5_VABI sqlite3_column_int_nid_postfix(); -int APS5_VABI sqlite3_column_int64_nid_postfix(); -int APS5_VABI sqlite3_column_name16_nid_postfix(); -int APS5_VABI sqlite3_column_text16_nid_postfix(); -int APS5_VABI sqlite3_column_type_nid_postfix(); -int APS5_VABI sqlite3_column_value_nid_postfix(); -int APS5_VABI sqlite3_create_collation_v2_nid_postfix(); -int APS5_VABI sqlite3_create_function_nid_postfix(); -int APS5_VABI sqlite3_data_count_nid_postfix(); -int APS5_VABI sqlite3_errcode_nid_postfix(); -int APS5_VABI sqlite3_errmsg_nid_postfix(); -int APS5_VABI sqlite3_errstr_nid_postfix(); -int APS5_VABI sqlite3_extended_result_codes_nid_postfix(); -int APS5_VABI sqlite3_finalize_nid_postfix(); -int APS5_VABI sqlite3_get_autocommit_nid_postfix(); -int APS5_VABI sqlite3_initialize_nid_postfix(); -int APS5_VABI sqlite3_interrupt_nid_postfix(); -int APS5_VABI sqlite3_last_insert_rowid_nid_postfix(); -int APS5_VABI sqlite3_next_stmt_nid_postfix(); -int APS5_VABI sqlite3_open_v2_nid_postfix(); -int APS5_VABI sqlite3_prepare_v2_nid_postfix(); -int APS5_VABI sqlite3_reset_nid_postfix(); -int APS5_VABI sqlite3_result_error_nid_postfix(); -int APS5_VABI sqlite3_set_authorizer_nid_postfix(); -int APS5_VABI sqlite3_sql_nid_postfix(); -int APS5_VABI sqlite3_step_nid_postfix(); -int APS5_VABI sqlite3_stmt_busy_nid_postfix(); -int APS5_VABI sqlite3_total_changes_nid_postfix(); -int APS5_VABI sqlite3_user_data_nid_postfix(); -int APS5_VABI sqlite3_value_double_nid_postfix(); -int APS5_VABI sqlite3_value_text16_nid_postfix(); -int APS5_VABI sqlite3_value_type_nid_postfix(); -int APS5_VABI sqlite3_wal_checkpoint_v2_nid_postfix(); -int APS5_VABI sqlite3_wal_hook_nid_postfix(); int APS5_VABI uncompress_nid_postfix(); int APS5_VABI xmlByteConsumed_nid_postfix(); int APS5_VABI xmlCreateMemoryParserCtxt_nid_postfix(); diff --git a/core/libs/prx/libSceNKWebKitRequirements/include/Sqlite.hpp b/core/libs/prx/libSceNKWebKitRequirements/include/Sqlite.hpp new file mode 100644 index 0000000000..1f4dd1bbaf --- /dev/null +++ b/core/libs/prx/libSceNKWebKitRequirements/include/Sqlite.hpp @@ -0,0 +1,79 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +struct sqlite3; +struct sqlite3_stmt; +struct sqlite3_value; +struct sqlite3_context; + +namespace NKWebKit { +using GuestSqliteDestructor = void (APS5_VABI *)(void*); +using GuestSqliteBusy = int (APS5_VABI *)(void*, int); +using GuestSqliteCompare = int (APS5_VABI *)(void*, int, const void*, int, const void*); +using GuestSqliteFunction = void (APS5_VABI *)(sqlite3_context*, int, sqlite3_value**); +using GuestSqliteFinal = void (APS5_VABI *)(sqlite3_context*); +using GuestSqliteAuthorizer = int (APS5_VABI *)(void*, int, const char*, const char*, const char*, const char*); +using GuestSqliteWalHook = int (APS5_VABI *)(void*, sqlite3*, const char*, int); +} + +extern "C" { +int APS5_VABI sqlite3_bind_blob_nid_postfix(sqlite3_stmt* statement, int index, const void* data, int size, + NKWebKit::GuestSqliteDestructor destructor); +int APS5_VABI sqlite3_bind_double_nid_postfix(sqlite3_stmt* statement, int index, double value); +int APS5_VABI sqlite3_bind_int_nid_postfix(sqlite3_stmt* statement, int index, int value); +int APS5_VABI sqlite3_bind_int64_nid_postfix(sqlite3_stmt* statement, int index, std::int64_t value); +int APS5_VABI sqlite3_bind_null_nid_postfix(sqlite3_stmt* statement, int index); +int APS5_VABI sqlite3_bind_parameter_count_nid_postfix(sqlite3_stmt* statement); +int APS5_VABI sqlite3_bind_text16_nid_postfix(sqlite3_stmt* statement, int index, const void* text, int size, + NKWebKit::GuestSqliteDestructor destructor); +int APS5_VABI sqlite3_busy_handler_nid_postfix(sqlite3* db, NKWebKit::GuestSqliteBusy handler, void* argument); +int APS5_VABI sqlite3_busy_timeout_nid_postfix(sqlite3* db, int milliseconds); +int APS5_VABI sqlite3_clear_bindings_nid_postfix(sqlite3_stmt* statement); +int APS5_VABI sqlite3_close_nid_postfix(sqlite3* db); +const void* APS5_VABI sqlite3_column_blob_nid_postfix(sqlite3_stmt* statement, int column); +int APS5_VABI sqlite3_column_bytes_nid_postfix(sqlite3_stmt* statement, int column); +int APS5_VABI sqlite3_column_bytes16_nid_postfix(sqlite3_stmt* statement, int column); +const char* APS5_VABI sqlite3_column_decltype_nid_postfix(sqlite3_stmt* statement, int column); +double APS5_VABI sqlite3_column_double_nid_postfix(sqlite3_stmt* statement, int column); +int APS5_VABI sqlite3_column_int_nid_postfix(sqlite3_stmt* statement, int column); +std::int64_t APS5_VABI sqlite3_column_int64_nid_postfix(sqlite3_stmt* statement, int column); +const void* APS5_VABI sqlite3_column_name16_nid_postfix(sqlite3_stmt* statement, int column); +const void* APS5_VABI sqlite3_column_text16_nid_postfix(sqlite3_stmt* statement, int column); +int APS5_VABI sqlite3_column_type_nid_postfix(sqlite3_stmt* statement, int column); +sqlite3_value* APS5_VABI sqlite3_column_value_nid_postfix(sqlite3_stmt* statement, int column); +int APS5_VABI sqlite3_create_collation_v2_nid_postfix(sqlite3* db, const char* name, int encoding, void* argument, + NKWebKit::GuestSqliteCompare compare, NKWebKit::GuestSqliteDestructor destroy); +int APS5_VABI sqlite3_create_function_nid_postfix(sqlite3* db, const char* name, int arguments, int encoding, + void* application, NKWebKit::GuestSqliteFunction function, NKWebKit::GuestSqliteFunction step, + NKWebKit::GuestSqliteFinal final); +int APS5_VABI sqlite3_data_count_nid_postfix(sqlite3_stmt* statement); +int APS5_VABI sqlite3_errcode_nid_postfix(sqlite3* db); +const char* APS5_VABI sqlite3_errmsg_nid_postfix(sqlite3* db); +const char* APS5_VABI sqlite3_errstr_nid_postfix(int code); +int APS5_VABI sqlite3_extended_result_codes_nid_postfix(sqlite3* db, int enabled); +int APS5_VABI sqlite3_finalize_nid_postfix(sqlite3_stmt* statement); +int APS5_VABI sqlite3_get_autocommit_nid_postfix(sqlite3* db); +int APS5_VABI sqlite3_initialize_nid_postfix(); +void APS5_VABI sqlite3_interrupt_nid_postfix(sqlite3* db); +std::int64_t APS5_VABI sqlite3_last_insert_rowid_nid_postfix(sqlite3* db); +sqlite3_stmt* APS5_VABI sqlite3_next_stmt_nid_postfix(sqlite3* db, sqlite3_stmt* statement); +int APS5_VABI sqlite3_open_v2_nid_postfix(const char* filename, sqlite3** db, int flags, const char* vfs); +int APS5_VABI sqlite3_prepare_v2_nid_postfix(sqlite3* db, const char* sql, int size, sqlite3_stmt** statement, + const char** tail); +int APS5_VABI sqlite3_reset_nid_postfix(sqlite3_stmt* statement); +void APS5_VABI sqlite3_result_error_nid_postfix(sqlite3_context* context, const char* message, int size); +int APS5_VABI sqlite3_set_authorizer_nid_postfix(sqlite3* db, NKWebKit::GuestSqliteAuthorizer authorizer, + void* argument); +const char* APS5_VABI sqlite3_sql_nid_postfix(sqlite3_stmt* statement); +int APS5_VABI sqlite3_step_nid_postfix(sqlite3_stmt* statement); +int APS5_VABI sqlite3_stmt_busy_nid_postfix(sqlite3_stmt* statement); +int APS5_VABI sqlite3_total_changes_nid_postfix(sqlite3* db); +void* APS5_VABI sqlite3_user_data_nid_postfix(sqlite3_context* context); +double APS5_VABI sqlite3_value_double_nid_postfix(sqlite3_value* value); +const void* APS5_VABI sqlite3_value_text16_nid_postfix(sqlite3_value* value); +int APS5_VABI sqlite3_value_type_nid_postfix(sqlite3_value* value); +int APS5_VABI sqlite3_wal_checkpoint_v2_nid_postfix(sqlite3* db, const char* schema, int mode, int* logFrames, + int* checkpointedFrames); +void* APS5_VABI sqlite3_wal_hook_nid_postfix(sqlite3* db, NKWebKit::GuestSqliteWalHook hook, void* argument); +} diff --git a/core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp b/core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp new file mode 100644 index 0000000000..e9f89eb127 --- /dev/null +++ b/core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp @@ -0,0 +1,380 @@ +#include "prx/libSceNKWebKitRequirements/include/Sqlite.hpp" +#include "prx/libc/include/General.hpp" + +#include + +#include +#include +#include + +namespace { + +constexpr const char* GuestHooksKey = "anyps5.guest"; + +struct GuestHooks { + NKWebKit::GuestSqliteBusy busy = nullptr; + void* busyArgument = nullptr; + NKWebKit::GuestSqliteAuthorizer authorizer = nullptr; + void* authorizerArgument = nullptr; + NKWebKit::GuestSqliteWalHook walHook = nullptr; + void* walArgument = nullptr; +}; + +struct GuestCollation { + NKWebKit::GuestSqliteCompare compare; + NKWebKit::GuestSqliteDestructor destroy; + void* argument; +}; + +struct GuestFunction { + NKWebKit::GuestSqliteFunction function; + NKWebKit::GuestSqliteFunction step; + NKWebKit::GuestSqliteFinal final; + void* application; +}; + +void FreeHooks(void* hooks) { + delete static_cast(hooks); +} + +GuestHooks* Hooks(sqlite3* db) { + if (auto* hooks = static_cast(sqlite3_get_clientdata(db, GuestHooksKey))) { + return hooks; + } + auto* hooks = new (std::nothrow) GuestHooks(); + if (hooks && sqlite3_set_clientdata(db, GuestHooksKey, hooks, FreeHooks) != SQLITE_OK) { + return nullptr; + } + return hooks; +} + +int CallBusy(void* hooks, int count) { + auto* guest = static_cast(hooks); + return guest->busy(guest->busyArgument, count); +} + +int CallAuthorizer(void* hooks, int action, const char* first, const char* second, const char* schema, + const char* trigger) { + auto* guest = static_cast(hooks); + return guest->authorizer(guest->authorizerArgument, action, first, second, schema, trigger); +} + +int CallWalHook(void* hooks, sqlite3* db, const char* schema, int frames) { + auto* guest = static_cast(hooks); + return guest->walHook(guest->walArgument, db, schema, frames); +} + +int CallCompare(void* collation, int leftSize, const void* left, int rightSize, const void* right) { + auto* guest = static_cast(collation); + return guest->compare(guest->argument, leftSize, left, rightSize, right); +} + +void DestroyCollation(void* collation) { + auto* guest = static_cast(collation); + if (guest->destroy) { + guest->destroy(guest->argument); + } + delete guest; +} + +void CallFunction(sqlite3_context* context, int count, sqlite3_value** values) { + static_cast(sqlite3_user_data(context))->function(context, count, values); +} + +void CallStep(sqlite3_context* context, int count, sqlite3_value** values) { + static_cast(sqlite3_user_data(context))->step(context, count, values); +} + +void CallFinal(sqlite3_context* context) { + static_cast(sqlite3_user_data(context))->final(context); +} + +void DestroyFunction(void* function) { + delete static_cast(function); +} + +bool SpecialDestructor(NKWebKit::GuestSqliteDestructor destructor) { + const auto value = reinterpret_cast(destructor); + return value == 0 || value == -1; +} + +} + +extern "C" { + +int APS5_VABI sqlite3_bind_blob_nid_postfix(sqlite3_stmt* statement, int index, const void* data, int size, + NKWebKit::GuestSqliteDestructor destructor) { + const bool owned = !SpecialDestructor(destructor); + const int result = sqlite3_bind_blob(statement, index, data, size, + owned || destructor ? SQLITE_TRANSIENT : SQLITE_STATIC); + if (owned && data) { + destructor(const_cast(data)); + } + return result; +} + +int APS5_VABI sqlite3_bind_double_nid_postfix(sqlite3_stmt* statement, int index, double value) { + return sqlite3_bind_double(statement, index, value); +} + +int APS5_VABI sqlite3_bind_int_nid_postfix(sqlite3_stmt* statement, int index, int value) { + return sqlite3_bind_int(statement, index, value); +} + +int APS5_VABI sqlite3_bind_int64_nid_postfix(sqlite3_stmt* statement, int index, std::int64_t value) { + return sqlite3_bind_int64(statement, index, value); +} + +int APS5_VABI sqlite3_bind_null_nid_postfix(sqlite3_stmt* statement, int index) { + return sqlite3_bind_null(statement, index); +} + +int APS5_VABI sqlite3_bind_parameter_count_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_bind_parameter_count(statement); +} + +int APS5_VABI sqlite3_bind_text16_nid_postfix(sqlite3_stmt* statement, int index, const void* text, int size, + NKWebKit::GuestSqliteDestructor destructor) { + const bool owned = !SpecialDestructor(destructor); + const int result = sqlite3_bind_text16(statement, index, text, size, + owned || destructor ? SQLITE_TRANSIENT : SQLITE_STATIC); + if (owned && text) { + destructor(const_cast(text)); + } + return result; +} + +int APS5_VABI sqlite3_busy_handler_nid_postfix(sqlite3* db, NKWebKit::GuestSqliteBusy handler, void* argument) { + if (!db || !handler) { + return sqlite3_busy_handler(db, nullptr, nullptr); + } + sqlite3_mutex_enter(sqlite3_db_mutex(db)); + GuestHooks* hooks = Hooks(db); + int result = SQLITE_NOMEM; + if (hooks) { + hooks->busy = handler; + hooks->busyArgument = argument; + result = sqlite3_busy_handler(db, CallBusy, hooks); + } + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + return result; +} + +int APS5_VABI sqlite3_busy_timeout_nid_postfix(sqlite3* db, int milliseconds) { + return sqlite3_busy_timeout(db, milliseconds); +} + +int APS5_VABI sqlite3_clear_bindings_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_clear_bindings(statement); +} + +int APS5_VABI sqlite3_close_nid_postfix(sqlite3* db) { + return sqlite3_close(db); +} + +const void* APS5_VABI sqlite3_column_blob_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_blob(statement, column); +} + +int APS5_VABI sqlite3_column_bytes_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_bytes(statement, column); +} + +int APS5_VABI sqlite3_column_bytes16_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_bytes16(statement, column); +} + +const char* APS5_VABI sqlite3_column_decltype_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_decltype(statement, column); +} + +double APS5_VABI sqlite3_column_double_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_double(statement, column); +} + +int APS5_VABI sqlite3_column_int_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_int(statement, column); +} + +std::int64_t APS5_VABI sqlite3_column_int64_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_int64(statement, column); +} + +const void* APS5_VABI sqlite3_column_name16_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_name16(statement, column); +} + +const void* APS5_VABI sqlite3_column_text16_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_text16(statement, column); +} + +int APS5_VABI sqlite3_column_type_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_type(statement, column); +} + +sqlite3_value* APS5_VABI sqlite3_column_value_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_value(statement, column); +} + +int APS5_VABI sqlite3_create_collation_v2_nid_postfix(sqlite3* db, const char* name, int encoding, void* argument, + NKWebKit::GuestSqliteCompare compare, NKWebKit::GuestSqliteDestructor destroy) { + auto* collation = new (std::nothrow) GuestCollation{compare, destroy, argument}; + if (!collation) { + return SQLITE_NOMEM; + } + const int result = sqlite3_create_collation_v2(db, name, encoding, collation, compare ? CallCompare : nullptr, + DestroyCollation); + if (result != SQLITE_OK) { + delete collation; + } + return result; +} + +int APS5_VABI sqlite3_create_function_nid_postfix(sqlite3* db, const char* name, int arguments, int encoding, + void* application, NKWebKit::GuestSqliteFunction function, NKWebKit::GuestSqliteFunction step, + NKWebKit::GuestSqliteFinal final) { + auto* guest = new (std::nothrow) GuestFunction{function, step, final, application}; + if (!guest) { + return SQLITE_NOMEM; + } + return sqlite3_create_function_v2(db, name, arguments, encoding, guest, function ? CallFunction : nullptr, + step ? CallStep : nullptr, final ? CallFinal : nullptr, DestroyFunction); +} + +int APS5_VABI sqlite3_data_count_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_data_count(statement); +} + +int APS5_VABI sqlite3_errcode_nid_postfix(sqlite3* db) { + return sqlite3_errcode(db); +} + +const char* APS5_VABI sqlite3_errmsg_nid_postfix(sqlite3* db) { + return sqlite3_errmsg(db); +} + +const char* APS5_VABI sqlite3_errstr_nid_postfix(int code) { + return sqlite3_errstr(code); +} + +int APS5_VABI sqlite3_extended_result_codes_nid_postfix(sqlite3* db, int enabled) { + return sqlite3_extended_result_codes(db, enabled); +} + +int APS5_VABI sqlite3_finalize_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_finalize(statement); +} + +int APS5_VABI sqlite3_get_autocommit_nid_postfix(sqlite3* db) { + return sqlite3_get_autocommit(db); +} + +int APS5_VABI sqlite3_initialize_nid_postfix() { + return sqlite3_initialize(); +} + +void APS5_VABI sqlite3_interrupt_nid_postfix(sqlite3* db) { + sqlite3_interrupt(db); +} + +std::int64_t APS5_VABI sqlite3_last_insert_rowid_nid_postfix(sqlite3* db) { + return sqlite3_last_insert_rowid(db); +} + +sqlite3_stmt* APS5_VABI sqlite3_next_stmt_nid_postfix(sqlite3* db, sqlite3_stmt* statement) { + return sqlite3_next_stmt(db, statement); +} + +int APS5_VABI sqlite3_open_v2_nid_postfix(const char* filename, sqlite3** db, int flags, const char* vfs) { + static_cast(vfs); + const bool unmapped = !filename || !*filename || std::strcmp(filename, ":memory:") == 0 || + (flags & SQLITE_OPEN_MEMORY) || ((flags & SQLITE_OPEN_URI) && std::strncmp(filename, "file:", 5) == 0); + if (unmapped) { + return sqlite3_open_v2(filename, db, flags, nullptr); + } + const std::u8string host = ResolvePath_nid_no_patch(filename).u8string(); + return sqlite3_open_v2(reinterpret_cast(host.c_str()), db, flags, nullptr); +} + +int APS5_VABI sqlite3_prepare_v2_nid_postfix(sqlite3* db, const char* sql, int size, sqlite3_stmt** statement, + const char** tail) { + return sqlite3_prepare_v2(db, sql, size, statement, tail); +} + +int APS5_VABI sqlite3_reset_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_reset(statement); +} + +void APS5_VABI sqlite3_result_error_nid_postfix(sqlite3_context* context, const char* message, int size) { + sqlite3_result_error(context, message, size); +} + +int APS5_VABI sqlite3_set_authorizer_nid_postfix(sqlite3* db, NKWebKit::GuestSqliteAuthorizer authorizer, + void* argument) { + if (!db || !authorizer) { + return sqlite3_set_authorizer(db, nullptr, nullptr); + } + sqlite3_mutex_enter(sqlite3_db_mutex(db)); + GuestHooks* hooks = Hooks(db); + int result = SQLITE_NOMEM; + if (hooks) { + hooks->authorizer = authorizer; + hooks->authorizerArgument = argument; + result = sqlite3_set_authorizer(db, CallAuthorizer, hooks); + } + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + return result; +} + +const char* APS5_VABI sqlite3_sql_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_sql(statement); +} + +int APS5_VABI sqlite3_step_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_step(statement); +} + +int APS5_VABI sqlite3_stmt_busy_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_stmt_busy(statement); +} + +int APS5_VABI sqlite3_total_changes_nid_postfix(sqlite3* db) { + return sqlite3_total_changes(db); +} + +void* APS5_VABI sqlite3_user_data_nid_postfix(sqlite3_context* context) { + return static_cast(sqlite3_user_data(context))->application; +} + +double APS5_VABI sqlite3_value_double_nid_postfix(sqlite3_value* value) { + return sqlite3_value_double(value); +} + +const void* APS5_VABI sqlite3_value_text16_nid_postfix(sqlite3_value* value) { + return sqlite3_value_text16(value); +} + +int APS5_VABI sqlite3_value_type_nid_postfix(sqlite3_value* value) { + return sqlite3_value_type(value); +} + +int APS5_VABI sqlite3_wal_checkpoint_v2_nid_postfix(sqlite3* db, const char* schema, int mode, int* logFrames, + int* checkpointedFrames) { + return sqlite3_wal_checkpoint_v2(db, schema, mode, logFrames, checkpointedFrames); +} + +void* APS5_VABI sqlite3_wal_hook_nid_postfix(sqlite3* db, NKWebKit::GuestSqliteWalHook hook, void* argument) { + sqlite3_mutex_enter(sqlite3_db_mutex(db)); + GuestHooks* hooks = Hooks(db); + void* previous = nullptr; + if (hooks) { + void* host = sqlite3_wal_hook(db, hook ? CallWalHook : nullptr, hook ? hooks : nullptr); + previous = host == hooks ? hooks->walArgument : host; + hooks->walHook = hook; + hooks->walArgument = argument; + } + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + return previous; +} + +} diff --git a/core/libs/tests/GuestNKWebKitSqlite.cpp b/core/libs/tests/GuestNKWebKitSqlite.cpp new file mode 100644 index 0000000000..127cbcb6c8 --- /dev/null +++ b/core/libs/tests/GuestNKWebKitSqlite.cpp @@ -0,0 +1,348 @@ +#include "prx/libSceNKWebKitRequirements/include/Sqlite.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int SQLITE_OK = 0; +constexpr int SQLITE_ERROR = 1; +constexpr int SQLITE_BUSY = 5; +constexpr int SQLITE_INTERRUPT = 9; +constexpr int SQLITE_AUTH = 23; +constexpr int SQLITE_ROW = 100; +constexpr int SQLITE_DONE = 101; +constexpr int SQLITE_CONSTRAINT_UNIQUE = 2067; +constexpr int SQLITE_OPEN_READWRITE = 0x2; +constexpr int SQLITE_OPEN_CREATE = 0x4; +constexpr int SQLITE_UTF8 = 1; +constexpr int SQLITE_UTF16LE = 2; +constexpr int SQLITE_INTEGER = 1; +constexpr int SQLITE_FLOAT = 2; +constexpr int SQLITE_TEXT = 3; +constexpr int SQLITE_BLOB = 4; +constexpr int SQLITE_NULL = 5; +constexpr int SQLITE_DENY = 1; +constexpr int SQLITE_DELETE = 9; +constexpr int SQLITE_CHECKPOINT_PASSIVE = 0; +const auto SQLITE_STATIC = reinterpret_cast(std::intptr_t{0}); +const auto SQLITE_TRANSIENT = reinterpret_cast(std::intptr_t{-1}); + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "NKWebKit SQLite assertion failed: %s\n", message); + std::abort(); +} + +int destroyedBuffers = 0; +int destroyedCollations = 0; +int functionCalls = 0; +int stepCalls = 0; +int finalCalls = 0; +int busyCalls = 0; +int walCalls = 0; +double lastFunctionValue = 0; +void* lastFunctionApplication = nullptr; +int walFrames = 0; +std::string walSchema; +sqlite3* interruptTarget = nullptr; + +void APS5_VABI FreeBuffer(void* buffer) { + ++destroyedBuffers; + std::memset(buffer, 0x5A, 8); + std::free(buffer); +} + +int APS5_VABI ReverseCompare(void* argument, int leftSize, const void* left, int rightSize, const void* right) { + Require(*static_cast(argument) == 0x51, "collation argument"); + const int common = leftSize < rightSize ? leftSize : rightSize; + const int order = std::memcmp(left, right, static_cast(common)); + return order ? -order : rightSize - leftSize; +} + +void APS5_VABI DestroyCollation(void* argument) { + Require(*static_cast(argument) == 0x51, "collation destroy argument"); + ++destroyedCollations; +} + +void APS5_VABI Probe(sqlite3_context* context, int count, sqlite3_value** values) { + ++functionCalls; + Require(count == 1, "function argument count"); + lastFunctionApplication = sqlite3_user_data_nid_postfix(context); + if (sqlite3_value_type_nid_postfix(values[0]) == SQLITE_TEXT) { + sqlite3_result_error_nid_postfix(context, "probe rejects text", -1); + return; + } + lastFunctionValue = sqlite3_value_double_nid_postfix(values[0]); +} + +void APS5_VABI Interrupt(sqlite3_context*, int, sqlite3_value**) { + sqlite3_interrupt_nid_postfix(interruptTarget); +} + +void APS5_VABI CountStep(sqlite3_context* context, int, sqlite3_value**) { + Require(sqlite3_user_data_nid_postfix(context) == &stepCalls, "aggregate application"); + ++stepCalls; +} + +void APS5_VABI CountFinal(sqlite3_context*) { + ++finalCalls; +} + +int APS5_VABI DenyDelete(void* argument, int action, const char*, const char*, const char*, const char*) { + Require(*static_cast(argument) == 0xA7, "authorizer argument"); + return action == SQLITE_DELETE ? SQLITE_DENY : SQLITE_OK; +} + +int APS5_VABI GiveUp(void* argument, int count) { + Require(*static_cast(argument) == 0xB5, "busy argument"); + Require(count == busyCalls, "busy count"); + ++busyCalls; + return 0; +} + +int APS5_VABI RecordWal(void* argument, sqlite3*, const char* schema, int frames) { + Require(*static_cast(argument) == 0xC3, "wal argument"); + ++walCalls; + walSchema = schema; + walFrames = frames; + return SQLITE_OK; +} + +sqlite3* Open(const std::string& name) { + sqlite3* db = nullptr; + const int result = sqlite3_open_v2_nid_postfix(name.c_str(), &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + nullptr); + if (result != SQLITE_OK) { + std::fprintf(stderr, "open %s: %d %s\n", name.c_str(), result, db ? sqlite3_errmsg_nid_postfix(db) : ""); + } + Require(result == SQLITE_OK, "open"); + Require(db != nullptr, "open handle"); + return db; +} + +sqlite3_stmt* Prepare(sqlite3* db, const char* sql) { + sqlite3_stmt* statement = nullptr; + const char* tail = nullptr; + Require(sqlite3_prepare_v2_nid_postfix(db, sql, -1, &statement, &tail) == SQLITE_OK, sql); + Require(statement && tail && *tail == '\0', "prepare tail"); + return statement; +} + +void Execute(sqlite3* db, const char* sql) { + sqlite3_stmt* statement = Prepare(db, sql); + int result = SQLITE_ROW; + while (result == SQLITE_ROW) { + result = sqlite3_step_nid_postfix(statement); + } + Require(result == SQLITE_DONE, sql); + Require(sqlite3_finalize_nid_postfix(statement) == SQLITE_OK, "finalize"); +} + +void CheckStatements(sqlite3* db) { + Execute(db, "CREATE TABLE t(a INTEGER, b REAL, c TEXT, d BLOB)"); + sqlite3_stmt* insert = Prepare(db, "INSERT INTO t VALUES(?, ?, ?, ?)"); + Require(sqlite3_bind_parameter_count_nid_postfix(insert) == 4, "parameter count"); + Require(sqlite3_stmt_busy_nid_postfix(insert) == 0, "idle statement"); + Require(sqlite3_bind_int64_nid_postfix(insert, 1, 0x123456789AB) == SQLITE_OK, "bind int64"); + Require(sqlite3_bind_double_nid_postfix(insert, 2, 2.5) == SQLITE_OK, "bind double"); + auto* text = static_cast(std::malloc(16)); + std::memcpy(text, u"hé€", 8); + Require(sqlite3_bind_text16_nid_postfix(insert, 3, text, 6, FreeBuffer) == SQLITE_OK, "bind text16"); + Require(destroyedBuffers == 1, "text16 destructor runs after the copy"); + auto* blob = static_cast(std::malloc(8)); + std::memcpy(blob, "\x01\x02\x03\x00\x05\x06\x07\x08", 8); + Require(sqlite3_bind_blob_nid_postfix(insert, 4, blob, 8, FreeBuffer) == SQLITE_OK, "bind blob"); + Require(destroyedBuffers == 2, "blob destructor runs after the copy"); + Require(sqlite3_step_nid_postfix(insert) == SQLITE_DONE, "insert"); + Require(sqlite3_last_insert_rowid_nid_postfix(db) == 1, "last rowid"); + Require(sqlite3_reset_nid_postfix(insert) == SQLITE_OK, "reset"); + Require(sqlite3_clear_bindings_nid_postfix(insert) == SQLITE_OK, "clear bindings"); + static const unsigned char fixed[3] = {9, 8, 7}; + Require(sqlite3_bind_int_nid_postfix(insert, 1, -7) == SQLITE_OK, "bind int"); + Require(sqlite3_bind_null_nid_postfix(insert, 2) == SQLITE_OK, "bind null"); + Require(sqlite3_bind_text16_nid_postfix(insert, 3, u"z", -1, SQLITE_TRANSIENT) == SQLITE_OK, "transient"); + Require(sqlite3_bind_blob_nid_postfix(insert, 4, fixed, 3, SQLITE_STATIC) == SQLITE_OK, "static"); + Require(destroyedBuffers == 2, "static data is not released"); + Require(sqlite3_bind_blob_nid_postfix(insert, 5, std::malloc(8), 8, FreeBuffer) != SQLITE_OK, "bad index"); + Require(destroyedBuffers == 3, "destructor runs when the bind fails"); + Require(sqlite3_step_nid_postfix(insert) == SQLITE_DONE, "second insert"); + Require(sqlite3_total_changes_nid_postfix(db) == 2, "total changes"); + Require(sqlite3_get_autocommit_nid_postfix(db) == 1, "autocommit"); + Require(sqlite3_next_stmt_nid_postfix(db, nullptr) == insert, "next statement"); + Require(sqlite3_finalize_nid_postfix(insert) == SQLITE_OK, "finalize insert"); + + const char* query = "SELECT a, b, c, d FROM t ORDER BY rowid"; + sqlite3_stmt* select = Prepare(db, query); + Require(std::strcmp(sqlite3_sql_nid_postfix(select), query) == 0, "statement text"); + Require(sqlite3_data_count_nid_postfix(select) == 0, "no row yet"); + Require(sqlite3_step_nid_postfix(select) == SQLITE_ROW, "first row"); + Require(sqlite3_stmt_busy_nid_postfix(select) != 0, "busy statement"); + Require(sqlite3_data_count_nid_postfix(select) == 4, "data count"); + Require(sqlite3_column_type_nid_postfix(select, 0) == SQLITE_INTEGER, "integer type"); + Require(sqlite3_column_int64_nid_postfix(select, 0) == 0x123456789AB, "column int64"); + Require(sqlite3_column_int_nid_postfix(select, 0) == static_cast(0x456789AB), "column int"); + Require(sqlite3_column_type_nid_postfix(select, 1) == SQLITE_FLOAT, "float type"); + Require(sqlite3_column_double_nid_postfix(select, 1) == 2.5, "column double"); + Require(sqlite3_column_type_nid_postfix(select, 2) == SQLITE_TEXT, "text type"); + const auto* column = static_cast(sqlite3_column_text16_nid_postfix(select, 2)); + Require(std::memcmp(column, u"hé€", 8) == 0, "column text16"); + Require(sqlite3_column_bytes16_nid_postfix(select, 2) == 6, "column bytes16"); + Require(sqlite3_column_type_nid_postfix(select, 3) == SQLITE_BLOB, "blob type"); + Require(sqlite3_column_bytes_nid_postfix(select, 3) == 8, "column bytes"); + Require(std::memcmp(sqlite3_column_blob_nid_postfix(select, 3), "\x01\x02\x03\x00\x05\x06\x07\x08", 8) == 0, + "column blob"); + Require(std::strcmp(sqlite3_column_decltype_nid_postfix(select, 0), "INTEGER") == 0, "decltype"); + Require(std::memcmp(sqlite3_column_name16_nid_postfix(select, 2), u"c", 4) == 0, "column name16"); + sqlite3_value* value = sqlite3_column_value_nid_postfix(select, 1); + Require(sqlite3_value_type_nid_postfix(value) == SQLITE_FLOAT, "value type"); + Require(sqlite3_value_double_nid_postfix(value) == 2.5, "value double"); + Require(std::memcmp(sqlite3_value_text16_nid_postfix(value), u"2.5", 8) == 0, "value text16"); + Require(sqlite3_step_nid_postfix(select) == SQLITE_ROW, "second row"); + Require(sqlite3_column_int_nid_postfix(select, 0) == -7, "negative int"); + Require(sqlite3_column_type_nid_postfix(select, 1) == SQLITE_NULL, "null type"); + Require(std::memcmp(sqlite3_column_text16_nid_postfix(select, 2), u"z", 4) == 0, "transient text"); + Require(std::memcmp(sqlite3_column_blob_nid_postfix(select, 3), fixed, 3) == 0, "static blob"); + Require(sqlite3_step_nid_postfix(select) == SQLITE_DONE, "end of rows"); + Require(sqlite3_finalize_nid_postfix(select) == SQLITE_OK, "finalize select"); +} + +void CheckFunctions(sqlite3* db) { + static int application = 0x42; + Require(sqlite3_create_function_nid_postfix(db, "probe", 1, SQLITE_UTF8, &application, Probe, nullptr, + nullptr) == SQLITE_OK, "create function"); + Execute(db, "SELECT probe(4.25)"); + Require(functionCalls == 1 && lastFunctionValue == 4.25, "function value"); + Require(lastFunctionApplication == &application, "function application"); + sqlite3_stmt* rejected = Prepare(db, "SELECT probe('x')"); + Require(sqlite3_step_nid_postfix(rejected) == SQLITE_ERROR, "function error"); + Require(std::strcmp(sqlite3_errmsg_nid_postfix(db), "probe rejects text") == 0, "function message"); + Require(sqlite3_errcode_nid_postfix(db) == SQLITE_ERROR, "function error code"); + Require(sqlite3_finalize_nid_postfix(rejected) == SQLITE_ERROR, "finalize after error"); + + Require(sqlite3_create_function_nid_postfix(db, "counted", 1, SQLITE_UTF8, &stepCalls, nullptr, CountStep, + CountFinal) == SQLITE_OK, "create aggregate"); + Execute(db, "SELECT counted(a) FROM t"); + Require(stepCalls == 2 && finalCalls == 1, "aggregate calls"); + Require(sqlite3_create_function_nid_postfix(db, "counted", 1, SQLITE_UTF8, nullptr, nullptr, nullptr, + nullptr) == SQLITE_OK, "delete aggregate"); + sqlite3_stmt* missing = nullptr; + Require(sqlite3_prepare_v2_nid_postfix(db, "SELECT counted(a) FROM t", -1, &missing, nullptr) == SQLITE_ERROR, + "deleted aggregate"); + Require(missing == nullptr, "no statement for deleted aggregate"); + + interruptTarget = db; + Require(sqlite3_create_function_nid_postfix(db, "stop", 0, SQLITE_UTF8, nullptr, Interrupt, nullptr, + nullptr) == SQLITE_OK, "create interrupt"); + sqlite3_stmt* endless = Prepare(db, + "WITH RECURSIVE n(i) AS (SELECT 1 UNION ALL SELECT i + 1 FROM n) SELECT stop() FROM n"); + int result = SQLITE_ROW; + while (result == SQLITE_ROW) { + result = sqlite3_step_nid_postfix(endless); + } + Require(result == SQLITE_INTERRUPT, "interrupt"); + sqlite3_finalize_nid_postfix(endless); +} + +void CheckCollation(sqlite3* db) { + static int argument = 0x51; + Require(sqlite3_create_collation_v2_nid_postfix(db, "reverse", SQLITE_UTF8, &argument, ReverseCompare, + DestroyCollation) == SQLITE_OK, "create collation"); + Execute(db, "CREATE TABLE words(w TEXT)"); + Execute(db, "INSERT INTO words VALUES('alpha'), ('gamma'), ('beta')"); + sqlite3_stmt* ordered = Prepare(db, "SELECT w FROM words ORDER BY w COLLATE reverse"); + const char16_t* expected[] = {u"gamma", u"beta", u"alpha"}; + for (const char16_t* word : expected) { + Require(sqlite3_step_nid_postfix(ordered) == SQLITE_ROW, "collation row"); + const auto* text = static_cast(sqlite3_column_text16_nid_postfix(ordered, 0)); + Require(std::u16string(text) == word, "collation order"); + } + Require(sqlite3_finalize_nid_postfix(ordered) == SQLITE_OK, "finalize collation"); + Require(sqlite3_create_collation_v2_nid_postfix(db, "reverse", SQLITE_UTF16LE, &argument, ReverseCompare, + DestroyCollation) == SQLITE_OK, "second encoding"); + Require(destroyedCollations == 0, "collations live until close"); +} + +void CheckAuthorizer(sqlite3* db) { + static int argument = 0xA7; + Require(sqlite3_set_authorizer_nid_postfix(db, DenyDelete, &argument) == SQLITE_OK, "set authorizer"); + sqlite3_stmt* denied = nullptr; + Require(sqlite3_prepare_v2_nid_postfix(db, "DELETE FROM words", -1, &denied, nullptr) == SQLITE_AUTH, + "authorizer denies"); + Require(denied == nullptr, "no denied statement"); + Require(std::strcmp(sqlite3_errstr_nid_postfix(SQLITE_AUTH), "authorization denied") == 0, "errstr"); + Require(sqlite3_set_authorizer_nid_postfix(db, nullptr, nullptr) == SQLITE_OK, "clear authorizer"); + Execute(db, "DELETE FROM words WHERE w = 'beta'"); +} + +void CheckExtendedCodes(sqlite3* db) { + Execute(db, "CREATE TABLE unique_values(v INTEGER UNIQUE)"); + Execute(db, "INSERT INTO unique_values VALUES(1)"); + Require(sqlite3_extended_result_codes_nid_postfix(db, 1) == SQLITE_OK, "extended codes"); + sqlite3_stmt* duplicate = Prepare(db, "INSERT INTO unique_values VALUES(1)"); + Require(sqlite3_step_nid_postfix(duplicate) == SQLITE_CONSTRAINT_UNIQUE, "extended step code"); + Require(sqlite3_errcode_nid_postfix(db) == SQLITE_CONSTRAINT_UNIQUE, "extended errcode"); + sqlite3_finalize_nid_postfix(duplicate); +} + +void CheckFileDatabase(const std::filesystem::path& directory) { + const std::string name = "/wal.db"; + sqlite3* writer = Open(name); + Execute(writer, "PRAGMA journal_mode = WAL"); + Execute(writer, "CREATE TABLE log(v INTEGER)"); + static int walArgument = 0xC3; + void* previous = sqlite3_wal_hook_nid_postfix(writer, RecordWal, &walArgument); + Require(reinterpret_cast(previous) == 1000, "default autocheckpoint hook"); + Execute(writer, "INSERT INTO log VALUES(1)"); + Require(walCalls == 1 && walSchema == "main" && walFrames > 0, "wal hook"); + Require(sqlite3_wal_hook_nid_postfix(writer, RecordWal, &walArgument) == &walArgument, "previous wal argument"); + int logFrames = -1; + int checkpointed = -1; + Require(sqlite3_wal_checkpoint_v2_nid_postfix(writer, "main", SQLITE_CHECKPOINT_PASSIVE, &logFrames, + &checkpointed) == SQLITE_OK, "checkpoint"); + Require(logFrames >= 1 && checkpointed == logFrames, "checkpoint frames"); + + sqlite3* reader = Open(name); + static int busyArgument = 0xB5; + Require(sqlite3_busy_handler_nid_postfix(reader, GiveUp, &busyArgument) == SQLITE_OK, "busy handler"); + Execute(writer, "BEGIN EXCLUSIVE"); + Require(sqlite3_get_autocommit_nid_postfix(writer) == 0, "transaction open"); + sqlite3_stmt* blocked = Prepare(reader, "INSERT INTO log VALUES(2)"); + Require(sqlite3_step_nid_postfix(blocked) == SQLITE_BUSY, "busy"); + Require(busyCalls == 1, "busy handler called"); + sqlite3_finalize_nid_postfix(blocked); + Require(sqlite3_busy_timeout_nid_postfix(reader, 10) == SQLITE_OK, "busy timeout"); + Execute(writer, "COMMIT"); + Execute(reader, "INSERT INTO log VALUES(2)"); + Require(busyCalls == 1, "timeout replaces the guest handler"); + Require(sqlite3_close_nid_postfix(reader) == SQLITE_OK, "close reader"); + Require(sqlite3_close_nid_postfix(writer) == SQLITE_OK, "close writer"); +} + +} + +int main() { + Require(sqlite3_initialize_nid_postfix() == SQLITE_OK, "initialize"); + sqlite3* db = Open(":memory:"); + CheckStatements(db); + CheckFunctions(db); + CheckCollation(db); + CheckAuthorizer(db); + CheckExtendedCodes(db); + Require(sqlite3_close_nid_postfix(db) == SQLITE_OK, "close"); + Require(destroyedCollations == 2, "collation destructors on close"); + + std::random_device random; + const auto directory = std::filesystem::temp_directory_path() / ("nk_sqlite_" + std::to_string(random())); + std::filesystem::create_directories(directory); + std::filesystem::current_path(directory); + CheckFileDatabase(directory); + Require(std::filesystem::is_regular_file(directory / "wal.db"), "guest path maps into the root"); + std::filesystem::current_path(directory.parent_path()); + std::filesystem::remove_all(directory); + return 0; +} diff --git a/docs/dev/debt/libSceNKWebKitRequirements.md b/docs/dev/debt/libSceNKWebKitRequirements.md index 179b64d8a4..3135dd9d2f 100644 --- a/docs/dev/debt/libSceNKWebKitRequirements.md +++ b/docs/dev/debt/libSceNKWebKitRequirements.md @@ -2,8 +2,15 @@ ### Known export names; ABI prototypes unverified -- 133 pending exports in [Export.cpp](../../../core/libs/prx/libSceNKWebKitRequirements/Export.cpp) are present in the [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs) inventory. Their prototypes and behavior are unverified. They use the no-parameter import-resolution convention and throw through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. -- The 122 of them declared in [Identified.hpp](../../../core/libs/prx/libSceNKWebKitRequirements/include/Identified.hpp) carry the zlib, libjpeg, libpsl, SQLite, libxml2 and UndefinedBehaviorSanitizer names whose `tools/nid_names.py` hash equals the inventory NID; the names were taken from the identifiers of the zlib 1.3.1, libjpeg-turbo 3.0.4, libpsl 0.21.5, SQLite 3.46.1 and libxml2 2.13.4 sources and from the two `__ubsan_handle_cfi_check_fail` handlers libkernel_sys exports. A name match does not show that the console function has that library's prototype or behavior. The other 11 hash to no identifier of those sources or of ICU 76.1, HarfBuzz 10.0.1, cairo 1.18.2, pixman 0.42.2, FreeType 2.13.3, libpng 1.6.44, Expat 2.6.3, libxslt 1.1.42, libwebp 1.4.0, Brotli 1.1.0, WOFF2 1.0.2, curl 8.10.1, Little CMS 2.16, OpenJPEG 2.5.2 and Fontconfig 2.15.0. +- 83 pending exports in [Export.cpp](../../../core/libs/prx/libSceNKWebKitRequirements/Export.cpp) are present in the [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs) inventory. Their prototypes and behavior are unverified. They use the no-parameter import-resolution convention and throw through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. +- The 72 of them declared in [Identified.hpp](../../../core/libs/prx/libSceNKWebKitRequirements/include/Identified.hpp) carry the zlib, libjpeg, libpsl, libxml2 and UndefinedBehaviorSanitizer names whose `tools/nid_names.py` hash equals the inventory NID; the names were taken from the identifiers of the zlib 1.3.1, libjpeg-turbo 3.0.4, libpsl 0.21.5, SQLite 3.46.1 and libxml2 2.13.4 sources and from the two `__ubsan_handle_cfi_check_fail` handlers libkernel_sys exports. A name match does not show that the console function has that library's prototype or behavior. The other 11 hash to no identifier of those sources or of ICU 76.1, HarfBuzz 10.0.1, cairo 1.18.2, pixman 0.42.2, FreeType 2.13.3, libpng 1.6.44, Expat 2.6.3, libxslt 1.1.42, libwebp 1.4.0, Brotli 1.1.0, WOFF2 1.0.2, curl 8.10.1, Little CMS 2.16, OpenJPEG 2.5.2 and Fontconfig 2.15.0. + +### SQLite adapters + +- The 50 `sqlite3_*` exports in [Sqlite.cpp](../../../core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp) assume the public SQLite C prototypes because their names hash to the inventory NIDs; the console prototypes are not confirmed. They call SQLite 3.54.1 built from `3rdparty/sqlite` with `SQLITE_THREADSAFE=1` and `SQLITE_OMIT_LOAD_EXTENSION`. The console library's version and compile options are unknown. +- [`sqlite3_open_v2`](../../../core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp) ignores the guest VFS name and uses the host default VFS. It maps file names through the guest path resolver, but passes `:memory:`, empty names, `SQLITE_OPEN_MEMORY` and `file:` URIs opened with `SQLITE_OPEN_URI` unchanged, so a URI path is not mapped. A database that exists only inside `app0.zar` is refused. +- [`sqlite3_bind_blob`](../../../core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp) and `sqlite3_bind_text16` copy data given with a guest destructor and call that destructor before returning, instead of when SQLite releases the value. +- [`sqlite3_wal_hook`](../../../core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp) returns the host's previous argument, such as the default autocheckpoint frame count, until a guest hook is installed. ### Additional callable exports From 6efb2079bcb35e34da28a05f3c62ad976c2def83 Mon Sep 17 00:00:00 2001 From: Noxy-HNR Date: Mon, 2 Nov 2026 17:33:04 +0000 Subject: [PATCH 285/612] fix(libSceNKWebKitRequirements): include the SQLite declarations in the library header --- .../libSceNKWebKitRequirements/libSceNKWebKitRequirements.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/core/libs/prx/libSceNKWebKitRequirements/libSceNKWebKitRequirements.hpp b/core/libs/prx/libSceNKWebKitRequirements/libSceNKWebKitRequirements.hpp index f487bd43a8..91dd8b8a5f 100644 --- a/core/libs/prx/libSceNKWebKitRequirements/libSceNKWebKitRequirements.hpp +++ b/core/libs/prx/libSceNKWebKitRequirements/libSceNKWebKitRequirements.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceNKWebKitRequirements/include/InventoryExports.hpp" #include "prx/libSceNKWebKitRequirements/include/Identified.hpp" +#include "prx/libSceNKWebKitRequirements/include/Sqlite.hpp" From 4816b33918cad6ec1bd9b963aecea143f27fac49 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Thu, 29 Oct 2026 09:13:48 -0400 Subject: [PATCH 286/612] fix(libc): print %C, %S and a zero %lc in the printf families as titles' libc.prx does --- .../prx/libc/include/WindowsFormatting.hpp | 13 ++++---- core/libs/prx/libc/src/FormattingWide.cpp | 12 +++---- core/libs/tests/Formatting.cpp | 33 ++++++++++++++++--- core/libs/tests/GuestWideFormatting.cpp | 3 ++ docs/dev/debt/libc.md | 4 +-- 5 files changed, 46 insertions(+), 19 deletions(-) diff --git a/core/libs/prx/libc/include/WindowsFormatting.hpp b/core/libs/prx/libc/include/WindowsFormatting.hpp index 6e59910a44..849b3faddd 100644 --- a/core/libs/prx/libc/include/WindowsFormatting.hpp +++ b/core/libs/prx/libc/include/WindowsFormatting.hpp @@ -182,7 +182,7 @@ inline std::string ApplicableLength(const std::string& length, char conversion) if (std::strchr("diouxXn", conversion)) return length == "L" ? std::string() : length; if (std::strchr("aAeEfFgG", conversion)) return length == "L" ? length : std::string(); if (conversion == 'c' || conversion == 's') return length == "l" ? length : std::string(); - if (conversion == 'p') return std::string(); + if (conversion == 'p' || conversion == 'S') return std::string(); return length; } @@ -510,7 +510,7 @@ inline std::string FormatPointer(const std::string& spec, const void* pointer) { return std::string(padding, ' ') + digits; } -inline constexpr char KnownConversions[] = "diouxXeEfFgGaAcCsSpn"; +inline constexpr char KnownConversions[] = "diouxXeEfFgGaAcsSpn"; constexpr size_t maximumArgumentPosition = 65535; inline size_t ArgumentPosition(const char*& format) { @@ -526,7 +526,7 @@ inline size_t ArgumentPosition(const char*& format) { return position; } -inline std::vector NumberedArgumentClasses(const char* format) { +inline std::vector NumberedArgumentClasses(const char* format, bool wide = false) { if (!std::strchr(format, '$')) return {}; std::vector classes; bool numbered = false; @@ -557,7 +557,7 @@ inline std::vector NumberedArgumentClasses(const char* format) { if (!conversion) break; ++format; if (std::strchr("aAeEfFgG", conversion)) take(next++, longDouble ? ArgumentClass::LongDouble : ArgumentClass::Double); - else if (std::strchr("diouxXcCsSpn", conversion)) take(next++, ArgumentClass::Integer); + else if (std::strchr(wide ? "diouxXcspn" : "diouxXcsSpn", conversion)) take(next++, ArgumentClass::Integer); } if (!numbered) classes.clear(); return classes; @@ -685,11 +685,10 @@ inline int FormatWindows(char* buffer, size_t size, const char* format, const vo } } else if (conversion == 'c' && (length.empty() || length == "h")) { output.Value(spec + conversion, args.Next()); - } else if ((conversion == 'c' && length == "l") || (conversion == 'C' && length.empty())) { + } else if (conversion == 'c' && length == "l") { std::string utf8; AppendUtf8(utf8, static_cast(args.Next())); - if (utf8[0] == 0) output.Value(spec + 'c', 0); - else output.Value(spec + 's', utf8.c_str()); + if (utf8[0] != 0) output.Value(spec.substr(0, spec.find('.')) + 's', utf8.c_str()); } else if (conversion == 's' && (length.empty() || length == "h")) { const char* value = args.Next(); if (!value && secure) throw FormatConstraintViolation("Null string argument"); diff --git a/core/libs/prx/libc/src/FormattingWide.cpp b/core/libs/prx/libc/src/FormattingWide.cpp index e841b32026..d63f13fc35 100644 --- a/core/libs/prx/libc/src/FormattingWide.cpp +++ b/core/libs/prx/libc/src/FormattingWide.cpp @@ -120,7 +120,7 @@ std::u16string FormatWide(const char16_t* format, VaList* source, bool secure = const char16_t* const start = format; std::string ascii; for (const char16_t* unit = format; *unit != 0; ++unit) ascii.push_back(*unit < 0x80 ? static_cast(*unit) : '?'); - args.Number(LibcDetail::NumberedArgumentClasses(ascii.c_str())); + args.Number(LibcDetail::NumberedArgumentClasses(ascii.c_str(), true)); const auto position = [&] { const char* cursor = ascii.c_str() + (format - start); const std::size_t found = LibcDetail::ArgumentPosition(cursor); @@ -228,16 +228,16 @@ std::u16string FormatWide(const char16_t* format, VaList* source, bool secure = } else if (conversion == u'p' && (length.empty() || length == "l")) { const std::string text = LibcDetail::FormatPointer(spec, args.Next()); out.append(text.begin(), text.end()); - } else if (conversion == u'c' || conversion == u'C') { + } else if (conversion == u'c') { const int value = args.Next(); std::u16string text; - if (conversion == u'C' || length == "l") text.push_back(static_cast(value)); + if (length == "l") text.push_back(static_cast(value)); else text.push_back(static_cast(static_cast(value))); AppendPadded(out, text, left, width); - } else if (conversion == u's' || conversion == u'S') { + } else if (conversion == u's') { std::u16string text; const std::size_t limit = precision < 0 ? SIZE_MAX : static_cast(precision); - if (conversion == u'S' || length == "l") { + if (length == "l") { const char16_t* value = args.Next(); if (value == nullptr && secure) throw std::invalid_argument("Null string argument"); if (value == nullptr) value = u"(null)"; @@ -257,7 +257,7 @@ std::u16string FormatWide(const char16_t* format, VaList* source, bool secure = else if (length == "h") *static_cast(pointer) = static_cast(count); else if (length.empty()) *static_cast(pointer) = count; else *static_cast(pointer) = count; - } else if (conversion < 0x80 && !std::strchr(LibcDetail::KnownConversions, static_cast(conversion))) { + } else if (conversion == u'S' || (conversion < 0x80 && !std::strchr(LibcDetail::KnownConversions, static_cast(conversion)))) { std::u16string text = u"%"; if (conversion != u'%') text.push_back(conversion); AppendPadded(out, text, left, width); diff --git a/core/libs/tests/Formatting.cpp b/core/libs/tests/Formatting.cpp index 7884c352b3..0efa519916 100644 --- a/core/libs/tests/Formatting.cpp +++ b/core/libs/tests/Formatting.cpp @@ -114,6 +114,21 @@ static int APS5_VABI PrintList(const char* format, ...) { return result; } +static void CheckPositionalUnknownConversion() { + char output[32]; + for (const std::uint32_t gpOffset : {0u, 48u}) { + VaList args = {gpOffset, 176u, nullptr, nullptr}; + const VaList original = args; + for (const char* format : {"%1$C", "%2$C", "%1$hC"}) { + Require(vsnprintf_nid_postfix(output, sizeof(output), format, &args) == 2 && std::strcmp(output, "%C") == 0); + Require(std::memcmp(&args, &original, sizeof(args)) == 0); + } + Require(vsnprintf_nid_postfix(output, sizeof(output), "[%1$5C]", &args) == 7 && std::strcmp(output, "[ %C]") == 0); + Require(std::memcmp(&args, &original, sizeof(args)) == 0); + } + Require(FormatList(output, sizeof(output), "%1$C%d", 7) == 3 && std::strcmp(output, "%C7") == 0); +} + static void CheckIgnoredLengths() { CheckCharacterScanLengths(); char output[96]; @@ -183,10 +198,20 @@ static bool CheckWidePrecision() { Require(snprintf_nid_postfix(output, sizeof(output), "%.2ls", bounded) == 2 && std::strcmp(output, "AB") == 0); Require(snprintf_nid_postfix(nullptr, 0, "%.1ls", u"\u00e9") == 0); Require(snprintf_nid_postfix(output, sizeof(output), "%.1s", "\xc3\xa9") == 1 && static_cast(output[0]) == 0xc3 && output[1] == 0); - Require(FormatList(output, sizeof(output), "%S:%lc:%C:%d", u"\u00e9", 0x20ac, 0x41, 7) == 10 && std::strcmp(output, "\xc3\xa9:\xe2\x82\xac:A:7") == 0); + Require(FormatList(output, sizeof(output), "%S:%lc:%C:%d", u"\u00e9", 0x20ac, 0x41, 7) == 12 && std::strcmp(output, "\xc3\xa9:\xe2\x82\xac:%C:65") == 0); Require(snprintf_nid_postfix(output, sizeof(output), "%.*S", 3, u"\u00e9\u20ac") == 2 && std::strcmp(output, "\xc3\xa9") == 0); Require(snprintf_nid_postfix(output, sizeof(output), "[%ls]", static_cast(nullptr)) == 8 && std::strcmp(output, "[(null)]") == 0); - Require(snprintf_nid_postfix(output, sizeof(output), "a%lcb%C", 0, 0) == 4 && std::memcmp(output, "a\0b\0", 5) == 0); + Require(snprintf_nid_postfix(output, sizeof(output), "a%lcb%C", 0, 0) == 4 && std::strcmp(output, "ab%C") == 0); + Require(snprintf_nid_postfix(output, sizeof(output), "%3lc|%-3lc|", 0, 0) == 2 && std::strcmp(output, "||") == 0); + for (const char* format : {"%lc", "%.0lc", "%.1lc"}) + Require(snprintf_nid_postfix(output, sizeof(output), format, 0xe9) == 2 && std::strcmp(output, "\xc3\xa9") == 0); + Require(snprintf_nid_postfix(output, sizeof(output), "%5lc|%-5lc|%5.1lc", 0xe9, 0xe9, 0xe9) == 17 + && std::strcmp(output, " \xc3\xa9|\xc3\xa9 | \xc3\xa9") == 0); + Require(FormatList(output, sizeof(output), "%*.*lc", 5, 1, 0xe9) == 5 && std::strcmp(output, " \xc3\xa9") == 0); + Require(snprintf_nid_postfix(output, sizeof(output), "%3.0lc|%-3.1lc", 0, 0) == 1 && std::strcmp(output, "|") == 0); + Require(snprintf_nid_postfix(output, sizeof(output), "%5C|%-3C|%hC%lC%#C", 0xe9) == 16 && std::strcmp(output, " %C|%C |%C%C%C") == 0); + Require(snprintf_nid_postfix(output, sizeof(output), "%C%d", 65) == 4 && std::strcmp(output, "%C65") == 0); + Require(snprintf_nid_postfix(output, sizeof(output), "%hS|%lS|%5S", u"ab", u"c\u00e9", u"d") == 12 && std::strcmp(output, "ab|c\xc3\xa9| d") == 0); Require(FormatList(output, sizeof(output), "%%ls:%2$d:%1$d", 3, 7) == 7 && std::strcmp(output, "%ls:7:3") == 0); Require(FormatList(output, sizeof(output), "%ls[%f][%+f][% F][%5.1e]", u"", -NAN, -NAN, NAN, -NAN) == 25 && std::strcmp(output, "[-nan][-nan][ NAN][ -nan]") == 0); @@ -356,7 +381,7 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { Require(std::strcmp(buffer, "[ 3.50][3.5 ][ z]") == 0); Require(sprintf_nid_postfix(buffer, "[%2$*1$d][%2$.*1$d]", -4, 5) == 9 && std::strcmp(buffer, "[5 ][5]") == 0); Require(sprintf_nid_postfix(buffer, "[%1$*2$d]", 5, 3) == 5 && std::strcmp(buffer, "[ 5]") == 0); - Require(FormatList(buffer, sizeof(buffer), "%2$ls|%1$C|%3$d", 0x41, u"\u00e9", 7) == 6 && std::strcmp(buffer, "\xc3\xa9|A|7") == 0); + Require(FormatList(buffer, sizeof(buffer), "%2$ls|%1$lc|%3$d", 0x41, u"\u00e9", 7) == 6 && std::strcmp(buffer, "\xc3\xa9|A|7") == 0); Require(FormatList(buffer, sizeof(buffer), "%2$d %d %ls|%*5$d|%1$d", 1, 2, 3, u"x", 4) == 12 && std::strcmp(buffer, "2 3 x| 4|1") == 0); Require(FormatList(buffer, sizeof(buffer), "[%*3$d]%ls", 5, u"", 3) == 5 && std::strcmp(buffer, "[ 5]") == 0); Require(FormatList(buffer, sizeof(buffer), "%100$d %2$d%1$ls", u"", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100) == 4 && std::strcmp(buffer, "99 1") == 0); @@ -719,4 +744,4 @@ static void CheckLongDoubleHexFloat() { } } -int main() { CheckSizeLimits(); CheckIgnoredLengths(); RunChecks(); CheckHexFloat(); CheckDecimalFloat(); CheckLongDoubleHexFloat(); CheckAllocatingFormat(); return CheckWidePrecision() ? 0 : 1; } +int main() { CheckPositionalUnknownConversion(); CheckSizeLimits(); CheckIgnoredLengths(); RunChecks(); CheckHexFloat(); CheckDecimalFloat(); CheckLongDoubleHexFloat(); CheckAllocatingFormat(); return CheckWidePrecision() ? 0 : 1; } diff --git a/core/libs/tests/GuestWideFormatting.cpp b/core/libs/tests/GuestWideFormatting.cpp index fd56827605..5c3ad1930b 100644 --- a/core/libs/tests/GuestWideFormatting.cpp +++ b/core/libs/tests/GuestWideFormatting.cpp @@ -247,6 +247,9 @@ static void CheckSwprintf() { Require(swprintf_nid_postfix(wide, 16, u"%5ls|%-3c|%lc", u"wide", 'z', 0x20ac) == 11, "swprintf padded length"); Require(wide == std::u16string(u" wide|z |€"), "swprintf padded output"); Require(swprintf_nid_postfix(wide, 16, u"[%q][%5q]") == 11 && wide == std::u16string(u"[%q][ %q]"), "swprintf prints an unknown conversion"); + char16_t letters[32]; + Require(swprintf_nid_postfix(letters, 32, u"%C|%5C|%hC|%S|%-3S|%lS%d", 7) == 22 && letters == std::u16string(u"%C| %C|%C|%S|%S |%S7"), "swprintf prints %C and %S as unknown conversions"); + Require(swprintf_nid_postfix(letters, 32, u"%2$d %S %f", 1, 2, 3.5) == 13 && letters == std::u16string(u"2 %S 3.500000"), "positional %S takes no argument"); for (const char16_t* format : {u"%99999999999d", u"%.99999999999d", u"%4294967296s", u"%.4294967296s", u"%2147483648ls", u"%.2147483648ls"}) { wide[0] = u'x'; Require(swprintf_nid_postfix(wide, 16, format, 7, u"a") < 0 && wide[0] == 0, "swprintf width or precision beyond int"); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 1a69b81954..96d36cc34a 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -215,7 +215,7 @@ - [wctomb_s, wcrtomb_s, mbstowcs_s and mbsrtowcs_s](../../../core/libs/prx/libc/src/BoundsChecking.cpp) (libc) follow the console's `libc.prx`, but convert in the single-byte C locale of `wctomb`, `wcrtomb` and `mbrtowc` (`MB_CUR_MAX` 1): a character above 0xFF is `EILSEQ` in `wctomb_s` and `wcrtomb_s`, and each byte is one wide character in `mbstowcs_s` and `mbsrtowcs_s`. The locale of a title's `libc.prx` was not measured. The `wcrtomb_s` path where a character does not fit `smax` and the `EILSEQ` paths of `mbstowcs_s` and `mbsrtowcs_s` are not reached - [wcsrtombs_s](../../../core/libs/prx/libc/src/Multibyte.cpp) (libc) follows the console's `libc.prx`, but a runtime-constraint violation only returns its error: the default handler there writes the message and a newline to `stderr` before returning, which is not done here. Conversion uses the C locale of `wcrtomb` (`MB_CUR_MAX` 1), so the path where a multibyte character does not fit the remaining space is not reached - [c16rtomb, c32rtomb, mbrtoc16 and mbrtoc32](../../../core/libs/prx/libc/src/Multibyte.cpp) (libSceLibcInternal, implemented in libc) - a null state, which the console dereferences, throws. Otherwise they follow the console's `libSceLibcInternal.sprx` (0x7D480 to 0x7D920), read from disassembly: UTF-8 whatever the locale, up to six bytes per sequence, with no surrogate or 0x10FFFF check when encoding. The state holds the value in its first eight bytes and the expected byte count (0x10 for a pending low surrogate, 1 for a stored high one) as 16 bits at 0xA. `mbrtoc16` builds a low surrogate from the value the state last held, `c16rtomb` returns -1 without `errno` for an unpaired high surrogate, and `EILSEQ` is 86. On a PS5 (13.60) the system library's default locale converts UTF-8 in `wcrtomb`, `wctomb`, `mbrtowc` and `mblen`; a title's bundled `libc.prx` does too until `setlocale` installs a table ([disassembly](https://github.com/boykopovar/AnyPS5/pull/8855#issuecomment-6335267758)) -- [The narrow printf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) follows a payload process on a PS5 (firmware 13.60): each 16-bit unit of a `%ls` or `%lc` argument is encoded alone, so a surrogate pair gives two 3-byte sequences; a NaN keeps its sign and the `+` and space flags, and `0` pads it with spaces; argument positions above 99 work (up to 400 measured; above 65535 throw here); an unknown conversion such as `%m` or `%k` prints `%` and its letter, padded to the width, without taking an argument. The guest's `wcrtomb` and locales are not used. On Linux the formats without those conversions or a floating-point conversion go to glibc, which prints NaN the same way. The `_s` forms throw for an unknown conversion, `%m` included, which was not measured +- [The narrow printf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) follows a payload process on a PS5 (firmware 13.60): each 16-bit unit of a `%ls` or `%lc` argument is encoded alone, so a surrogate pair gives two 3-byte sequences; a NaN keeps its sign and the `+` and space flags, and `0` pads it with spaces; argument positions above 99 work (up to 400 measured; above 65535 throw here); an unknown conversion such as `%m` or `%k` prints `%` and its letter, padded to the width, without taking an argument, and `%C` is one in the `libc.prx` of PPSA07923 and PPSA05916, where a `%lc` of 0 prints nothing whatever the width; a positional `%C`, and `%S` in `swprintf`, take no argument here, which was not measured. The guest's `wcrtomb` and locales are not used. On Linux the formats without those conversions or a floating-point conversion go to glibc, which prints NaN the same way. The `_s` forms print an unknown conversion the same way, as the conversions entry below describes - [asinh](../../../core/libs/prx/libc/src/Math.cpp) (libc) is FreeBSD msun's [`s_asinh.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_asinh.c): below 2^-28 and up to |x| = 2 it runs over this file's msun `log1p` and gives msun's bits (0 of 2,000,000 random arguments in each of those ranges differ from a locally built msun on a Windows host). Above 2 it calls this file's `log`, which is still the host's, so those results can differ from msun in the last bit until `log` follows msun: 38,719 of 2,000,000 random arguments in (2, 2^28] differ with the Windows UCRT, none above 2^28 there (not measured with glibc). It sets no `errno`, as msun's does not, and was compared with msun built on the host, not with a console: the titles' `libc.prx` and the system `libSceLibcInternal` are unmeasured. - [%a and %A](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) format a finite double as the `libc.prx` that titles ship does, instead of through the host: a subnormal is normalised (`0x1p-1074`), a shorter precision rounds half up on the dropped hexadecimal digits and a carry stays in the leading digit (`%.3a` of `0x0.fffffffffffffp-1022` is `0x2.000p-1023`), and `-` overrides `0`. Checked by running the copies of PPSA13934, PPSA07923 and PPSA05916 side by side on 7 million random formats, not measured on a console. A `long double` is normalised the same way, with 16 hexadecimal digits (`0x1.999999999999999ap-4` for 0.1L). Infinities still go to the host; NaN keeps its sign and flags through the formatter on current main. - [%e, %f and %g](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) format a finite `double` or `long double` as the `libc.prx` that titles ship does, instead of through the host. It keeps the exact decimal digits down to position `4 * floor(k / 4) - 20`, where `k` is the `frexp` exponent times 30103 / 100000 truncated, and prints zeros after them; a shorter precision rounds half up on the kept digits, except that an exact tie rounds down when `k` is at least 8 (`%.0f` of 67108865.5 is `67108865`). The console computes the kept digits in extended precision, so about 7% of outputs longer than 17 significant digits end one unit lower than here (`%f` of 1e22 is `9999999999999999999999.000000`). Checked by running the copies of PPSA07923, PPSA05916 and PPSA13934 side by side, not measured on a console. The wide printf family uses the same digits, as the titles' `swprintf` gives the same text as their `snprintf`. @@ -270,8 +270,8 @@ - [collate vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `ruZtIwbCFjk`) is nine `uintptr_t` slots matching `st_size` 72 on the title `libc.prx` from PPSA16833 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and three `do_*` virtuals). Slots 2 through 8 hold the same `DestroyCollate`, `DeleteCollate`, `RetainFacet`, `ReleaseFacet`, `CollateCompare`, `CollateTransform` and `CollateHash` functions that `g_collateVtable` uses, so facet calls through the exported object work; offset-to-top and typeinfo remain zero, unlike the console's relocated entries, and `_Getcat` still installs `g_collateVtable` for facets it builds. - [num_get::id, time_get::id and the num_get vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) are data objects, as the public SDK's libSceLibcInternal table declares them and as the other facet ids and the `num_put` and `codecvt` vtables are kept: the ids start at zero and the vtable is 17 zeroed slots, the 136-byte size of `KfcTPbeaOqg` in the console's libSceLibcInternal, whose relocations fill the offset-to-top and typeinfo entries, the two destructors, `locale::facet::_Incref` and `_Decref`, and the eleven `do_get` of Dinkumware's crt.vc12 `xlocnum`, so a guest resolves the symbols and reads data, and a facet call through the vtable faults. libSceAmpr exported the three as throwing functions, which a data consumer reads as code; its qualified imports fall back to libc. - [printf family conversions](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) - Windows `FormatWindows` follows the title `libc.prx` `_Printf` measured from PPSA13934, PPSA07923, PPSA05916 and PPSA04158: `%q`, `%D`, `%O` and `%U` are unknown conversions; an unknown character is printed with its leading `%`, and width, `-` and discarded length modifiers behave as measured (`%5y` is ` %y`, `%hhz` is `%z`). `%%` takes a width. A trailing `%` reads past the format's NUL and is not reproduced; it throws. `#`, precision and a `0` without a width are ignored, as a payload process on a PS5 (firmware 13.60) printed `%#k` and `%.2k` as `%k`; `+`, space and `0` are ignored on an unknown conversion and a width pads it with spaces, in the `_s` forms too, and `'` is itself an unknown conversion (`%'d` prints `%'d`), as the `libc.prx` of PPSA07923 and PPSA13934 does. On Linux a format with an unknown conversion also goes to this formatter +- [printf length modifiers](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) - the `_s` forms (`snprintf_s`, `vswprintf_s` and the others) still throw for a length modifier that does not apply to its conversion, unmeasured on the console. The plain forms ignore it as the `libc.prx` of PPSA07923 and PPSA13934 does: `L` on an integer conversion or `%n`, any length but `l` on `%c` and `%s`, any but `L` on a floating conversion, and any on `%p` and `%S`. - [_Cnd_register_at_thread_exit, _Cnd_unregister_at_thread_exit and _Cnd_do_broadcast_at_thread_exit](../../../core/libs/prx/libc/src/Threads.cpp) (libc) keep registrations in a process-wide list keyed by the host thread id, as Microsoft's [xnotify.cpp](https://github.com/microsoft/STL/blob/main/stl/src/xnotify.cpp) does: registering validates both handles, unregistering drops every slot that holds the mutex, and the broadcast of the current thread sets the supplied flag to 1, broadcasts the condition and unlocks the mutex, in that order. The exit path splices nodes, does not allocate and is `noexcept`. The handles are passed by value, following the modern UCRT signature; the VC12 form and the console signature are not verified. The broadcast runs when a `std::thread` exits through `_Pad::_Launch` and when a title calls `_Thrd_exit`; a libkernel `scePthreadCreate` thread that returns without `_Thrd_exit` never broadcasts and keeps its registration. -- [printf length modifiers](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) - the `_s` forms (`snprintf_s`, `vswprintf_s` and the others) still throw for a length modifier that does not apply to its conversion, unmeasured on the console. The plain forms ignore it as the `libc.prx` of PPSA07923 and PPSA13934 does: `L` on an integer conversion or `%n`, any length but `l` on `%c` and `%s`, any but `L` on a floating conversion, and any on `%p`. - [child process registry](../../../core/libs/prx/libc/src/Process.cpp) (libc) - `RegisterChildProcess_nid_no_patch` and `WaitChildProcess_nid_no_patch` track the native handles of children created by `posix_spawn` ([libSceLibcInternal](../../../core/libs/prx/libSceLibcInternal/Spawn.cpp)) so libkernel can reap them on Windows, where the host has no `waitpid`. The registry lives in libc because libSceLibcInternal imports libc but not libkernel. `RegisterChildProcess_nid_no_patch` stores the process ID and handle; only reaped handles are closed, and a process that exits without waiting leaks them. Registration without a live child is not validated, and a duplicate process ID is accepted. The decoder maps recognized structured-exception codes to guest signals, keeping the guest `exit` byte layout for anything else. These exports are internal glue for libkernel, not console ABI: their behavior is unverified against any PS5 library and a title importing their NIDs directly is not expected. - [fprintf_s, vfprintf_s, vprintf_s, fwprintf_s, wprintf_s, vfwprintf_s and vwprintf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) follow C11 K.3.5.3.1, K.3.5.3.10 and K.3.9.1.1 with the constraints of `snprintf_s` and `swprintf_s`: a null stream, format or argument list, any `%n`, and a null `%s`, `%ls` or `%S` argument return -1 and write nothing, because the output is formatted completely before it is written. A violation only returns: the constraint handler is not invoked, `errno` is untouched, and the value is the one the narrow `snprintf_s` returns on this branch, not a result measured for a stream function on a PS5. A closed stream sets `EBADF` (9) as `fprintf` does. `vprintf_s` and `vwprintf_s` write to the guest standard output stream, and the wide forms count characters while the narrow ones count bytes, as their unchecked siblings do - [scanf_s, fscanf_s, vscanf_s and vfscanf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) run the scanner of `sscanf_s` over the stream, taking the size of `%s`, `%c` and `%[` as `unsigned int` after the pointer, so everything listed for `sscanf_s` applies, including the count of items converted before an undersized destination, whose first element is set to zero. Each directive is read from the stream with the host `fscanf`, with one character of look-ahead pushed back, so the stream stops after the last character a conversion used. A null stream, null format or null destination returns `EOF`, a closed stream also sets `EBADF`, and the constraint handler is not invoked. The 16-bit wide conversions (`%ls`, `%lc`, `%l[`, `%S`) are read by the same scanner as in `fscanf`. None of the stream forms were measured on a PS5, and the size type follows `sscanf_s` rather than `rsize_t` From 04983c615f878dc138be93f879e7e75106626283 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Fri, 23 Oct 2026 13:49:47 -0400 Subject: [PATCH 287/612] fix(libc): compute hypotf with FreeBSD msun's algorithm, as titles' libc.prx does hypotf rounded the host's double hypot to float, which differs from the titles' copy on about one in sixteen random pairs. The titles' copy is FreeBSD msun's e_hypotf.c, computed in float, which sets no errno even on overflow; hypotf now computes that algorithm. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Math.cpp | 70 ++++++++++++++++++++++++++++++++- core/libs/tests/GuestMath.cpp | 38 ++++++++++++++++++ docs/dev/debt/libc.md | 1 + 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 7efc4b8626..40a173375c 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -131,6 +131,7 @@ #include #include #include +#include #include #include "prx/libc/include/General.hpp" @@ -488,7 +489,74 @@ float APS5_VABI acosf_nid_postfix(float x) { const float correction = (z - high * high) / (s + high); return 2.0f * (high + (AcosfRational(z) * s + correction)); } -float APS5_VABI hypotf_nid_postfix(float x, float y) { return static_cast(std::hypot(static_cast(x), static_cast(y))); } +float APS5_VABI hypotf_nid_postfix(float x, float y) { + const auto word = [](float value) { return static_cast(std::bit_cast(value)); }; + const auto fromWord = [](std::int32_t value) { return std::bit_cast(static_cast(value)); }; + std::int32_t ha = word(x) & 0x7fffffff; + std::int32_t hb = word(y) & 0x7fffffff; + float a = x; + float b = y; + if (hb > ha) { + std::swap(a, b); + std::swap(ha, hb); + } + a = std::fabs(a); + b = std::fabs(b); + if (ha - hb > 0xf000000) { + return a + b; + } + int k = 0; + if (ha > 0x58800000) { + if (ha >= 0x7f800000) { + float w = std::fabs(x + 0.0f) - std::fabs(y + 0.0f); + if (ha == 0x7f800000) { + w = a; + } + if (hb == 0x7f800000) { + w = b; + } + return w; + } + ha -= 0x22000000; + hb -= 0x22000000; + k += 68; + a = fromWord(ha); + b = fromWord(hb); + } + if (hb < 0x26800000) { + if (hb <= 0x007fffff) { + if (hb == 0) { + return a; + } + b *= 0x1p126f; + a *= 0x1p126f; + k -= 126; + } else { + ha += 0x22000000; + hb += 0x22000000; + k -= 68; + a = fromWord(ha); + b = fromWord(hb); + } + } + float w = a - b; + if (w > b) { + const float t1 = fromWord(ha & static_cast(0xfffff000)); + const float t2 = a - t1; + w = std::sqrt(t1 * t1 - (b * (-b) - t2 * (a + t1))); + } else { + a = a + a; + const float y1 = fromWord(hb & static_cast(0xfffff000)); + const float y2 = b - y1; + const float t1 = fromWord((ha + 0x00800000) & static_cast(0xfffff000)); + const float t2 = a - t1; + w = std::sqrt(t1 * y1 - (w * (-w) - (t1 * y2 + t2 * b))); + } + if (k != 0) { + return fromWord((127 + k) << 23) * w; + } + return w; +} double APS5_VABI hypot_nid_postfix(double x, double y) { const std::uint64_t hx = std::bit_cast(x); const std::uint64_t hy = std::bit_cast(y); diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 34db858c7c..f4e18811ad 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -1357,6 +1357,43 @@ static void CheckExpm1ScalingRounding() { } _mm_setcsr(savedCsr); } +static void CheckHypotfConsoleBits() { + constexpr struct { + std::uint32_t x; + std::uint32_t y; + std::uint32_t expected; + } cases[] = { + {0x40400000u, 0xc0800000u, 0x40a00000u}, + {0x00000000u, 0x80000000u, 0x00000000u}, + {0x3f800000u, 0x00000000u, 0x3f800000u}, + {0x00000001u, 0x00000001u, 0x00000001u}, + {0x007fffffu, 0x00000003u, 0x007fffffu}, + {0x26800000u, 0x267fffffu, 0x26b504f3u}, + {0x3f800000u, 0x307fffffu, 0x3f800000u}, + {0x3f800000u, 0x30800001u, 0x3f800000u}, + {0x58800001u, 0x58800000u, 0x58b504f4u}, + {0x7f000000u, 0x7f000000u, 0x7f3504f3u}, + {0x7f3504f3u, 0x7f3504f3u, 0x7f7fffffu}, + {0x7f3504f4u, 0xff3504f4u, 0x7f800000u}, + {0x7f7fffffu, 0x73000000u, 0x7f7fffffu}, + {0x7f800000u, 0x7fc00123u, 0x7f800000u}, + {0xffc00123u, 0xff800000u, 0x7f800000u}, + {0x7fc00123u, 0x3f800000u, 0x7fc00123u}, + {0xffc00002u, 0x7fc00001u, 0x7fc00002u}, + {0x7fc00001u, 0xffc00002u, 0x7fc00001u}, + {0xbc059eb6u, 0x39413c60u, 0x3c05a772u}, + {0x3f700f06u, 0x452dc68bu, 0x452dc68bu}, + {0xb9b28c49u, 0xb9416ea5u, 0x39cb0f61u}, + {0x4056e15fu, 0x4379cdefu, 0x4379d3b5u}, + {0x3f750592u, 0x3a7fa6a3u, 0x3f75059bu}, + {0xc3a8012eu, 0x47264372u, 0x472644c5u}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(hypotf_nid_postfix(std::bit_cast(item.x), std::bit_cast(item.y))) == item.expected && errno == 1234); + Require(std::bit_cast(hypotf_nid_postfix(std::bit_cast(item.y), std::bit_cast(item.x))) == item.expected || std::isnan(std::bit_cast(item.expected))); + } +} @@ -2899,6 +2936,7 @@ int main() { CheckNextafterEnvironment(nextafterf_nid_postfix, 0x80000000u, 0x3f800000u, 0x00800000u, 0x7f7fffffu, 0x7f800000u); CheckNextafterEnvironment(nextafter_nid_postfix, std::uint64_t{0x8000000000000000ull}, std::uint64_t{0x3ff0000000000000ull}, std::uint64_t{0x0010000000000000ull}, std::uint64_t{0x7fefffffffffffffull}, std::uint64_t{0x7ff0000000000000ull}); CheckExpm1ScalingRounding(); + CheckHypotfConsoleBits(); CheckLog10ConsoleBits(); CheckLog10EdgeFlags(); CheckLog2ConsoleBits(); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 273a1dff14..f3d14af88e 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -174,6 +174,7 @@ - [log1p](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`s_log1p.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_log1p.c). As the titles' `libc.prx` does, it sets `EDOM` at msun's NaN return (arguments below -1, `-inf` and negative NaNs) and leaves `errno` alone for `log1p(-1)`, where it returns `-inf`. It gives the same bits, NaN payloads included, and `errno` as the `log1p` bundled with PPSA07923 and PPSA05916 on 320 million random and 68,216 structured arguments on a Windows host. The FMA-contracted build bundled with PPSA13934 differs on 73,682 of the random ones. - [nextafter and nextafterf](../../../core/libs/prx/libc/src/Math.cpp) (libc) have no physical-console or unmasked-exception validation. A Linux CPU probe compares result bits, `errno` and masked floating-point flags with the unchanged math code in FAR: Changing Tides PPSA03766 bundled `libc.prx` (`nextafter` 0x51b30, `nextafterf` 0x51c80, `_Feraise` 0x11da0): 16,864 calls across both precisions, four rounding modes and all DAZ/FTZ combinations, not exhaustive inputs. Earlier Windows sampling of eleven bundled libraries compares only bits and `errno`; exception behavior outside the FAR module remains unverified. - [tanh](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`s_tanh.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_tanh.c) over this file's msun `Expm1`, with `tiny` read at run time; it sets no `errno`, as the titles' `libc.prx` does not. It gives the same bits as the `tanh` bundled with PPSA07923 and PPSA05916 on 16 million random and 68,211 structured arguments on a Windows host; the FMA-contracted build bundled with PPSA13934 differs on 1,054 of them. +- [hypotf](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions other than the invalid one `main` already tests, and its inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`e_hypotf.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_hypotf.c) in float, which sets no `errno`, even on overflow, as the titles' `libc.prx` does. It gives the same bits as the copies bundled with PPSA07923 and PPSA05916 on 160 million random and 311,943 structured argument pairs each on a Windows host; the FMA-contracted build bundled with PPSA13934 differs on 95,940. - [log10](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is the fdlibm [`e_log10.c`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/e_log10.c) over its [`k_log.h`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/k_log.h), which FreeBSD 11.4 later replaced. As the titles' `libc.prx` does, it sets `EDOM` for every nonzero argument with the sign bit set (negatives, `-inf` and negative NaNs) and leaves `errno` alone for ±0, where it returns `-inf`, unlike `log`. It gives the same bits, NaN payloads included, and `errno` as the `log10` bundled with PPSA07923 and PPSA05916 on 320 million random and 31,165 structured arguments on a Windows host. The FMA-contracted build bundled with PPSA13934 differs on 229,944 of the random ones. - [log2](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is the fdlibm [`e_log2.c`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/e_log2.c) over the same `k_log.h` kernel as `log10` (`KernelLog`), which FreeBSD 11.4 later replaced. As the titles' `libc.prx` does, it sets `EDOM` for every nonzero argument with the sign bit set and leaves `errno` alone for ±0. It gives the same bits, NaN payloads included, and `errno` as the `log2` bundled with PPSA07923 and PPSA05916 on 16 million random and 31,165 structured arguments on a Windows host; the FMA-contracted build bundled with PPSA13934 differs on 727 of the random ones. - [sinhf](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows the `sinhf` of the `libc.prx` bundled with PPSA28319 (disassembly): FreeBSD msun's [`e_sinhf.c`](https://github.com/freebsd/freebsd-src/blob/5aa554c7e5b6fd9042986df07f12ac5687e41392/lib/msun/src/e_sinhf.c) from before `__ldexp_expf`, which squares `expf(|x| / 2)` between `logf(FLT_MAX)` and the overflow threshold, calling msun's [`expm1f`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/s_expm1f.c) and [`expf`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/e_expf.c), whose paths for the arguments it passes are inlined here. Past the threshold Sony's error helper sets `errno` to `ERANGE` before returning `x * 1e37`; msun itself sets no `errno`. That code, run on a Windows host, gives the same bits as this implementation for every finite argument up to the threshold, of both signs; it was not run on Linux. That the system `libSceLibcInternal`, which Ruffle (PPSA68091) and RetroArch (PPSA99169) import while bundling a zero-return `libc.prx`, has the same code is assumed From 7c1b94d83eb63f219959915dd94fede928361669 Mon Sep 17 00:00:00 2001 From: Noxy-HNR Date: Mon, 2 Nov 2026 19:26:55 +0000 Subject: [PATCH 288/612] fix(libSceJxr): bound the remapQP shift before adding the QP exponent Sum the shift and the index exponent in 64 bits so an extreme shift is rejected by the range check instead of overflowing first, and test both extreme shifts as rejected arguments. --- core/libs/prx/libSceJxr/src/Quantizer.cpp | 2 +- core/libs/tests/GuestJxrQuantizer.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceJxr/src/Quantizer.cpp b/core/libs/prx/libSceJxr/src/Quantizer.cpp index 5f794e214a..44ae36ac67 100644 --- a/core/libs/prx/libSceJxr/src/Quantizer.cpp +++ b/core/libs/prx/libSceJxr/src/Quantizer.cpp @@ -24,7 +24,7 @@ void RequireShift(const void* quantizer, std::int32_t shift, std::int32_t scaled Require(quantizer != nullptr, function, "null quantizer"); const auto index = *static_cast(quantizer); if (index == 0 || !scaled) return; - const auto exponent = index < 16 ? shift : (index >> 4) - 1 + shift; + const std::int64_t exponent = static_cast(shift) + (index < 16 ? 0 : (index >> 4) - 1); Require(exponent >= 0 && exponent <= 24, function, "shift leaves the QP outside 32 bits"); } diff --git a/core/libs/tests/GuestJxrQuantizer.cpp b/core/libs/tests/GuestJxrQuantizer.cpp index e576d8c979..78ce968cc8 100644 --- a/core/libs/tests/GuestJxrQuantizer.cpp +++ b/core/libs/tests/GuestJxrQuantizer.cpp @@ -86,6 +86,12 @@ void TestRemap() { RequireInvalidArgument([&] { remapQP_nid_postfix(&wide, 11, 1); }, "QP over 32 bits"); wide.index = 3; RequireInvalidArgument([&] { remapQP_nid_postfix(&wide, -1, 1); }, "negative shift"); + for (const std::uint8_t index : {std::uint8_t{3}, std::uint8_t{255}}) { + wide.index = index; + RequireInvalidArgument([&] { remapQP_nid_postfix(&wide, INT32_MAX, 1); }, "largest shift"); + RequireInvalidArgument([&] { remapQP_nid_postfix(&wide, INT32_MIN, 1); }, "smallest shift"); + } + wide.index = 3; remapQP_nid_postfix(&wide, -1, 0); } From 369b743519bf1b49430efa1597fb557e1dbd57f8 Mon Sep 17 00:00:00 2001 From: Noxy-HNR Date: Mon, 2 Nov 2026 19:45:43 +0000 Subject: [PATCH 289/612] fix(libSceJxr): define the bit I/O boundary calls jxrlib leaves undefined Return 0 from zero-bit reads, skip empty stores at a 16-bit word boundary, mask putBit16 and putBit32 values before calling putBit16z instead of jxrlib's negative left shift, reject write positions past the word, and build strcodec.c and segdec.c with jxrlib's 16-bit load path so stream loads stay aligned. The test checks these boundaries against explicit values and passes under UBSan. --- cmake/Jxr.cmake | 2 + core/libs/prx/libSceJxr/src/BitIo.cpp | 40 +++++++++++---- core/libs/prx/libSceJxr/src/NativeBitIo.c | 2 - core/libs/tests/GuestJxrBitIo.cpp | 60 ++++++++++++++++++++++- core/libs/tests/GuestJxrBitIoReference.c | 14 +++++- docs/dev/debt/libSceJxr.md | 4 +- 6 files changed, 106 insertions(+), 16 deletions(-) diff --git a/cmake/Jxr.cmake b/cmake/Jxr.cmake index 9e20294e31..109b12a74d 100644 --- a/cmake/Jxr.cmake +++ b/cmake/Jxr.cmake @@ -39,6 +39,8 @@ add_library(jxr STATIC EXCLUDE_FROM_ALL ${JXR_SOURCE_DIR}/jxrgluelib/JXRGluePFC.c ${JXR_SOURCE_DIR}/jxrgluelib/JXRMeta.c) configure_jxr_native(jxr) +set_source_files_properties(${JXR_SOURCE_DIR}/image/sys/strcodec.c ${JXR_SOURCE_DIR}/image/decode/segdec.c + PROPERTIES COMPILE_DEFINITIONS _M_IA64) if(UNIX) target_link_libraries(jxr PRIVATE m) endif() diff --git a/core/libs/prx/libSceJxr/src/BitIo.cpp b/core/libs/prx/libSceJxr/src/BitIo.cpp index c3bfee54d8..9a8add44c7 100644 --- a/core/libs/prx/libSceJxr/src/BitIo.cpp +++ b/core/libs/prx/libSceJxr/src/BitIo.cpp @@ -12,8 +12,6 @@ std::int32_t JxrGetBit16s_nid_no_patch(void* io, std::uint32_t bits); std::uint32_t JxrGetBit32_nid_no_patch(void* io, std::uint32_t bits); std::uint32_t JxrFlushToByte_nid_no_patch(void* io); void JxrPutBit16z_nid_no_patch(void* io, std::uint32_t value, std::uint32_t bits); -void JxrPutBit16_nid_no_patch(void* io, std::uint32_t value, std::uint32_t bits); -void JxrPutBit32_nid_no_patch(void* io, std::uint32_t value, std::uint32_t bits); void JxrFillToByte_nid_no_patch(void* io); std::uint32_t JxrGetSizeRead_nid_no_patch(void* io); std::uint32_t JxrGetSizeWrite_nid_no_patch(void* io); @@ -48,15 +46,27 @@ void* Read(Jxr::BitIo* io, std::uint32_t bits, std::uint32_t limit, const char* void* Write(Jxr::BitIo* io, std::uint32_t bits, std::uint32_t limit, const char* function) { Stream(io, function); Require(bits <= limit, function, "bit count over the jxrlib limit"); + Require(io->bitsUsed < 16, function, "write position outside the 16-bit word"); return io; } +std::uint32_t ZeroBitRead(Jxr::BitIo* io) { + JxrFlushBit16_nid_no_patch(io, 0); + return 0; +} + +void PutMasked(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits) { + if (bits == 0 && io->bitsUsed == 0) return; + JxrPutBit16z_nid_no_patch(io, value & (0xffffu >> (16 - bits)), bits); +} + } // namespace extern "C" { std::uint32_t APS5_VABI peekBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bits) { - return JxrPeekBit16_nid_no_patch(Read(io, bits, 16, __func__), bits); + Read(io, bits, 16, __func__); + return bits == 0 ? 0 : JxrPeekBit16_nid_no_patch(io, bits); } std::uint32_t APS5_VABI flushBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bits) { @@ -64,7 +74,8 @@ std::uint32_t APS5_VABI flushBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bit } std::uint32_t APS5_VABI getBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bits) { - return JxrGetBit16_nid_no_patch(Read(io, bits, 16, __func__), bits); + Read(io, bits, 16, __func__); + return bits == 0 ? ZeroBitRead(io) : JxrGetBit16_nid_no_patch(io, bits); } std::uint32_t APS5_VABI getBool16_nid_postfix(Jxr::BitIo* io) { @@ -76,7 +87,8 @@ std::int32_t APS5_VABI getBit16s_nid_postfix(Jxr::BitIo* io, std::uint32_t bits) } std::uint32_t APS5_VABI getBit32_nid_postfix(Jxr::BitIo* io, std::uint32_t bits) { - return JxrGetBit32_nid_no_patch(Read(io, bits, 32, __func__), bits); + Read(io, bits, 32, __func__); + return bits == 0 ? ZeroBitRead(io) : JxrGetBit32_nid_no_patch(io, bits); } std::uint32_t APS5_VABI flushToByte_nid_postfix(Jxr::BitIo* io) { @@ -85,19 +97,29 @@ std::uint32_t APS5_VABI flushToByte_nid_postfix(Jxr::BitIo* io) { void APS5_VABI putBit16z_nid_postfix(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits) { Require(bits >= 32 || (value >> bits) == 0, __func__, "value wider than the bit count"); - JxrPutBit16z_nid_no_patch(Write(io, bits, 16, __func__), value, bits); + Write(io, bits, 16, __func__); + if (bits == 0 && io->bitsUsed == 0) return; + JxrPutBit16z_nid_no_patch(io, value, bits); } void APS5_VABI putBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits) { - JxrPutBit16_nid_no_patch(Write(io, bits, 16, __func__), value, bits); + Write(io, bits, 16, __func__); + PutMasked(io, value, bits); } void APS5_VABI putBit32_nid_postfix(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits) { - JxrPutBit32_nid_no_patch(Write(io, bits, 32, __func__), value, bits); + Write(io, bits, 32, __func__); + if (bits > 16) { + PutMasked(io, value >> (bits - 16), 16); + bits -= 16; + } + PutMasked(io, value, bits); } void APS5_VABI fillToByte_nid_postfix(Jxr::BitIo* io) { - JxrFillToByte_nid_no_patch(Write(io, 0, 16, __func__)); + Write(io, 0, 16, __func__); + if (io->bitsUsed == 0) return; + JxrFillToByte_nid_no_patch(io); } std::uint32_t APS5_VABI getBit16_S_nid_postfix(void* codec, Jxr::BitIo* io, std::uint32_t bits) { diff --git a/core/libs/prx/libSceJxr/src/NativeBitIo.c b/core/libs/prx/libSceJxr/src/NativeBitIo.c index 6367d026bf..f8e32f06d3 100644 --- a/core/libs/prx/libSceJxr/src/NativeBitIo.c +++ b/core/libs/prx/libSceJxr/src/NativeBitIo.c @@ -11,8 +11,6 @@ int32_t JxrGetBit16s_nid_no_patch(void* io, uint32_t bits) { return getBit16s((B uint32_t JxrGetBit32_nid_no_patch(void* io, uint32_t bits) { return getBit32((BitIOInfo*)io, bits); } uint32_t JxrFlushToByte_nid_no_patch(void* io) { return flushToByte((BitIOInfo*)io); } void JxrPutBit16z_nid_no_patch(void* io, uint32_t value, uint32_t bits) { putBit16z((BitIOInfo*)io, value, bits); } -void JxrPutBit16_nid_no_patch(void* io, uint32_t value, uint32_t bits) { putBit16((BitIOInfo*)io, value, bits); } -void JxrPutBit32_nid_no_patch(void* io, uint32_t value, uint32_t bits) { putBit32((BitIOInfo*)io, value, bits); } void JxrFillToByte_nid_no_patch(void* io) { fillToByte((BitIOInfo*)io); } uint32_t JxrGetSizeRead_nid_no_patch(void* io) { return getSizeRead((BitIOInfo*)io); } uint32_t JxrGetSizeWrite_nid_no_patch(void* io) { return getSizeWrite((BitIOInfo*)io); } diff --git a/core/libs/tests/GuestJxrBitIo.cpp b/core/libs/tests/GuestJxrBitIo.cpp index b65f20a013..bbdb8a23eb 100644 --- a/core/libs/tests/GuestJxrBitIo.cpp +++ b/core/libs/tests/GuestJxrBitIo.cpp @@ -102,7 +102,9 @@ void RunReads() { std::uint32_t bits = Next() % 17; if (operation == 4) bits %= 16; if (operation == 5) bits = Next() % 33; - const auto expected = JxrBitIoReference_nid_no_patch(operation, &native, 0, bits); + const bool zeroBits = bits == 0 && (operation == 0 || operation == 2 || operation == 5); + const auto expected = zeroBits ? 0u : JxrBitIoReference_nid_no_patch(operation, &native, 0, bits); + if (zeroBits && operation != 0) JxrBitIoReference_nid_no_patch(1, &native, 0, 0); const auto actual = Guest(operation, &guest, 0, bits); Require(actual == expected, "read result differs from jxrlib"); RequireSame(guest, guestRing, native, nativeRing, "read state differs from jxrlib"); @@ -129,7 +131,8 @@ void RunWrites() { std::uint32_t bits = operation == 11 ? Next() % 33 : Next() % 17; std::uint32_t value = Next() ^ (Next() << 24); if (operation == 9) value &= bits == 0 ? 0u : 0xffffffffu >> (32 - bits); - const auto expected = JxrBitIoReference_nid_no_patch(operation, &native, value, bits); + const bool emptyStore = native.bitsUsed == 0 && (operation == 12 || (operation != 13 && bits == 0)); + const auto expected = emptyStore ? 0u : JxrBitIoReference_nid_no_patch(operation, &native, value, bits); const auto actual = Guest(operation, &guest, value, bits); Require(actual == expected, "write result differs from jxrlib"); RequireSame(guest, guestRing, native, nativeRing, "write state differs from jxrlib"); @@ -137,6 +140,52 @@ void RunWrites() { } } +void RunZeroBits(Ring& ring) { + for (std::size_t i = 0; i < RingAlignment; ++i) ring.base[i] = static_cast(i * 13 + 5); + for (std::uint32_t used = 0; used < 16; ++used) { + auto io = Attach(ring, 64, used, 0); + const auto before = io; + Require(peekBit16_nid_postfix(&io, 0) == 0, "zero-bit peek"); + Require(std::memcmp(&io, &before, sizeof(io)) == 0, "zero-bit peek keeps the state"); + Require(getBit16_nid_postfix(&io, 0) == 0, "zero-bit read"); + Require(io.current == before.current && io.bitsUsed == used && io.accumulator == before.accumulator, + "zero-bit read keeps the position"); + Require(getBit32_nid_postfix(&io, 0) == 0, "zero-bit 32-bit read"); + Require(io.current == before.current && io.bitsUsed == used && io.accumulator == before.accumulator, + "zero-bit 32-bit read keeps the position"); + } + + std::vector snapshot(ring.base, ring.base + RingAlignment); + Jxr::BitIo io{}; + io.mask = WriteMask; + io.start = ring.base; + io.current = ring.base + 128; + const auto before = io; + putBit16z_nid_postfix(&io, 0, 0); + putBit16_nid_postfix(&io, 0xffffffffu, 0); + putBit32_nid_postfix(&io, 0xffffffffu, 0); + fillToByte_nid_postfix(&io); + Require(std::memcmp(&io, &before, sizeof(io)) == 0, "aligned empty stores keep the state"); + Require(std::memcmp(snapshot.data(), ring.base, RingAlignment) == 0, "aligned empty stores keep the buffer"); + + putBit16_nid_postfix(&io, 0xfff5u, 4); + Require(io.accumulator == 5 && io.bitsUsed == 4 && io.current == ring.base + 128, "masked 4-bit store state"); + Require(ring.base[128] == 0x50 && ring.base[129] == 0x00, "masked 4-bit store bytes"); + fillToByte_nid_postfix(&io); + Require(io.bitsUsed == 8 && ring.base[128] == 0x50, "fill to the byte boundary"); + fillToByte_nid_postfix(&io); + putBit16_nid_postfix(&io, 0x1234u, 0); + Require(io.bitsUsed == 8 && io.current == ring.base + 128, "empty stores at a byte boundary"); + putBit32_nid_postfix(&io, 0xfabcdeu, 20); + Require(io.bitsUsed == 12 && io.current == ring.base + 130, "20-bit store position"); + Require(ring.base[128] == 0x50 && ring.base[129] == 0xab && ring.base[130] == 0xcd && ring.base[131] == 0xe0, + "20-bit store bytes"); + putBit32_nid_postfix(&io, 0xffffffffu, 32); + Require(io.bitsUsed == 12 && io.current == ring.base + 134, "32-bit store position"); + Require(ring.base[130] == 0xcd && ring.base[131] == 0xef && ring.base[132] == 0xff && ring.base[133] == 0xff, + "32-bit store bytes"); +} + void RunEdges() { for (std::uint32_t qp = 0; qp < 256; ++qp) Require(dquantBits_nid_postfix(static_cast(qp)) == @@ -155,6 +204,8 @@ void RunEdges() { auto crossing = Attach(ring, 4096, 0, 0); RequireInvalidArgument([&] { getBit16_S_nid_postfix(nullptr, &crossing, 1); }, "getBit16_S packet without stream"); + RunZeroBits(ring); + RequireInvalidArgument([] { getBit16_nid_postfix(nullptr, 1); }, "null read state"); RequireInvalidArgument([] { putBit16_nid_postfix(nullptr, 1, 1); }, "null write state"); RequireInvalidArgument([] { getSizeRead_nid_postfix(nullptr); }, "null size state"); @@ -170,6 +221,11 @@ void RunEdges() { RequireInvalidArgument([&] { putBit16z_nid_postfix(&edge, 2, 1); }, "wide unmasked value"); RequireInvalidArgument([&] { putBit16_nid_postfix(&edge, 0, 17); }, "17-bit write"); RequireInvalidArgument([&] { putBit32_nid_postfix(&edge, 0, 33); }, "33-bit write"); + edge.mask = WriteMask; + edge.bitsUsed = 16; + RequireInvalidArgument([&] { putBit16z_nid_postfix(&edge, 0, 0); }, "write position past the word"); + RequireInvalidArgument([&] { fillToByte_nid_postfix(&edge); }, "fill position past the word"); + edge.bitsUsed = 0; edge.mask = -1; RequireInvalidArgument([&] { flushBit16_nid_postfix(&edge, 1); }, "odd wrap mask"); edge.mask = ReadMask; diff --git a/core/libs/tests/GuestJxrBitIoReference.c b/core/libs/tests/GuestJxrBitIoReference.c index 1f6e789868..2e4e5fd335 100644 --- a/core/libs/tests/GuestJxrBitIoReference.c +++ b/core/libs/tests/GuestJxrBitIoReference.c @@ -3,6 +3,10 @@ _Static_assert(sizeof(BitIOInfo) == 48, "BitIOInfo size"); +static void PutMasked(BitIOInfo* io, uint32_t value, uint32_t bits) { + putBit16z(io, value & (0xffffu >> (16 - bits)), bits); +} + uint32_t JxrBitIoReference_nid_no_patch(int operation, void* state, uint32_t value, uint32_t bits) { BitIOInfo* io = (BitIOInfo*)state; switch (operation) { @@ -16,8 +20,14 @@ uint32_t JxrBitIoReference_nid_no_patch(int operation, void* state, uint32_t val case 7: return getSizeRead(io); case 8: return getPosRead(io); case 9: putBit16z(io, value, bits); return 0; - case 10: putBit16(io, value, bits); return 0; - case 11: putBit32(io, value, bits); return 0; + case 10: PutMasked(io, value, bits); return 0; + case 11: + if (bits > 16) { + PutMasked(io, value >> (bits - 16), 16); + bits -= 16; + } + PutMasked(io, value, bits); + return 0; case 12: fillToByte(io); return 0; case 13: return getSizeWrite(io); case 14: return dquantBits((U8)value); diff --git a/docs/dev/debt/libSceJxr.md b/docs/dev/debt/libSceJxr.md index 300d9ed07b..5f6f15837a 100644 --- a/docs/dev/debt/libSceJxr.md +++ b/docs/dev/debt/libSceJxr.md @@ -12,7 +12,9 @@ - [readIS and writeIS](../../../core/libs/prx/libSceJxr/Export.cpp) use the non-ARM, LP64 `BitIOInfo` and `WMPStream` ABI and packet algorithm from [Microsoft's JPEG XR reference implementation](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L1090-L1177). Console structure layouts and execution remain unverified. Profiling callbacks are omitted as in the reference Unix build with `DISABLE_PERF_MEASUREMENT`; the codec argument is unused. Where the reference uses an invalid pointer, `std::invalid_argument` is thrown instead: a null bit state, stream or callback, a read-only read packet, and a null packet when a read copies its shadow or a successful write offsets it; a null packet still reaches the callback first. No codec is instantiated by these helpers; callers must supply the reference ring buffer and guest-ABI stream callbacks. -- [Bit I/O](../../../core/libs/prx/libSceJxr/src/BitIo.cpp) (`peekBit16`, `flushBit16`, `getBit16`, `getBool16`, `getBit16s`, `getBit32`, `flushToByte`, `putBit16z`, `putBit16`, `putBit32`, `fillToByte`, `getSizeRead`, `getSizeWrite`, `getPosRead`, `dquantBits`) calls jxrlib's non-ARM [strcodec.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L905-L1061), built from `3rdparty/jxrlib`, on the guest `BitIOInfo`. That the console's module uses these prototypes and that layout is inferred from the export names. Bit counts over jxrlib's asserted limits, a value wider than `putBit16z`'s count, an odd read wrap mask and a null state or current pointer throw. Zero-bit reads and byte-aligned stores reach jxrlib's 32-bit shift by 32, which C leaves undefined. Closes when: the console's functions are disassembled or measured on a PS5 +- [Bit I/O](../../../core/libs/prx/libSceJxr/src/BitIo.cpp) (`peekBit16`, `flushBit16`, `getBit16`, `getBool16`, `getBit16s`, `getBit32`, `flushToByte`, `putBit16z`, `putBit16`, `putBit32`, `fillToByte`, `getSizeRead`, `getSizeWrite`, `getPosRead`, `dquantBits`) calls jxrlib's non-ARM [strcodec.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L905-L1061), built from `3rdparty/jxrlib`, on the guest `BitIOInfo`. That the console's module uses these prototypes and that layout is inferred from the export names. Bit counts over jxrlib's asserted limits, a value wider than `putBit16z`'s count, an odd read wrap mask and a null state or current pointer throw. Closes when: the console's functions are disassembled or measured on a PS5 + +- [Bit I/O boundaries](../../../core/libs/prx/libSceJxr/src/BitIo.cpp): Calls whose jxrlib path is undefined in C are handled in the adapter. A zero-bit peek returns 0, and a zero-bit `getBit16` or `getBit32` returns 0 after `flushBit16(0)`, instead of shifting the accumulator by 32. A zero-bit store or `fillToByte` at a 16-bit boundary changes nothing, instead of shifting by 32. `putBit16` and `putBit32` mask the value and call `putBit16z` in 16-bit parts, instead of jxrlib's `-1 << cBits` mask. A write position of 16 bits or more throws. These results define cases jxrlib leaves undefined; the console behaviour for them is unknown. Closes when: the console's functions are disassembled or measured on a PS5 - [getBit16_S](../../../core/libs/prx/libSceJxr/src/BitIo.cpp) is `getBit16` followed by `readIS`, as in [strcodec.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L1021-L1027) with `readIS_L1` defined as `readIS`; the codec argument is passed through. Closes when: the console's function is disassembled or measured on a PS5 From 99a3aaaf67e0851716b84cb5959b0907da91f067 Mon Sep 17 00:00:00 2001 From: Noxy-HNR Date: Mon, 2 Nov 2026 14:40:42 -0600 Subject: [PATCH 290/612] fix(libSceJxr): reject unaligned bit writer positions --- core/libs/prx/libSceJxr/src/BitIo.cpp | 2 ++ core/libs/tests/GuestJxrBitIo.cpp | 13 +++++++++++++ docs/dev/debt/libSceJxr.md | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceJxr/src/BitIo.cpp b/core/libs/prx/libSceJxr/src/BitIo.cpp index 9a8add44c7..01abd23bc7 100644 --- a/core/libs/prx/libSceJxr/src/BitIo.cpp +++ b/core/libs/prx/libSceJxr/src/BitIo.cpp @@ -47,6 +47,8 @@ void* Write(Jxr::BitIo* io, std::uint32_t bits, std::uint32_t limit, const char* Stream(io, function); Require(bits <= limit, function, "bit count over the jxrlib limit"); Require(io->bitsUsed < 16, function, "write position outside the 16-bit word"); + Require(reinterpret_cast(io->current) % alignof(std::uint16_t) == 0, function, + "unaligned write position"); return io; } diff --git a/core/libs/tests/GuestJxrBitIo.cpp b/core/libs/tests/GuestJxrBitIo.cpp index bbdb8a23eb..bc00be2dff 100644 --- a/core/libs/tests/GuestJxrBitIo.cpp +++ b/core/libs/tests/GuestJxrBitIo.cpp @@ -206,6 +206,19 @@ void RunEdges() { RunZeroBits(ring); + for (const std::uint32_t used : {0u, 1u, 8u, 15u}) { + auto odd = Attach(ring, 129, used, 0); + odd.mask = WriteMask; + const auto before = odd; + const std::vector snapshot(ring.base, ring.base + RingAlignment); + RequireInvalidArgument([&] { putBit16z_nid_postfix(&odd, 0, 0); }, "odd-position unmasked store"); + RequireInvalidArgument([&] { putBit16_nid_postfix(&odd, 1, 1); }, "odd-position masked store"); + RequireInvalidArgument([&] { putBit32_nid_postfix(&odd, 0xffffffffu, 32); }, "odd-position 32-bit store"); + RequireInvalidArgument([&] { fillToByte_nid_postfix(&odd); }, "odd-position byte fill"); + Require(std::memcmp(&odd, &before, sizeof(odd)) == 0, "rejected odd-position stores keep the state"); + Require(std::memcmp(ring.base, snapshot.data(), RingAlignment) == 0, "rejected odd-position stores keep the buffer"); + } + RequireInvalidArgument([] { getBit16_nid_postfix(nullptr, 1); }, "null read state"); RequireInvalidArgument([] { putBit16_nid_postfix(nullptr, 1, 1); }, "null write state"); RequireInvalidArgument([] { getSizeRead_nid_postfix(nullptr); }, "null size state"); diff --git a/docs/dev/debt/libSceJxr.md b/docs/dev/debt/libSceJxr.md index 5f6f15837a..6db02ed81e 100644 --- a/docs/dev/debt/libSceJxr.md +++ b/docs/dev/debt/libSceJxr.md @@ -14,7 +14,7 @@ - [Bit I/O](../../../core/libs/prx/libSceJxr/src/BitIo.cpp) (`peekBit16`, `flushBit16`, `getBit16`, `getBool16`, `getBit16s`, `getBit32`, `flushToByte`, `putBit16z`, `putBit16`, `putBit32`, `fillToByte`, `getSizeRead`, `getSizeWrite`, `getPosRead`, `dquantBits`) calls jxrlib's non-ARM [strcodec.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L905-L1061), built from `3rdparty/jxrlib`, on the guest `BitIOInfo`. That the console's module uses these prototypes and that layout is inferred from the export names. Bit counts over jxrlib's asserted limits, a value wider than `putBit16z`'s count, an odd read wrap mask and a null state or current pointer throw. Closes when: the console's functions are disassembled or measured on a PS5 -- [Bit I/O boundaries](../../../core/libs/prx/libSceJxr/src/BitIo.cpp): Calls whose jxrlib path is undefined in C are handled in the adapter. A zero-bit peek returns 0, and a zero-bit `getBit16` or `getBit32` returns 0 after `flushBit16(0)`, instead of shifting the accumulator by 32. A zero-bit store or `fillToByte` at a 16-bit boundary changes nothing, instead of shifting by 32. `putBit16` and `putBit32` mask the value and call `putBit16z` in 16-bit parts, instead of jxrlib's `-1 << cBits` mask. A write position of 16 bits or more throws. These results define cases jxrlib leaves undefined; the console behaviour for them is unknown. Closes when: the console's functions are disassembled or measured on a PS5 +- [Bit I/O boundaries](../../../core/libs/prx/libSceJxr/src/BitIo.cpp): the console behaviour for these cases is unknown. The adapter defines paths jxrlib leaves undefined in C: a zero-bit peek returns 0; zero-bit `getBit16` and `getBit32` return 0 after `flushBit16(0)`; an empty store or byte fill at a word boundary changes nothing. Masked writes use unsigned masks and `putBit16z` in 16-bit parts, avoiding jxrlib's negative left shift. A bit position of 16 or more or a byte pointer not aligned for a 16-bit store throws before changing state or memory. Closes when: the console's functions are disassembled or measured on a PS5 - [getBit16_S](../../../core/libs/prx/libSceJxr/src/BitIo.cpp) is `getBit16` followed by `readIS`, as in [strcodec.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L1021-L1027) with `readIS_L1` defined as `readIS`; the codec argument is passed through. Closes when: the console's function is disassembled or measured on a PS5 From 2159dd620b94715e574563ff5593b76df2b042cb Mon Sep 17 00:00:00 2001 From: JJenkx Date: Sun, 1 Nov 2026 15:04:06 -0500 Subject: [PATCH 291/612] fix(libkernel): notice a socket closed during poll or select Both waits held the guest sockets they polled and handed the host one poll call for the whole wait. A guest close dropped only the table entry, so the native socket stayed open and the wait ran to its timeout: poll returned 0 instead of POLLNVAL, and select reported EBADF only after its next 100 ms re-lookup, or not at all when the timeout was shorter. FreeBSD 11.4 wakes such a waiter and reports the closed descriptor. Waits that include sockets now poll the host in slices of at most 10 ms and, before each slice, check that every descriptor still maps to the socket it held. A closed one answers POLLNVAL from poll and EBADF from select. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel/Socket/src/Udp.cpp | 45 ++++++++++++++++++---- core/libs/tests/GuestPoll.cpp | 22 +++++++++++ core/libs/tests/GuestSelect.cpp | 26 +++++++++++++ 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index dfe5040afc..bed79e7b53 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -665,6 +665,7 @@ struct PollFlag { }; constexpr short GuestPollPriority = 0x2; constexpr short GuestPollInvalid = 0x20; +constexpr int SocketCloseCheckMilliseconds = 10; constexpr short GuestPollAccepted = 0x1 | 0x2 | 0x4 | 0x8 | 0x10 | 0x20 | 0x40 | 0x80 | 0x100; #ifdef _WIN32 constexpr short NativePollIn = POLLRDNORM; @@ -1193,11 +1194,24 @@ int GuestSockets::Poll(KernelSocketPoll::Entry* entries, int count, int timeoutM held.push_back(std::move(socket)); } if (native.empty()) return 0; + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeoutMilliseconds < 0 ? 0 : timeoutMilliseconds); + int result = 0; + for (;;) { + for (std::size_t index = 0; index < held.size(); ++index) { + if (Find(entries[index].descriptor) != held[index]) return -9; + } + int slice = SocketCloseCheckMilliseconds; + if (timeoutMilliseconds >= 0) { + const auto left = std::chrono::duration_cast(deadline - std::chrono::steady_clock::now()).count(); + slice = static_cast(std::clamp(left, 0, SocketCloseCheckMilliseconds)); + } #ifdef _WIN32 - const int result = WSAPoll(native.data(), static_cast(native.size()), timeoutMilliseconds); + result = WSAPoll(native.data(), static_cast(native.size()), slice); #else - const int result = ::poll(native.data(), static_cast(native.size()), timeoutMilliseconds); + result = ::poll(native.data(), static_cast(native.size()), slice); #endif + if (result != 0 || (timeoutMilliseconds >= 0 && std::chrono::steady_clock::now() >= deadline)) break; + } if (result < 0) return -NativeError(); int ready = 0; for (int index = 0; index < count; ++index) { @@ -2279,6 +2293,13 @@ int APS5_VABI sendfile_nid_postfix(int file, int descriptor, std::int64_t offset if (const int error = SendIovecs(*socket, trailerIovecs.data(), static_cast(trailerIovecs.size()), sent)) return finish(error); return finish(0); } +static bool SocketStillHeld(int descriptor, const void* owner) { + if (auto socket = Find(descriptor)) return socket.get() == owner; + std::shared_ptr current; + std::uintptr_t value = 0; + return KernelFindSocketNative_nid_no_patch(descriptor, ¤t, &value) && current.get() == owner; +} + static int PollDescriptors(GuestPollDescriptor* descriptors, std::uint32_t count, int timeout) { if (timeout < -1) return Fail(22); if (count && !descriptors) return Fail(14); @@ -2392,10 +2413,12 @@ static int PollDescriptors(GuestPollDescriptor* descriptors, std::uint32_t count constexpr bool waitsForUrgent = false; #endif #ifdef _WIN32 - const bool sliced = !pipes.empty() || !kqueues.empty(); + const bool fastSlices = !pipes.empty() || !kqueues.empty(); #else - const bool sliced = !kqueues.empty() || waitsForUrgent; + const bool fastSlices = !kqueues.empty() || waitsForUrgent; #endif + const bool sliced = fastSlices || !held.empty(); + const int step = fastSlices ? 1 : SocketCloseCheckMilliseconds; const auto anyUrgentPending = [&]() -> bool { #ifdef __APPLE__ for (std::size_t i = 0; i < socketCount; ++i) { @@ -2407,6 +2430,12 @@ static int PollDescriptors(GuestPollDescriptor* descriptors, std::uint32_t count }; for (;;) { int checkedReady = 0; + bool closed = false; + for (std::size_t i = 0; i < held.size(); ++i) { + if (SocketStillHeld(descriptors[owners[i]].descriptor, held[i].get())) continue; + descriptors[owners[i]].revents = GuestPollInvalid; + closed = true; + } const bool urgent = anyUrgentPending(); #ifdef _WIN32 for (const auto index : pipes) { @@ -2427,12 +2456,12 @@ static int PollDescriptors(GuestPollDescriptor* descriptors, std::uint32_t count if (entry.revents) ++checkedReady; } int slice = 0; - if (!ready && !checkedReady && !urgent) { + if (!ready && !checkedReady && !urgent && !closed) { if (!sliced) slice = timeout; - else if (timeout < 0) slice = 1; + else if (timeout < 0) slice = step; else { const auto left = std::chrono::duration_cast(deadline - std::chrono::steady_clock::now()).count(); - slice = static_cast(std::clamp(left, 0, 1)); + slice = static_cast(std::clamp(left, 0, step)); } } if (!native.empty()) { @@ -2450,7 +2479,7 @@ static int PollDescriptors(GuestPollDescriptor* descriptors, std::uint32_t count for (const auto& request : native) if (request.revents) ++socketReady; const bool expired = timeout >= 0 && std::chrono::steady_clock::now() >= deadline; - if (ready || checkedReady || urgent || socketReady || expired || !sliced) { + if (ready || checkedReady || urgent || socketReady || expired || closed || !sliced) { ready += checkedReady; #ifdef _WIN32 for (std::size_t i = 0; i < native.size(); ++i) collect(i, false); diff --git a/core/libs/tests/GuestPoll.cpp b/core/libs/tests/GuestPoll.cpp index be82e99f0a..7020cb88a4 100644 --- a/core/libs/tests/GuestPoll.cpp +++ b/core/libs/tests/GuestPoll.cpp @@ -349,6 +349,27 @@ static void PollsAPipeClosedDuringTheWait() { } } +static void PollsASocketClosedDuringTheWait() { + for (int attempt = 0; attempt < 20; ++attempt) { + const int socket = socket_nid_postfix(2, 2, 0); + Require(socket >= 0); + int result = 0; + short revents = 0; + std::atomic started{false}; + std::thread waiter([&] { + GuestPollDescriptor entry{socket, In, 0}; + started = true; + result = poll_nid_postfix(&entry, 1, 200); + revents = entry.revents; + }); + while (!started) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::microseconds(1000 + attempt * 100)); + Require(close_nid_postfix(socket) == 0); + waiter.join(); + Require(result == 1 && revents == Invalid); + } +} + int main() { for (const int descriptor : {0, 1, 2}) StandardDescriptorFile(descriptor); PollsUrgentData(); @@ -456,4 +477,5 @@ int main() { Require(std::chrono::steady_clock::now() - inputStart >= std::chrono::milliseconds(25)); PollsKqueues(listener); Require(close_nid_postfix(listener) == 0); + PollsASocketClosedDuringTheWait(); } diff --git a/core/libs/tests/GuestSelect.cpp b/core/libs/tests/GuestSelect.cpp index e28074413b..454967da74 100644 --- a/core/libs/tests/GuestSelect.cpp +++ b/core/libs/tests/GuestSelect.cpp @@ -81,6 +81,31 @@ static void StandardDescriptorFile(int descriptor) { Require(unlink_nid_postfix(path.c_str()) == 0); } +static void SocketClosedDuringAWait() { + for (int attempt = 0; attempt < 20; ++attempt) { + const int socket = socket_nid_postfix(2, 2, 0); + Require(socket >= 0); + int result = 0; + int error = 0; + std::atomic started{false}; + std::thread waiter([&] { + GuestDescriptorSet read{}; + read.Add(socket); + GuestTimeval limit{2, 0}; + started = true; + result = select_nid_postfix(socket + 1, read.words.data(), nullptr, nullptr, &limit); + error = *__error_nid_postfix(); + }); + while (!started) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::microseconds(1000 + attempt * 100)); + const auto closedAt = std::chrono::steady_clock::now(); + Require(close_nid_postfix(socket) == 0); + waiter.join(); + Require(result == -1 && error == GuestEbadf); + Require(std::chrono::steady_clock::now() - closedAt < std::chrono::milliseconds(60)); + } +} + static void Kqueues() { const int kq = kqueue_nid_postfix(); Require(kq >= 0 && kq < GuestDescriptorLimit); @@ -330,4 +355,5 @@ int main() { Require(select_nid_postfix(reader + 1, read.words.data(), nullptr, nullptr, &limit) == -1); Require(*__error_nid_postfix() == GuestEbadf); } + SocketClosedDuringAWait(); } From db6c0dae18ad249068fb887896a537d3377b6acd Mon Sep 17 00:00:00 2001 From: JJenkx Date: Sun, 1 Nov 2026 17:27:10 -0500 Subject: [PATCH 292/612] fix(libkernel): reject socket readiness after a concurrent close Both waits re-checked the held sockets only before each host wait. A close during the last slice, or one followed by a packet the retained native socket then received, came back as a timeout or as readiness on a descriptor that no longer exists. FreeBSD 11.4's pollrescan reports POLLNVAL alone for such a descriptor. The held sockets are now re-checked after every successful host wait as well, select's socket group fails with EBADF, and poll reports POLLNVAL without ORing in the native socket's readiness. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel/Socket/src/Udp.cpp | 14 ++++++++- core/libs/tests/GuestPoll.cpp | 34 ++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index bed79e7b53..1edae9e902 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -1210,9 +1210,12 @@ int GuestSockets::Poll(KernelSocketPoll::Entry* entries, int count, int timeoutM #else result = ::poll(native.data(), static_cast(native.size()), slice); #endif + if (result < 0) return -NativeError(); + for (std::size_t index = 0; index < held.size(); ++index) { + if (Find(entries[index].descriptor) != held[index]) return -9; + } if (result != 0 || (timeoutMilliseconds >= 0 && std::chrono::steady_clock::now() >= deadline)) break; } - if (result < 0) return -NativeError(); int ready = 0; for (int index = 0; index < count; ++index) { const short events = native[static_cast(index)].revents; @@ -2383,6 +2386,10 @@ static int PollDescriptors(GuestPollDescriptor* descriptors, std::uint32_t count } const auto collect = [&](std::size_t i, bool pipe) { auto& entry = descriptors[owners[i]]; + if (entry.revents == GuestPollInvalid) { + ++ready; + return; + } #if defined(_WIN32) || defined(__APPLE__) if (!pipe) native[i].revents = ClosedStreamEvents(entry.descriptor, native[i].revents, native[i].events); #endif @@ -2475,6 +2482,11 @@ static int PollDescriptors(GuestPollDescriptor* descriptors, std::uint32_t count const auto wake = std::chrono::steady_clock::now() + std::chrono::milliseconds(1); PreciseSleepUntil(timeout < 0 ? wake : std::min(deadline, wake)); } + for (std::size_t i = 0; i < held.size(); ++i) { + if (SocketStillHeld(descriptors[owners[i]].descriptor, held[i].get())) continue; + descriptors[owners[i]].revents = GuestPollInvalid; + closed = true; + } int socketReady = 0; for (const auto& request : native) if (request.revents) ++socketReady; diff --git a/core/libs/tests/GuestPoll.cpp b/core/libs/tests/GuestPoll.cpp index 7020cb88a4..0a2a37708a 100644 --- a/core/libs/tests/GuestPoll.cpp +++ b/core/libs/tests/GuestPoll.cpp @@ -370,6 +370,39 @@ static void PollsASocketClosedDuringTheWait() { } } +static void RejectsReadinessAfterSocketClose() { + for (int attempt = 0; attempt < 20; ++attempt) { + const int receiver = socket_nid_postfix(2, 2, 0); + Require(receiver >= 0); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(receiver, address.data(), address.size()) == 0); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(receiver, address.data(), &size) == 0); + const int alias = dup_nid_postfix(receiver); + Require(alias >= 0); + const NativeSocket sender = ::socket(AF_INET, SOCK_DGRAM, 0); + Require(sender != NativeInvalid); + sockaddr_in destination{}; + destination.sin_family = AF_INET; + std::memcpy(&destination.sin_port, address.data() + 2, 2); + std::memcpy(&destination.sin_addr, address.data() + 4, 4); + GuestPollDescriptor entry{receiver, In, 0}; + std::atomic started{false}; + int result = -1; + std::thread waiter([&] { + started = true; + result = poll_nid_postfix(&entry, 1, 200); + }); + while (!started) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + Require(close_nid_postfix(receiver) == 0); + Require(::sendto(sender, "x", 1, 0, reinterpret_cast(&destination), sizeof(destination)) == 1); + waiter.join(); + CloseNative(sender); + Require(close_nid_postfix(alias) == 0); + Require(result == 1 && entry.revents == Invalid); + } +} int main() { for (const int descriptor : {0, 1, 2}) StandardDescriptorFile(descriptor); PollsUrgentData(); @@ -478,4 +511,5 @@ int main() { PollsKqueues(listener); Require(close_nid_postfix(listener) == 0); PollsASocketClosedDuringTheWait(); + RejectsReadinessAfterSocketClose(); } From b18e89d8934666e37ff8652849ed773b614f551c Mon Sep 17 00:00:00 2001 From: JJenkx Date: Sun, 1 Nov 2026 17:27:10 -0500 Subject: [PATCH 293/612] test(libkernel): bound select's close notice by its median The select close test required every one of its 20 attempts to report EBADF within 60 ms of the close, which a busy runner can miss. It now requires the median attempt to stay under 35 ms: main, which notices the close only at its next 100 ms re-lookup, still fails it, and a few slow attempts no longer do. Co-Authored-By: Claude Opus 5.5 --- core/libs/tests/GuestSelect.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/libs/tests/GuestSelect.cpp b/core/libs/tests/GuestSelect.cpp index 454967da74..8f1a2aecaa 100644 --- a/core/libs/tests/GuestSelect.cpp +++ b/core/libs/tests/GuestSelect.cpp @@ -2,6 +2,7 @@ #include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" +#include #include #include #include @@ -10,6 +11,7 @@ #include #include #include +#include struct GuestTimeval { std::int64_t seconds; @@ -82,6 +84,7 @@ static void StandardDescriptorFile(int descriptor) { } static void SocketClosedDuringAWait() { + std::vector noticed; for (int attempt = 0; attempt < 20; ++attempt) { const int socket = socket_nid_postfix(2, 2, 0); Require(socket >= 0); @@ -101,9 +104,11 @@ static void SocketClosedDuringAWait() { const auto closedAt = std::chrono::steady_clock::now(); Require(close_nid_postfix(socket) == 0); waiter.join(); + noticed.push_back(std::chrono::steady_clock::now() - closedAt); Require(result == -1 && error == GuestEbadf); - Require(std::chrono::steady_clock::now() - closedAt < std::chrono::milliseconds(60)); } + std::sort(noticed.begin(), noticed.end()); + Require(noticed[noticed.size() / 2] < std::chrono::milliseconds(35)); } static void Kqueues() { From dde22044f1652294b24b4040cbf263f2edff0ba3 Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Mon, 2 Nov 2026 23:05:19 +0100 Subject: [PATCH 294/612] docs(libSceBemp2sys): identify the 11.00 module in the debt entry --- docs/dev/debt/libSceBemp2sys.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceBemp2sys.md b/docs/dev/debt/libSceBemp2sys.md index 9a93ffc4d4..2f084e29bf 100644 --- a/docs/dev/debt/libSceBemp2sys.md +++ b/docs/dev/debt/libSceBemp2sys.md @@ -6,7 +6,7 @@ ### Functional -- [bemp2sys_logprintf_getlevel, bemp2sys_logprintf_setfunc, bemp2sys_logprintf_setlevel and bemp2sys_memmanager_set](../../../core/libs/prx/libSceBemp2sys/Export.cpp) - the 11.00 wrappers at `0x5cc0`, `0x5ca0`, `0x5cb0` and `0x5c90` keep a process-global log level, log function pointer and three memory-manager pointers. `setlevel` accepts 0..3 with an unsigned compare, so larger and negative values are ignored; `getlevel` starts at 0. The pointers are stored but have no consumer until the parsers are implemented. The rest of the library keeps throwing. +- [bemp2sys_logprintf_getlevel, bemp2sys_logprintf_setfunc, bemp2sys_logprintf_setlevel and bemp2sys_memmanager_set](../../../core/libs/prx/libSceBemp2sys/Export.cpp) - the wrappers at `0x5cc0`, `0x5ca0`, `0x5cb0` and `0x5c90` in the 11.00 system module `libSceBemp2sys.sprx` (SHA-256 `d66887aa1cfb464aaeaf1abf471c6dbedcf802edb4349fe6ee9d3d3f00936208`) keep a process-global log level, log function pointer and three memory-manager pointers. `setlevel` accepts 0..3 with an unsigned compare, so larger and negative values are ignored; `getlevel` starts at 0. The pointers are stored but have no consumer until the parsers are implemented. The rest of the library keeps throwing. ### Additional callable exports From c7f75100ee516450ff0a1647341c28d89f820abf Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Mon, 2 Nov 2026 23:06:28 +0100 Subject: [PATCH 295/612] docs(libSceAvSetting): identify the 11.00 module in the debt entry --- docs/dev/debt/libSceAvSetting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceAvSetting.md b/docs/dev/debt/libSceAvSetting.md index 7694948a6a..db49e35ae5 100644 --- a/docs/dev/debt/libSceAvSetting.md +++ b/docs/dev/debt/libSceAvSetting.md @@ -91,4 +91,4 @@ ### Functional -- [sceAvSettingSetAudioOutModeAny_, sceAvSettingSetAudioOutModeInvalid_, sceAvSettingSetVideoOutModeAny_, sceAvSettingSetVideoOutModeInvalid_ and sceAvSettingSetVideoOutSource](../../../core/libs/prx/libSceAvSetting/Export.cpp) - the 11.00 wrappers at `0x2d90`, `0x2dc0`, `0x2df0` and `0x2e20` fill the caller buffer with `0xff` (`Any_`) or 0 (`Invalid_`) for the requested byte count and then store the count as the first 32-bit value; a null pointer is ignored. The source setter at `0x17b0` writes its second argument to the caller's 32-bit slot. A negative mode size would make the console memset a huge range, so the host throws for it. The meaning of the buffers and the rest of the IPMI-backed AV client are unmeasured. +- [sceAvSettingSetAudioOutModeAny_, sceAvSettingSetAudioOutModeInvalid_, sceAvSettingSetVideoOutModeAny_, sceAvSettingSetVideoOutModeInvalid_ and sceAvSettingSetVideoOutSource](../../../core/libs/prx/libSceAvSetting/Export.cpp) - the wrappers at `0x2d90`, `0x2dc0`, `0x2df0` and `0x2e20` in the 11.00 system module `libSceAvSetting.sprx` (SHA-256 `97be2b6d5e4c95edb4d4b6e560866e4558970370297d3a5196037866309e8a38`) fill the caller buffer with `0xff` (`Any_`) or 0 (`Invalid_`) for the requested byte count and then store the count as the first 32-bit value; a null pointer is ignored. The source setter at `0x17b0` writes its second argument to the caller's 32-bit slot. A negative mode size would make the console memset a huge range, so the host throws for it. The meaning of the buffers and the rest of the IPMI-backed AV client are unmeasured. From 26781d101688f2a2080dd8a5806943fbd4efc59f Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Mon, 2 Nov 2026 23:11:20 +0100 Subject: [PATCH 296/612] fix(libSceNpUtility): bound request ids and match the abort wrapper --- core/libs/prx/libSceNpUtility/Export.cpp | 7 +++++-- core/libs/tests/GuestNpUtilityOffline.cpp | 12 ++++++++++++ docs/dev/debt/libSceNpUtility.md | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceNpUtility/Export.cpp b/core/libs/prx/libSceNpUtility/Export.cpp index 7d6c626795..68d6533639 100644 --- a/core/libs/prx/libSceNpUtility/Export.cpp +++ b/core/libs/prx/libSceNpUtility/Export.cpp @@ -26,6 +26,7 @@ static constexpr int POLL_FINISHED = 0; static constexpr int POLL_RUNNING = 1; static constexpr std::size_t MAX_TITLE_CONTEXTS = 32; static constexpr std::size_t MAX_REQUESTS = 32; +static constexpr std::uint32_t MAX_SERVICE_CHECKER2_REQUEST_ID = 0x1000; static constexpr int WORD_FILTER_TITLE_CTX_ID_OFFSET = 0x01000000; static constexpr int WORD_FILTER_REQUEST_ID_OFFSET = 0x02000000; static constexpr std::size_t WORD_FILTER_COMMENT_MAX_LENGTH = 0x400; @@ -60,7 +61,7 @@ struct ServiceChecker2 { std::mutex mutex; bool initialized = false; std::unordered_set requests; - int nextRequest = 1; + std::uint32_t nextRequest = 1; }; ServiceChecker2 serviceChecker2; @@ -384,6 +385,7 @@ int APS5_VABI sceNpLookupNetTerm() { int APS5_VABI sceNpServiceChecker2IntAbortRequest(int requestId) { std::lock_guard lock(serviceChecker2.mutex); if (!serviceChecker2.initialized) return SCE_NP_SERVICE_CHECKER2_ERROR_NOT_INITIALIZED; + if (!serviceChecker2.requests.contains(requestId)) return SCE_NP_SERVICE_CHECKER2_ERROR_REQUEST_NOT_FOUND; serviceChecker2.requests.erase(requestId); return 0; } @@ -391,7 +393,8 @@ int APS5_VABI sceNpServiceChecker2IntCreateRequest(int* requestId) { std::lock_guard lock(serviceChecker2.mutex); if (!serviceChecker2.initialized) return SCE_NP_SERVICE_CHECKER2_ERROR_NOT_INITIALIZED; if (!requestId) return SCE_NP_SERVICE_CHECKER2_ERROR_INVALID_ARGUMENT; - const int id = serviceChecker2.nextRequest++; + if (serviceChecker2.nextRequest > MAX_SERVICE_CHECKER2_REQUEST_ID) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; + const int id = static_cast(serviceChecker2.nextRequest++); serviceChecker2.requests.insert(id); *requestId = id; return 0; diff --git a/core/libs/tests/GuestNpUtilityOffline.cpp b/core/libs/tests/GuestNpUtilityOffline.cpp index 2a0cffa199..a79c1f6776 100644 --- a/core/libs/tests/GuestNpUtilityOffline.cpp +++ b/core/libs/tests/GuestNpUtilityOffline.cpp @@ -147,6 +147,8 @@ void CheckServiceChecker2() { Require(sceNpServiceChecker2IntDestroyRequest(second) == 0, "sceNpServiceChecker2IntDestroyRequest", "destroying the request failed"); Require(sceNpServiceChecker2IntGetServiceAvailability(second, selfNpId, &availability) == RequestNotFound, "sceNpServiceChecker2IntDestroyRequest", "a destroyed request was found"); Require(sceNpServiceChecker2IntAbortRequest(request) == 0, "sceNpServiceChecker2IntAbortRequest", "aborting the request failed"); + Require(sceNpServiceChecker2IntAbortRequest(request + 100) == RequestNotFound, "sceNpServiceChecker2IntAbortRequest", "an unknown abort request was accepted"); + Require(sceNpServiceChecker2IntDestroyRequest(request + 100) == 0, "sceNpServiceChecker2IntDestroyRequest", "the destroy wrapper rejected an unknown request"); const std::uint32_t serviceTypes[1] = {0x5}; Require(sceNpServiceChecker2IntIsSetServiceType(serviceTypes, 0) == 1, "sceNpServiceChecker2IntIsSetServiceType", "bit 0 was not set"); Require(sceNpServiceChecker2IntIsSetServiceType(serviceTypes, 1) == 0, "sceNpServiceChecker2IntIsSetServiceType", "bit 1 was set"); @@ -154,6 +156,16 @@ void CheckServiceChecker2() { Require(sceNpServiceChecker2IntIsSetServiceType(serviceTypes, 10) == 0, "sceNpServiceChecker2IntIsSetServiceType", "type 10 was taken"); Require(sceNpServiceChecker2IntIsSetServiceType(nullptr, 0) == 0, "sceNpServiceChecker2IntIsSetServiceType", "a null mask was taken"); Require(sceNpServiceChecker2IntFinalize() == 0, "sceNpServiceChecker2IntFinalize", "finalization failed"); + Require(sceNpServiceChecker2IntInitialize() == 0, "sceNpServiceChecker2IntInitialize", "reinitialization failed"); + int boundaryId = 0; + for (std::uint32_t index = 0; index < 0x1000; ++index) { + Require(sceNpServiceChecker2IntCreateRequest(&boundaryId) == 0, "sceNpServiceChecker2IntCreateRequest", "a request below the host limit was refused"); + } + const int lastId = boundaryId; + Require(sceNpServiceChecker2IntCreateRequest(&boundaryId) == TooManyObjects, "sceNpServiceChecker2IntCreateRequest", "the host request limit was not enforced"); + Require(boundaryId == lastId, "sceNpServiceChecker2IntCreateRequest", "the request output changed on refusal"); + Require(sceNpServiceChecker2IntGetServiceAvailability(lastId, selfNpId, &availability) == SignedOut, "sceNpServiceChecker2IntGetServiceAvailability", "an existing request was lost at the host limit"); + Require(sceNpServiceChecker2IntFinalize() == 0, "sceNpServiceChecker2IntFinalize", "finalization after the host limit failed"); Require(sceNpServiceChecker2IntCreateRequest(&request) == NotInitialized, "sceNpServiceChecker2IntCreateRequest", "a request was created after finalization"); } diff --git a/docs/dev/debt/libSceNpUtility.md b/docs/dev/debt/libSceNpUtility.md index ab3d26649a..dda2dabb3b 100644 --- a/docs/dev/debt/libSceNpUtility.md +++ b/docs/dev/debt/libSceNpUtility.md @@ -18,7 +18,7 @@ ### Functional -- [sceNpServiceChecker2IntAbortRequest, sceNpServiceChecker2IntCreateRequest, sceNpServiceChecker2IntDestroyRequest, sceNpServiceChecker2IntFinalize, sceNpServiceChecker2IntGetServiceAvailability, sceNpServiceChecker2IntGetServiceAvailabilityA, sceNpServiceChecker2IntInitialize and sceNpServiceChecker2IntIsSetServiceType](../../../core/libs/prx/libSceNpUtility/Export.cpp) - signatures and errors from the 11.00 wrappers: Initialize/Finalize hold one host state, CreateRequest writes request ids from 1, Destroy/Abort remove them, and the two availability calls return SIGNED_OUT without writing because no network is available; `IsSetServiceType` reads bit 0..9 of a 32-bit mask. The console's availability payload, its exact offline error and any request limit are unmeasured. +- [sceNpServiceChecker2IntAbortRequest, sceNpServiceChecker2IntCreateRequest, sceNpServiceChecker2IntDestroyRequest, sceNpServiceChecker2IntFinalize, sceNpServiceChecker2IntGetServiceAvailability, sceNpServiceChecker2IntGetServiceAvailabilityA, sceNpServiceChecker2IntInitialize and sceNpServiceChecker2IntIsSetServiceType](../../../core/libs/prx/libSceNpUtility/Export.cpp) - signatures and errors from the wrappers at `0x14670`, `0x146e0`, `0x14710`, `0x14780`, `0x147a0`, `0x14840`, `0x148e0` and `0x148f0` in the 11.00 system module `libSceNpUtility.sprx` (SHA-256 `1c90050abb737d3290539f46928fb536faca8773e335523479f06f4ae33f0c60`): Initialize/Finalize hold one host state, CreateRequest writes request ids from 1, Destroy/Abort remove them, and the two availability calls return SIGNED_OUT without writing because no network is available; `IsSetServiceType` reads bit 0..9 of a 32-bit mask. The wrapper immediates establish `0x8055B301`, `0x8055B302`, `0x8055B304` and `0x8055B305`; DestroyRequest ignores an unknown id and returns 0, while AbortRequest and the two availability calls return `0x8055B304` for it. The host refuses a new request once the monotonically increasing id would pass `0x1000`, returning `0x80550706` without changing the output or the existing requests; that cap is a host safety limit, not the console's unmeasured request limit. The console's availability payload, its exact offline error and any request limit are unmeasured. - [sceNpBandwidthTestInitStart, sceNpBandwidthTestShutdownWithDetailedInfo](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - offline answers, not measured on a PS5: InitStart returns `SCE_NP_ERROR_SIGNED_OUT` as `sceNpBandwidthTestInitStartDownload` and `InitStartUpload` do, so no test is ever started, and ShutdownWithDetailedInfo returns `SCE_NP_ERROR_INVALID_ARGUMENT` and writes nothing, as `sceNpBandwidthTestShutdown` does. Neither reads an argument, so their parameterless declarations stand; their prototypes are still unknown - [NpLookup and NpWordFilter](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - offline only, not measured on a PS5: the argument checks and their order, the limit of 32 title contexts and 32 requests, the word filter id bases `0x01000001` and `0x02000001`, the 0x400-byte comment limit and single-use requests (`INVALID_TYPE`) follow shadPS4's [lookup](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_utility/np_utility.cpp#L41-L222) and [word filter](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_utility/np_utility.cpp#L705-L909) calls. `sceNpLookupNpId`, `sceNpWordFilterCensorComment` and `sceNpWordFilterSanitizeComment` then finish with `SCE_NP_ERROR_SIGNED_OUT` and write no NpId or comment; shadPS4's word filter accepts every comment as a stand-in for the server, so its result is not used From 79d6d1af7036d17c6f51ad7158fb7d9ea2113565 Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Mon, 2 Nov 2026 23:15:24 +0100 Subject: [PATCH 297/612] fix(libSceContentSearch): keep the unmeasured index query throwing --- core/libs/prx/libSceContentSearch/Export.cpp | 1 + core/libs/tests/GuestContentSearch.cpp | 2 +- docs/dev/debt/libSceContentSearch.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceContentSearch/Export.cpp b/core/libs/prx/libSceContentSearch/Export.cpp index 72a644817c..9e0c985a9f 100644 --- a/core/libs/prx/libSceContentSearch/Export.cpp +++ b/core/libs/prx/libSceContentSearch/Export.cpp @@ -200,6 +200,7 @@ int APS5_VABI sceContentSearchGetMyApplicationIndex(std::int32_t* index) { if (!g_initialized.load()) return ErrorNotInitialized; if (index == nullptr) return ErrorInvalidArgument; *index = 0; + NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceContentSearchGetNumOfApplication(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, const ContentSearchContentOrderByCondition* orderByConditions, std::uint32_t orderByConditionsLength, std::int64_t* numOfApplication, std::int64_t* lastUpdateId) { diff --git a/core/libs/tests/GuestContentSearch.cpp b/core/libs/tests/GuestContentSearch.cpp index 76c6660d85..064029844e 100644 --- a/core/libs/tests/GuestContentSearch.cpp +++ b/core/libs/tests/GuestContentSearch.cpp @@ -99,7 +99,7 @@ int main() { updateId = -1; Require(sceContentSearchGetApplicationLastUpdateId(&updateId) == 0 && updateId == 0); applicationIndex = -1; - Require(sceContentSearchGetMyApplicationIndex(&applicationIndex) == 0 && applicationIndex == 0); + Require(ThrowsRuntimeError([&] { sceContentSearchGetMyApplicationIndex(&applicationIndex); }) && applicationIndex == 0); count = -1; updateId = -1; Require(sceContentSearchGetNumOfApplication(&columnSet, 1, &orderBy, 1, &count, &updateId) == 0); diff --git a/docs/dev/debt/libSceContentSearch.md b/docs/dev/debt/libSceContentSearch.md index 7ee5185575..627bdcd5d1 100644 --- a/docs/dev/debt/libSceContentSearch.md +++ b/docs/dev/debt/libSceContentSearch.md @@ -16,7 +16,7 @@ ### Functional - [Content queries](../../../core/libs/prx/libSceContentSearch/Export.cpp) use an empty host library and always report update id 0; the console's initial update id is unverified. No content rows are produced. Every title is assumed to have search permission. Nested filter/sort validation is incomplete: empty nested column sets, null filter values and unknown enum values throw. Guest heap allocation and the platform-dependent minimum heap size are unimplemented. Initialization and outer pointer/count/limit validation follow the supplied module. -- [sceContentSearchGetNumOfApplication, sceContentSearchGetApplicationLastUpdateId and sceContentSearchGetMyApplicationIndex](../../../core/libs/prx/libSceContentSearch/Export.cpp) (libSceContentSearch) - signatures from the 11.00 module wrapper: `GetNumOfApplication` takes the column-set and order-by pairs plus the count and update-id outputs; `GetApplicationLastUpdateId` writes a 64-bit update id; `GetMyApplicationIndex` writes a 32-bit index, defaulting it to 0 before delegating. The host reports zero applications and update id 0, and index 0, because the host library has no application records; the console values are unverified. All three check initialization and null outputs first, and `GetNumOfApplication` validates the column-set and order-by contents as the content queries do. `sceContentSearchSearchApplication` still throws because its eleven-argument contract is not mapped. +- [sceContentSearchGetNumOfApplication, sceContentSearchGetApplicationLastUpdateId and sceContentSearchGetMyApplicationIndex](../../../core/libs/prx/libSceContentSearch/Export.cpp) (libSceContentSearch) - signatures from the 11.00 module wrappers at `0x18e70`, `0x18f70` and `0x191a0` in `libSceContentSearch.sprx` (SHA-256 `af44252b7b9a0c2d83d01197a4d38119375fb14b82c7e2a822187a49432b42e4`): `GetNumOfApplication` takes the column-set and order-by pairs plus the count and update-id outputs; `GetApplicationLastUpdateId` writes a 64-bit update id. The host reports zero applications and update id 0 because the host library has no application records; the console values are unverified. `GetMyApplicationIndex` writes a 32-bit index, defaults it to 0 before delegating, and returns the delegate's result, which is not established when the title is absent from the library, so the host writes 0 and then throws through `NotImplemented_nid_no_patch`. All three check initialization and null outputs first, and `GetNumOfApplication` validates the column-set and order-by contents as the content queries do. `sceContentSearchSearchApplication` still throws because its eleven-argument contract is not mapped. - [Metadata access](../../../core/libs/prx/libSceContentSearch/Export.cpp) cannot open metadata by path or content id because missing-content errors remain unresolved: past their argument checks, both opening calls go through `NotImplemented_nid_no_patch`. No metadata handles are issued. The field-info and value queries reject invalid outer pointers, then go through `NotImplemented_nid_no_patch`, as no metadata id is ever opened and the console's error for one depends on the requested field; closing an unopened handle returns the confirmed invalid-argument error. ### Additional callable exports From 1b68aaf708d587bc92960314cafdffc2a8c3090e Mon Sep 17 00:00:00 2001 From: slopgobblercobbler Date: Sun, 1 Nov 2026 18:38:27 +0100 Subject: [PATCH 298/612] test(libSceAudioOut): cover invalid sample rate in sceAudioOutOpen --- core/libs/prx/libSceAudioOut/CMakeLists.txt | 2 +- core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index 48000b0f1d..3aeb8b1274 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -101,7 +101,7 @@ if(BUILD_TESTING) target_include_directories(audio_out_buffer_size_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) target_link_libraries(audio_out_buffer_size_tests PRIVATE libc libkernel libScePad) configure_windows_unwind(audio_out_buffer_size_tests) - foreach(bufferSizeMode IN ITEMS pass_through wrap mix threshold virtual_overflow invalid_format) + foreach(bufferSizeMode IN ITEMS pass_through wrap mix threshold virtual_overflow invalid_format invalid_sample_rate) set(bufferSizeTest "audio_out_buffer_size_${bufferSizeMode}") add_test(NAME ${bufferSizeTest} COMMAND audio_out_buffer_size_tests ${bufferSizeMode}) set_tests_properties(${bufferSizeTest} PROPERTIES TIMEOUT 5) diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp index f0bc493e73..1ad484415d 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp @@ -85,6 +85,12 @@ void RunInvalidFormat() { Require(sceAudioOutOpen(0, 0, 0, 512, 48000, 0xFF) == expectedError, "format 0xFF did not return SCE_AUDIO_OUT_ERROR_INVALID_FORMAT"); Require(opened == 0, "an invalid format opened an audio device"); } + +void RunInvalidSampleRate() { + constexpr int expectedError = -2144993272; + Require(sceAudioOutOpen(0, 0, 0, 512, 0, 0) == expectedError, "sample rate 0 did not return SCE_AUDIO_OUT_ERROR_INVALID_PARAM"); + Require(opened == 0, "a zero sample rate opened an audio device"); +} } int InitializeSdlAudio_nid_no_patch() { return 0; } @@ -125,6 +131,7 @@ int main(int argc, char** argv) { else if (mode == "threshold") RunOverflow(0x40000000u, 0, sizeof(std::int16_t)); else if (mode == "virtual_overflow") RunVirtualOverflow(); else if (mode == "invalid_format") RunInvalidFormat(); + else if (mode == "invalid_sample_rate") RunInvalidSampleRate(); else Require(false, "invalid buffer size mode"); std::puts("audio out buffer size tests passed"); return 0; From a40918806e7b8dfe399e40cb0e645ba941cbff2a Mon Sep 17 00:00:00 2001 From: Omar Elmahy Date: Mon, 2 Nov 2026 23:58:08 +0000 Subject: [PATCH 299/612] feat(libSceSysUtil): add CMakeLists.txt and test notification delivery --- core/libs/CMakeLists.txt | 10 ---------- core/libs/prx/libSceSysUtil/CMakeLists.txt | 15 +++++++++++++++ core/libs/tests/GuestSysUtilNotification.cpp | 17 ++++++++++++++++- docs/dev/debt/libSceSysUtil.md | 2 +- 4 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 core/libs/prx/libSceSysUtil/CMakeLists.txt diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 5a0c8465fa..d4032e8f00 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -1024,16 +1024,6 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() - add_test_executable(guest_sys_util_notification_tests tests/GuestSysUtilNotification.cpp) - target_include_directories(guest_sys_util_notification_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_link_libraries(guest_sys_util_notification_tests PRIVATE libSceSysUtil libkernel libc) - configure_windows_unwind(guest_sys_util_notification_tests) - add_test(NAME guest_sys_util_notification COMMAND guest_sys_util_notification_tests) - if(WIN32) - set_tests_properties(guest_sys_util_notification PROPERTIES ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") - endif() - if(UNIX AND (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$" OR CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64")) add_subdirectory(tests/register_context) endif() diff --git a/core/libs/prx/libSceSysUtil/CMakeLists.txt b/core/libs/prx/libSceSysUtil/CMakeLists.txt new file mode 100644 index 0000000000..dd8bcbf9d6 --- /dev/null +++ b/core/libs/prx/libSceSysUtil/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(libSceSysUtil SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceSysUtil PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceSysUtil) + +if(BUILD_TESTING) + add_test_executable(guest_sys_util_notification_tests ${LIBS_INCLUDE_DIR}/tests/GuestSysUtilNotification.cpp) + target_include_directories(guest_sys_util_notification_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_sys_util_notification_tests PRIVATE libSceSysUtil libkernel libc) + configure_windows_unwind(guest_sys_util_notification_tests) + add_test(NAME guest_sys_util_notification COMMAND guest_sys_util_notification_tests) + if(WIN32) + set_tests_properties(guest_sys_util_notification PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/tests/GuestSysUtilNotification.cpp b/core/libs/tests/GuestSysUtilNotification.cpp index 37afa71a77..0bd4886bb3 100644 --- a/core/libs/tests/GuestSysUtilNotification.cpp +++ b/core/libs/tests/GuestSysUtilNotification.cpp @@ -74,12 +74,18 @@ int main() { int hello = -1; int empty = -1; + int emptyString = -1; + int type1 = -1; + int type2 = -1; int truncated = -1; std::string output; try { StandardErrorCapture capture; hello = sceSysUtilSendSystemNotificationWithText(7, "hello"); empty = sceSysUtilSendSystemNotificationWithText(-1, nullptr); + emptyString = sceSysUtilSendSystemNotificationWithText(0, ""); + type1 = sceSysUtilSendSystemNotificationWithText(1, "notification type 1"); + type2 = sceSysUtilSendSystemNotificationWithText(2, "notification type 2"); truncated = sceSysUtilSendSystemNotificationWithText(0, longText.c_str()); output = capture.Text(); } catch (const std::exception& error) { @@ -89,10 +95,19 @@ int main() { Require(hello == 0, "a text notification was not sent"); Require(empty == 0, "a null message was not sent as an empty text"); + Require(emptyString == 0, "an empty string notification was not sent"); + Require(type1 == 0, "a type 1 notification was not sent"); + Require(type2 == 0, "a type 2 notification was not sent"); Require(truncated == 0, "a long message was not sent"); Require(sceSysUtilSendSystemNotificationWithText(0, reinterpret_cast(0x10)) == static_cast(0x8002000Eu), "an unreadable message pointer did not return EFAULT"); Require(sceSysUtilSendSystemNotificationWithText(0, reinterpret_cast(~std::uintptr_t{0xfff})) == static_cast(0x8002000Eu), "an unreadable message pointer did not return EFAULT"); - const std::string expected = "[notification] hello\n[notification] \n[notification] " + longText.substr(0, 179) + "\n"; + const std::string expected = + "[notification] hello\n" + "[notification] \n" + "[notification] \n" + "[notification] notification type 1\n" + "[notification] notification type 2\n" + "[notification] " + longText.substr(0, 179) + "\n"; if (output != expected) { std::fprintf(stderr, "notification output was:\n%s", output.c_str()); Require(false, "the notification output differs from the sent texts"); diff --git a/docs/dev/debt/libSceSysUtil.md b/docs/dev/debt/libSceSysUtil.md index dd8080ae42..e23c4d7943 100644 --- a/docs/dev/debt/libSceSysUtil.md +++ b/docs/dev/debt/libSceSysUtil.md @@ -9,4 +9,4 @@ ### Functional -- [sceSysUtilSendSystemNotificationWithText](../../../core/libs/prx/libSceSysUtil/Export.cpp) (libSceSysUtil) - follows @BunnyFl's [firmware 13.42 run](https://github.com/boykopovar/AnyPS5/pull/9081#issuecomment-6418015178) under an emulator with `sceKernelSendNotificationRequest` stubbed, not on console. Builds a 0xc30-byte request with kind 5 in the first dword, `type` at +0xc and the message at +0x2d, cut to 179 characters and empty for null, returning what `sceKernelSendNotificationRequest(0, request, 0xc30, 0)` returns. An unreadable message pointer returns `SCE_KERNEL_ERROR_EFAULT` (`0x8002000e`) as a host-safety fallback; console invalid-pointer behaviour was not measured. Other request bytes were not reported and are zero. Lacking system UI, text goes to standard error; console display per `type` is unverified. +- [sceSysUtilSendSystemNotificationWithText](../../../core/libs/prx/libSceSysUtil/Export.cpp) (libSceSysUtil) - follows @BunnyFl's [firmware 13.42 run](https://github.com/boykopovar/AnyPS5/pull/9081#issuecomment-6418015178) under an emulator with `sceKernelSendNotificationRequest` stubbed, not on console. Builds a 0xc30-byte request with kind 5 in the first dword, `type` at +0xc and the message at +0x2d, cut to 179 characters and empty for null, returning what `sceKernelSendNotificationRequest(0, request, 0xc30, 0)` returns. Unit tests cover notification delivery across types (-1, 0, 1, 2, 7) and valid, empty, null, or unreadable message text. An unreadable message pointer returns `SCE_KERNEL_ERROR_EFAULT` (`0x8002000e`) as a host-safety fallback; console invalid-pointer behaviour was not measured. Other request bytes were not reported and are zero. Lacking system UI, text goes to standard error; console display per `type` is unverified. From 292a50a94409762a21091066657c57f048df8702 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Mon, 2 Nov 2026 20:29:28 -0500 Subject: [PATCH 300/612] docs(libc): keep the fdlibm notice of the atanh port atanh carries over FreeBSD msun's lib/msun/src/e_atanh.c at release/11.4.0. Name that source above atanh_nid_postfix, in the form the exp port uses, followed by the file's Sun copyright and permission notice copied verbatim (its trailing space included, as main keeps it for the other SunSoft notices). No code changes. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Math.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index d94c23e598..7b8507f364 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -1535,6 +1535,18 @@ float APS5_VABI acoshf_nid_postfix(float x) { const float t = x - 1.0f; return AtanhfLog1p(t + std::sqrt(2.0f * t + t * t)); } +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_atanh.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_atanh.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ double APS5_VABI atanh_nid_postfix(double x) { const double huge = 1e300; static volatile double zero = 0.0; From 2c7a7026f78c81bd5a65de46d8e0ba2a862595cf Mon Sep 17 00:00:00 2001 From: JJenkx Date: Mon, 2 Nov 2026 20:31:18 -0500 Subject: [PATCH 301/612] docs(libc): keep the source notice of the hypotf port hypotf carries over FreeBSD msun's lib/msun/src/e_hypotf.c at release/11.4.0. Name that source above the port, in the form the exp port uses, followed by the file's Sun copyright and permission notice copied verbatim from that revision. No code changes. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Math.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 40a173375c..540b8fbb2f 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -489,6 +489,17 @@ float APS5_VABI acosf_nid_postfix(float x) { const float correction = (z - high * high) / (s + high); return 2.0f * (high + (AcosfRational(z) * s + correction)); } +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_hypotf.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_hypotf.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunPro, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ float APS5_VABI hypotf_nid_postfix(float x, float y) { const auto word = [](float value) { return static_cast(std::bit_cast(value)); }; const auto fromWord = [](std::int32_t value) { return std::bit_cast(static_cast(value)); }; From 05928fc82acf8929de8f11ef3a41adb00d65a6b0 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Mon, 2 Nov 2026 20:31:40 -0500 Subject: [PATCH 302/612] docs(libc): keep the source notice of the log port log carries over FreeBSD msun's lib/msun/src/e_log.c at release/11.4.0. Name that source above the port, in the form the exp port uses, followed by the file's Sun copyright and permission notice copied verbatim from that revision. No code changes. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Math.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index e99c248107..6c01d6319f 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -809,6 +809,17 @@ double APS5_VABI atan_nid_postfix(double x) { return hx < 0 ? -z : z; } double APS5_VABI tan_nid_postfix(double x) { return std::tan(x); } +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_log.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_log.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ double APS5_VABI log_nid_postfix(double x) { const double ln2_hi = 6.93147180369123816490e-01; const double ln2_lo = 1.90821492927058770002e-10; From ef74cb216e42d1e46cb87814f1a48f458158a8fb Mon Sep 17 00:00:00 2001 From: JJenkx Date: Mon, 2 Nov 2026 20:32:49 -0500 Subject: [PATCH 303/612] docs(libc): keep the source notice of fmod's special-value filter fmod's special-value filter (the bit tests and (x * y) / (x * y)) comes from FreeBSD msun's lib/msun/src/e_fmod.c at release/11.4.0. Name that source above fmod_nid_postfix, in the form the exp port uses, followed by the file's Sun copyright and permission notice copied verbatim from that revision. fmodf's source, e_fmodf.c, is already named in this file. No code changes. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Math.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index b8fdb1f785..7e18ec51e8 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -2113,6 +2113,17 @@ float APS5_VABI ldexpf_nid_postfix(float x, int exp) { errno = saved_errno; return result; } +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_fmod.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_fmod.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ double APS5_VABI fmod_nid_postfix(double x, double y) { const std::uint64_t ix = std::bit_cast(x) & 0x7fffffffffffffffull; const std::uint64_t iy = std::bit_cast(y) & 0x7fffffffffffffffull; From 3c88fb4e8ca8849745fc4fef520bd7684ce204db Mon Sep 17 00:00:00 2001 From: JJenkx Date: Mon, 2 Nov 2026 20:34:06 -0500 Subject: [PATCH 304/612] docs(libc): keep the source notices of the sin, cos and tan port sin, cos and tan carry over FreeBSD msun's e_rem_pio2.c, k_sin.c, k_cos.c, k_tan.c, s_sin.c, s_cos.c and s_tan.c at release/11.4.0. Name each source in the form the exp port uses, followed by that file's copyright and permission notice copied verbatim from that revision. Each source's line and notice sit above the first function taken from it (RemPio2Near, KernelSin, KernelCos, KernelTan, sin, cos, tan); k_rem_pio2.c was already named in this file. No code changes. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Math.cpp | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 5c886e4eba..de8fd4f8d8 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -1382,6 +1382,19 @@ static double ClearLowWord(double x) { return std::bit_cast(std::bit_cast(x) & 0xffffffff00000000ull); } +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_rem_pio2.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_rem_pio2.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + * Optimized by Bruce D. Evans. + */ static int RemPio2Near(double x, double* y, std::int32_t hx, int k) { const double pio2_1 = 1.57079632673412561417e+00; const double pio2_1t = 6.07710050650619224932e-11; @@ -1478,6 +1491,17 @@ static int RemPio2(double x, double* y) { return n; } +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/k_sin.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/k_sin.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ static double KernelSin(double x, double y, int iy) { const double S1 = -1.66666666666666324348e-01; const double S2 = 8.33333333332248946124e-03; @@ -1495,6 +1519,17 @@ static double KernelSin(double x, double y, int iy) { return x - ((z * (0.5 * y - v * r) - y) - v * S1); } +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/k_cos.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/k_cos.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ static double KernelCos(double x, double y) { const double C1 = 4.16666666666666019037e-02; const double C2 = -1.38888888888741095749e-03; @@ -1510,6 +1545,16 @@ static double KernelCos(double x, double y) { return w + (((1.0 - w) - hz) + (z * r - x * y)); } +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/k_tan.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/k_tan.c */ +/* + * ==================================================== + * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved. + * + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ static double KernelTan(double x, double y, int iy) { static constexpr double T[] = { 3.33333333333334091986e-01, 1.33333333333201242699e-01, 5.39682539762260521377e-02, 2.18694882948595424599e-02, @@ -1554,6 +1599,17 @@ static double KernelTan(double x, double y, int iy) { return t + a * (s + t * v); } +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/s_sin.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_sin.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunPro, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ double APS5_VABI sin_nid_postfix(double x) { const std::int32_t ix = HighWord(x) & 0x7fffffff; if (ix <= 0x3fe921fb) { @@ -1577,6 +1633,17 @@ double APS5_VABI sin_nid_postfix(double x) { } } +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/s_cos.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_cos.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunPro, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ double APS5_VABI cos_nid_postfix(double x) { const std::int32_t ix = HighWord(x) & 0x7fffffff; if (ix <= 0x3fe921fb) { @@ -1648,6 +1715,17 @@ static double SinhLdexpExpHalf(double x) { */ /* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_sinh.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_sinh.c */ +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/s_tan.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_tan.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunPro, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ double APS5_VABI tan_nid_postfix(double x) { const std::int32_t ix = HighWord(x) & 0x7fffffff; if (ix <= 0x3fe921fb) { From 4b26640f77bf326befabf233a69b241e55234dfb Mon Sep 17 00:00:00 2001 From: blu <131330772+ycbuzi@users.noreply.github.com> Date: Mon, 2 Nov 2026 18:52:50 -0700 Subject: [PATCH 305/612] docs(usage): say how a first run waits for a very large compute pipeline Co-Authored-By: Claude Opus 5.5 --- docs/user/USAGE.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/user/USAGE.md b/docs/user/USAGE.md index a7c6e808b8..92fa6a999b 100644 --- a/docs/user/USAGE.md +++ b/docs/user/USAGE.md @@ -457,6 +457,8 @@ HDR output is available on Windows only. Titles see it when the monitor showing Recompiled shaders and the Vulkan pipeline cache are kept in a `shader_cache/` directory beside the output executable, so later runs skip recompilation. Set `ANYPS5_SHADER_CACHE_DIR` to use another directory, or set `ANYPS5_NO_SHADER_CACHE` to any non-empty value other than `0` to disable both caches. Shaders are stored in a subdirectory named after the recompiler's source version; subdirectories of other versions unused for 14 days are removed the first time a run looks up a shader. The pipeline cache is one `vk-pipelines-*.bin` file per GPU and driver version in the same subdirectory, so it goes with the shaders it was built from. Entries of the current version are never removed and the directory has no size limit, so a title that compiles many shaders can add several GB within minutes. Deleting the directory is safe: it is rebuilt on the next run. +A first run can stop at one very large compute shader while the GPU driver compiles its pipeline: no new frames are shown and the driver's compiler keeps several CPU threads busy, which can last from minutes to hours for shaders of several million SPIR-V words. With `APS5_PROFILE_DRAW=1` the log names such a compile before it starts (`[dispatch] creating a pipeline for N SPIR-V words`, printed above 100000 words) and its time when it ends (`[dispatch] created a pipeline for N SPIR-V words in N ms`). The title's committed memory can keep growing during the compile, so the system commit limit (see Runtime layout) needs room for it; on Windows, enlarge the page file before such a run. The finished pipeline is kept in the driver's pipeline cache and in the pipeline journal, so later runs on the same GPU and driver do not compile it again; a driver update or another GPU starts over. + ### Pipeline journal Besides the recompiled shaders, the shader cache directory holds `pipelines.journal`: every Vulkan graphics pipeline the pipeline store kept and every compute pipeline a run built, with the specialized shader modules they were built from. The next run of the title builds those pipelines on background threads while it loads, so a draw or dispatch the earlier run already made does not compile its pipeline when it is first reached. The journal belongs to the GPU and driver that wrote it, like the driver's pipeline cache file next to it: a run on another GPU or driver version starts it over, and the driver's own pipeline cache decides how fast each pipeline is rebuilt. Two runs sharing the directory take turns on `pipelines.journal.lock`; a run that cannot lock it records nothing. `APS5_NO_PIPELINE_JOURNAL=1` disables recording and replay; `APS5_PIPELINE_JOURNAL_THREADS` sets the replay threads (half the CPU threads by default; `0` records without replaying), `APS5_PIPELINE_JOURNAL_ENTRIES` how many pipelines are replayed (4096, the last recorded) and `APS5_PIPELINE_STORE_ENTRIES` how many graphics pipelines stay resident (by default the store grows from 1024 up to 4096 to hold the replay and the pipelines that keep coming back), and `APS5_PIPELINE_JOURNAL_MAX_MIB` (1024) the file size at which recording stops. Deleting the file loses nothing but the head start. With `APS5_PROFILE_DRAW=1` a `[pipeline-journal]` line reports what was loaded, replayed and recorded. From 7cc41503105a951babadef6aeb51d6cd41f96512 Mon Sep 17 00:00:00 2001 From: blu <131330772+ycbuzi@users.noreply.github.com> Date: Mon, 2 Nov 2026 19:26:43 -0700 Subject: [PATCH 306/612] docs(usage): say the pipeline cache and journal can avoid the wait, not that they always do Co-Authored-By: Claude Opus 5.5 --- docs/user/USAGE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/USAGE.md b/docs/user/USAGE.md index 92fa6a999b..fd2430d5f9 100644 --- a/docs/user/USAGE.md +++ b/docs/user/USAGE.md @@ -457,7 +457,7 @@ HDR output is available on Windows only. Titles see it when the monitor showing Recompiled shaders and the Vulkan pipeline cache are kept in a `shader_cache/` directory beside the output executable, so later runs skip recompilation. Set `ANYPS5_SHADER_CACHE_DIR` to use another directory, or set `ANYPS5_NO_SHADER_CACHE` to any non-empty value other than `0` to disable both caches. Shaders are stored in a subdirectory named after the recompiler's source version; subdirectories of other versions unused for 14 days are removed the first time a run looks up a shader. The pipeline cache is one `vk-pipelines-*.bin` file per GPU and driver version in the same subdirectory, so it goes with the shaders it was built from. Entries of the current version are never removed and the directory has no size limit, so a title that compiles many shaders can add several GB within minutes. Deleting the directory is safe: it is rebuilt on the next run. -A first run can stop at one very large compute shader while the GPU driver compiles its pipeline: no new frames are shown and the driver's compiler keeps several CPU threads busy, which can last from minutes to hours for shaders of several million SPIR-V words. With `APS5_PROFILE_DRAW=1` the log names such a compile before it starts (`[dispatch] creating a pipeline for N SPIR-V words`, printed above 100000 words) and its time when it ends (`[dispatch] created a pipeline for N SPIR-V words in N ms`). The title's committed memory can keep growing during the compile, so the system commit limit (see Runtime layout) needs room for it; on Windows, enlarge the page file before such a run. The finished pipeline is kept in the driver's pipeline cache and in the pipeline journal, so later runs on the same GPU and driver do not compile it again; a driver update or another GPU starts over. +A first run can stop at one very large compute shader while the GPU driver compiles its pipeline: no new frames are shown and the driver's compiler keeps several CPU threads busy, which can last from minutes to hours for shaders of several million SPIR-V words. With `APS5_PROFILE_DRAW=1` the log names such a compile before it starts (`[dispatch] creating a pipeline for N SPIR-V words`, printed above 100000 words) and its time when it ends (`[dispatch] created a pipeline for N SPIR-V words in N ms`). The title's committed memory can keep growing during the compile, so the system commit limit (see Runtime layout) needs room for it; on Windows, enlarge the page file before such a run. Later runs can reuse the driver's pipeline cache, and the pipeline journal rebuilds recorded pipelines on background threads, which can avoid the wait when they are first reached; neither guarantees the driver skips the compile, and a driver update or another GPU starts a separate cache. ### Pipeline journal From 9c10e720457503804f378bb785ef3f23202a516d Mon Sep 17 00:00:00 2001 From: slopgobblercobbler Date: Sun, 1 Nov 2026 16:22:35 +0100 Subject: [PATCH 307/612] test(relinker): cover zero length and section overflow in eh_frame FDE record --- .../relinker/tests/UnwindRecordBoundsTests.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp b/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp index dd3ab410de..d536e627e4 100644 --- a/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp +++ b/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp @@ -387,6 +387,20 @@ int main() { unterminatedRecordRejected = std::string(error.what()).find("CIE fields exceed record") != std::string::npos; } Require(unterminatedRecordRejected); - std::cout << "33 unwind record cases passed\n"; + auto fdeZeroBytes = Fixture(); + Io::WriteU32(fdeZeroBytes, 96, 0); + bool fdeZeroRejected = false; + try { Relinker::UnusedNidFilter::ReadExceptionFunctions(fdeZeroBytes, {augLoad, augEh}, {}, {}); } + catch (const Domain::RelinkerException& error) { + fdeZeroRejected = std::string(error.what()).find("unsupported FDE record size") != std::string::npos; + } + Require(fdeZeroRejected); + auto fdeOverflowBytes = Fixture(); + Io::WriteU32(fdeOverflowBytes, 96, 1000); + bool fdeOverflowRejected = false; + try { Relinker::UnusedNidFilter::ReadExceptionFunctions(fdeOverflowBytes, {augLoad, augEh}, {}, {}); } + catch (const std::exception&) { fdeOverflowRejected = true; } + Require(fdeOverflowRejected); + std::cout << "35 unwind record cases passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } } From 6cf35fccdcb7826030c4bd95e2a8444418a1e24f Mon Sep 17 00:00:00 2001 From: Noxy-HNR Date: Tue, 3 Nov 2026 05:07:10 +0000 Subject: [PATCH 308/612] fix(libSceNKWebKitRequirements): record SQLite files for sceKernelSync and reject file URIs A read-write sqlite3_open_v2 records the database and its -wal, -shm and -journal paths so sceKernelSync flushes them. File URIs other than in-memory ones now throw instead of reaching the host path unmapped. The release archive carries SQLite's licence, and the debt file no longer lists SQLite among the sources of the remaining names. The test also checks that a rejected collation registration runs no destructor. --- .../libSceNKWebKitRequirements/src/Sqlite.cpp | 23 +++++++++++++++---- core/libs/tests/GuestNKWebKitSqlite.cpp | 18 +++++++++++++++ docs/dev/debt/libSceNKWebKitRequirements.md | 4 ++-- tools/package_release.py | 2 ++ 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp b/core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp index e9f89eb127..6af76c2bef 100644 --- a/core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp +++ b/core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -287,13 +288,25 @@ sqlite3_stmt* APS5_VABI sqlite3_next_stmt_nid_postfix(sqlite3* db, sqlite3_stmt* int APS5_VABI sqlite3_open_v2_nid_postfix(const char* filename, sqlite3** db, int flags, const char* vfs) { static_cast(vfs); - const bool unmapped = !filename || !*filename || std::strcmp(filename, ":memory:") == 0 || - (flags & SQLITE_OPEN_MEMORY) || ((flags & SQLITE_OPEN_URI) && std::strncmp(filename, "file:", 5) == 0); - if (unmapped) { + const bool uri = filename && (flags & SQLITE_OPEN_URI) && std::strncmp(filename, "file:", 5) == 0; + const bool memory = !filename || !*filename || std::strcmp(filename, ":memory:") == 0 || (flags & SQLITE_OPEN_MEMORY) || + (uri && (std::strncmp(filename, "file::memory:", 13) == 0 || std::strstr(filename, "mode=memory"))); + if (memory) { return sqlite3_open_v2(filename, db, flags, nullptr); } - const std::u8string host = ResolvePath_nid_no_patch(filename).u8string(); - return sqlite3_open_v2(reinterpret_cast(host.c_str()), db, flags, nullptr); + if (uri) { + NotImplemented_nid_no_patch("sqlite3_open_v2 file URI"); + } + const std::filesystem::path host = ResolvePath_nid_no_patch(filename); + const int result = sqlite3_open_v2(reinterpret_cast(host.u8string().c_str()), db, flags, nullptr); + if (result == SQLITE_OK && (flags & SQLITE_OPEN_READWRITE)) { + for (const char* suffix : {"", "-wal", "-shm", "-journal"}) { + std::filesystem::path file = host; + file += suffix; + RecordWrittenPath_nid_no_patch(file); + } + } + return result; } int APS5_VABI sqlite3_prepare_v2_nid_postfix(sqlite3* db, const char* sql, int size, sqlite3_stmt** statement, diff --git a/core/libs/tests/GuestNKWebKitSqlite.cpp b/core/libs/tests/GuestNKWebKitSqlite.cpp index 127cbcb6c8..c276a59f33 100644 --- a/core/libs/tests/GuestNKWebKitSqlite.cpp +++ b/core/libs/tests/GuestNKWebKitSqlite.cpp @@ -1,4 +1,7 @@ #include "prx/libSceNKWebKitRequirements/include/Sqlite.hpp" +#include "prx/libc/include/General.hpp" + +#include #include #include #include @@ -13,12 +16,14 @@ constexpr int SQLITE_OK = 0; constexpr int SQLITE_ERROR = 1; constexpr int SQLITE_BUSY = 5; constexpr int SQLITE_INTERRUPT = 9; +constexpr int SQLITE_MISUSE = 21; constexpr int SQLITE_AUTH = 23; constexpr int SQLITE_ROW = 100; constexpr int SQLITE_DONE = 101; constexpr int SQLITE_CONSTRAINT_UNIQUE = 2067; constexpr int SQLITE_OPEN_READWRITE = 0x2; constexpr int SQLITE_OPEN_CREATE = 0x4; +constexpr int SQLITE_OPEN_URI = 0x40; constexpr int SQLITE_UTF8 = 1; constexpr int SQLITE_UTF16LE = 2; constexpr int SQLITE_INTEGER = 1; @@ -264,6 +269,8 @@ void CheckCollation(sqlite3* db) { Require(sqlite3_finalize_nid_postfix(ordered) == SQLITE_OK, "finalize collation"); Require(sqlite3_create_collation_v2_nid_postfix(db, "reverse", SQLITE_UTF16LE, &argument, ReverseCompare, DestroyCollation) == SQLITE_OK, "second encoding"); + Require(sqlite3_create_collation_v2_nid_postfix(db, "invalid", 99, &argument, ReverseCompare, DestroyCollation) == + SQLITE_MISUSE, "invalid collation encoding"); Require(destroyedCollations == 0, "collations live until close"); } @@ -335,6 +342,11 @@ int main() { CheckExtendedCodes(db); Require(sqlite3_close_nid_postfix(db) == SQLITE_OK, "close"); Require(destroyedCollations == 2, "collation destructors on close"); + sqlite3* memory = nullptr; + Require(sqlite3_open_v2_nid_postfix("file::memory:", &memory, SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI, nullptr) == + SQLITE_OK, "memory URI"); + Execute(memory, "CREATE TABLE t(v)"); + Require(sqlite3_close_nid_postfix(memory) == SQLITE_OK, "close memory URI"); std::random_device random; const auto directory = std::filesystem::temp_directory_path() / ("nk_sqlite_" + std::to_string(random())); @@ -342,6 +354,12 @@ int main() { std::filesystem::current_path(directory); CheckFileDatabase(directory); Require(std::filesystem::is_regular_file(directory / "wal.db"), "guest path maps into the root"); + const auto written = WrittenPaths_nid_no_patch(); + for (const char* name : {"wal.db", "wal.db-wal", "wal.db-shm", "wal.db-journal"}) { + Require(std::any_of(written.begin(), written.end(), [&](const std::filesystem::path& path) { + return path.filename() == name && std::filesystem::equivalent(path.parent_path(), directory); + }), "database files recorded for sceKernelSync"); + } std::filesystem::current_path(directory.parent_path()); std::filesystem::remove_all(directory); return 0; diff --git a/docs/dev/debt/libSceNKWebKitRequirements.md b/docs/dev/debt/libSceNKWebKitRequirements.md index 3135dd9d2f..3acc3ae518 100644 --- a/docs/dev/debt/libSceNKWebKitRequirements.md +++ b/docs/dev/debt/libSceNKWebKitRequirements.md @@ -3,12 +3,12 @@ ### Known export names; ABI prototypes unverified - 83 pending exports in [Export.cpp](../../../core/libs/prx/libSceNKWebKitRequirements/Export.cpp) are present in the [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs) inventory. Their prototypes and behavior are unverified. They use the no-parameter import-resolution convention and throw through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. -- The 72 of them declared in [Identified.hpp](../../../core/libs/prx/libSceNKWebKitRequirements/include/Identified.hpp) carry the zlib, libjpeg, libpsl, libxml2 and UndefinedBehaviorSanitizer names whose `tools/nid_names.py` hash equals the inventory NID; the names were taken from the identifiers of the zlib 1.3.1, libjpeg-turbo 3.0.4, libpsl 0.21.5, SQLite 3.46.1 and libxml2 2.13.4 sources and from the two `__ubsan_handle_cfi_check_fail` handlers libkernel_sys exports. A name match does not show that the console function has that library's prototype or behavior. The other 11 hash to no identifier of those sources or of ICU 76.1, HarfBuzz 10.0.1, cairo 1.18.2, pixman 0.42.2, FreeType 2.13.3, libpng 1.6.44, Expat 2.6.3, libxslt 1.1.42, libwebp 1.4.0, Brotli 1.1.0, WOFF2 1.0.2, curl 8.10.1, Little CMS 2.16, OpenJPEG 2.5.2 and Fontconfig 2.15.0. +- The 72 of them declared in [Identified.hpp](../../../core/libs/prx/libSceNKWebKitRequirements/include/Identified.hpp) carry the zlib, libjpeg, libpsl, libxml2 and UndefinedBehaviorSanitizer names whose `tools/nid_names.py` hash equals the inventory NID; the names were taken from the identifiers of the zlib 1.3.1, libjpeg-turbo 3.0.4, libpsl 0.21.5 and libxml2 2.13.4 sources and from the two `__ubsan_handle_cfi_check_fail` handlers libkernel_sys exports. A name match does not show that the console function has that library's prototype or behavior. The other 11 hash to no identifier of those sources or of SQLite 3.46.1, ICU 76.1, HarfBuzz 10.0.1, cairo 1.18.2, pixman 0.42.2, FreeType 2.13.3, libpng 1.6.44, Expat 2.6.3, libxslt 1.1.42, libwebp 1.4.0, Brotli 1.1.0, WOFF2 1.0.2, curl 8.10.1, Little CMS 2.16, OpenJPEG 2.5.2 and Fontconfig 2.15.0. ### SQLite adapters - The 50 `sqlite3_*` exports in [Sqlite.cpp](../../../core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp) assume the public SQLite C prototypes because their names hash to the inventory NIDs; the console prototypes are not confirmed. They call SQLite 3.54.1 built from `3rdparty/sqlite` with `SQLITE_THREADSAFE=1` and `SQLITE_OMIT_LOAD_EXTENSION`. The console library's version and compile options are unknown. -- [`sqlite3_open_v2`](../../../core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp) ignores the guest VFS name and uses the host default VFS. It maps file names through the guest path resolver, but passes `:memory:`, empty names, `SQLITE_OPEN_MEMORY` and `file:` URIs opened with `SQLITE_OPEN_URI` unchanged, so a URI path is not mapped. A database that exists only inside `app0.zar` is refused. +- [`sqlite3_open_v2`](../../../core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp) ignores the guest VFS name and uses the host default VFS. It maps file names through the guest path resolver, but passes `:memory:`, empty names, `SQLITE_OPEN_MEMORY` and in-memory `file:` URIs unchanged. Other `file:` URIs opened with `SQLITE_OPEN_URI` throw through `NotImplemented_nid_no_patch`. A database that exists only inside `app0.zar` is refused. SQLite writes the database and its `-wal`, `-shm` and `-journal` files on the host directly; a read-write open records those paths so `sceKernelSync` flushes them, but SQLite's own sync calls are not routed through the guest file layer. - [`sqlite3_bind_blob`](../../../core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp) and `sqlite3_bind_text16` copy data given with a guest destructor and call that destructor before returning, instead of when SQLite releases the value. - [`sqlite3_wal_hook`](../../../core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp) returns the host's previous argument, such as the default autocheckpoint frame count, until a guest hook is installed. diff --git a/tools/package_release.py b/tools/package_release.py index c143ee7d8d..e4c03c44ca 100644 --- a/tools/package_release.py +++ b/tools/package_release.py @@ -86,6 +86,8 @@ def package(platform, build, output, version): licenses.append((ROOT / "3rdparty/vorbis/COPYING", "libs/licenses/vorbis.txt")) if expected & {"libSceOpusDec.prx", "libSceOpusCeltDec.prx"}: licenses.append((ROOT / "3rdparty/opus/COPYING", "libs/licenses/opus.txt")) + if "libSceNKWebKitRequirements.prx" in expected: + licenses.append((ROOT / "3rdparty/sqlite/LICENSE.md", "libs/licenses/sqlite.txt")) if platform == "windows": runtime = windows_runtime(build) files.extend(runtime / name for name in ("libgcc_s_seh-1.dll", "libstdc++-6.dll", "libwinpthread-1.dll")) From df959bf14bf37a1d830f92e461ddb770a96d4a34 Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Tue, 3 Nov 2026 23:14:49 +1300 Subject: [PATCH 309/612] test(relinker): print the changed flag bits when MULX lowering changes arithmetic flags mulx_lowering failed twice on the x86_64 macOS relinker job, which runs emulated on an arm runner (d68b36002d, run 41582706998, and 66387a10ee, run 41601236599), and passed in nine other runs of the same code. The failure only said "MULX changed arithmetic flags". It now also prints the input and output flag bits, the bits that differ, and the operand shape, so the next failure shows whether it is the auxiliary or parity flag or one of the flags the lowering actually has to preserve. --- core/relinker/codegen/tests/MulxLoweringTests.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/relinker/codegen/tests/MulxLoweringTests.cpp b/core/relinker/codegen/tests/MulxLoweringTests.cpp index d3d7a0528d..bac8a51b65 100644 --- a/core/relinker/codegen/tests/MulxLoweringTests.cpp +++ b/core/relinker/codegen/tests/MulxLoweringTests.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -255,7 +256,14 @@ void check(const State& state, const Codegen::MulxOperands& operands, std::uint6 const auto expected = reg == operands.HighDestination ? high : reg == operands.LowDestination ? low : state.Input[reg]; require(state.Output[reg] == expected, "MULX changed an unrelated register or produced an incorrect result"); } - require((state.Flags & 0x8D5u) == (state.InputFlags & 0x8D5u), "MULX changed arithmetic flags"); + if ((state.Flags & 0x8D5u) != (state.InputFlags & 0x8D5u)) { + char text[192]; + std::snprintf(text, sizeof text, "MULX changed arithmetic flags: input %#llx, output %#llx, changed bits %#llx (wide %d, high %u, low %u)", + static_cast(state.InputFlags & 0x8D5u), static_cast(state.Flags & 0x8D5u), + static_cast((state.InputFlags ^ state.Flags) & 0x8D5u), operands.Wide ? 1 : 0, + static_cast(operands.HighDestination), static_cast(operands.LowDestination)); + throw std::runtime_error(text); + } for (const auto value : state.RedZone) require(value == Execution::kSentinel, "MULX overwrote the caller's red zone"); } From 5c054e6b5e48aef1bde87b7f6c31b70a9e00d563 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Tue, 3 Nov 2026 08:12:09 -0500 Subject: [PATCH 310/612] fix(libc): preserve main float argument reduction --- core/libs/prx/libc/src/Math.cpp | 129 +++++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 236e124521..6bfeec9771 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -1300,6 +1300,133 @@ recompute: return n & 7; } +static const std::int32_t TrigfIpio2[] = { + 0xA2F983, 0x6E4E44, 0x1529FC, 0x2757D1, 0xF534DD, 0xC0DB62, 0x95993C, 0x439041, 0xFE5163, 0xABDEBB, 0xC561B7, 0x246E3A, + 0x424DD2, 0xE00649, 0x2EEA09, 0xD1921C, 0xFE1DEB, 0x1CB129, 0xA73EE8, 0x8235F5, 0x2EBB44, 0x84E99C, 0x7026B4, 0x5F7E41, + 0x3991D6, 0x398353, 0x39F49C, 0x845F8B, 0xBDF928, 0x3B1FF8, 0x97FFDE, 0x05980F, 0xEF2F11, 0x8B5A0A, 0x6D1F6D, 0x367ECF, + 0x27CB09, 0xB74F46, 0x3F669E, 0x5FEA2D, 0x7527BA, 0xC7EBE5, 0xF17B3D, 0x0739F7, 0x8A5292, 0xEA6BFB, 0x5FB11F, 0x8D5D08, + 0x560330, 0x46FC7B, 0x6BABF0, 0xCFBC20, 0x9AF436, 0x1DA9E3, 0x91615E, 0xE61B08, 0x659985, 0x5F14A0, 0x68408D, 0xFFD880, + 0x4D7327, 0x310606, 0x1556CA, 0x73A8C9, 0x60E27B, 0xC08C6B, +}; + +static const double TrigfPio2Parts[] = { + 1.57079625129699707031e+00, 7.54978941586159635335e-08, 5.39030252995776476554e-15, 3.28200341580791294123e-22, + 1.27065575308067607349e-29, 1.22933308981111328932e-36, 2.73370053816464559624e-44, 2.16741683877804819444e-51, +}; + +static int TrigfKernelRemPio2(const double* x, double* y, int e0) { + const double two24 = 1.67772160000000000000e+07, twon24 = 5.96046447753906250000e-08; + std::int32_t jz, jv, jp, jk, carry, n, iq[20], i, j, k, m, q0, ih; + double z, fw, f[20], fq[20], q[20]; + const std::int32_t jx = 0; + jk = 3; + jp = jk; + jv = (e0 - 3) / 24; + if (jv < 0) jv = 0; + q0 = e0 - 24 * (jv + 1); + j = jv - jx; + m = jx + jk; + for (i = 0; i <= m; i++, j++) f[i] = j < 0 ? 0.0 : static_cast(TrigfIpio2[j]); + for (i = 0; i <= jk; i++) { + for (j = 0, fw = 0.0; j <= jx; j++) fw += x[j] * f[jx + i - j]; + q[i] = fw; + } + jz = jk; +recompute: + for (i = 0, j = jz, z = q[jz]; j > 0; i++, j--) { + fw = static_cast(static_cast(twon24 * z)); + iq[i] = static_cast(z - two24 * fw); + z = q[j - 1] + fw; + } + z = std::scalbn(z, q0); + z -= 8.0 * std::floor(z * 0.125); + n = static_cast(z); + z -= static_cast(n); + ih = 0; + if (q0 > 0) { + i = iq[jz - 1] >> (24 - q0); + n += i; + iq[jz - 1] -= i << (24 - q0); + ih = iq[jz - 1] >> (23 - q0); + } else if (q0 == 0) { + ih = iq[jz - 1] >> 23; + } else if (z >= 0.5) { + ih = 2; + } + if (ih > 0) { + n += 1; + carry = 0; + for (i = 0; i < jz; i++) { + j = iq[i]; + if (carry == 0) { + if (j != 0) { + carry = 1; + iq[i] = 0x1000000 - j; + } + } else { + iq[i] = 0xffffff - j; + } + } + if (q0 > 0) { + switch (q0) { + case 1: iq[jz - 1] &= 0x7fffff; break; + case 2: iq[jz - 1] &= 0x3fffff; break; + } + } + if (ih == 2) { + z = 1.0 - z; + if (carry != 0) z -= std::scalbn(1.0, q0); + } + } + if (z == 0.0) { + j = 0; + for (i = jz - 1; i >= jk; i--) j |= iq[i]; + if (j == 0) { + for (k = 1; iq[jk - k] == 0; k++) { + } + for (i = jz + 1; i <= jz + k; i++) { + f[jx + i] = static_cast(TrigfIpio2[jv + i]); + for (j = 0, fw = 0.0; j <= jx; j++) fw += x[j] * f[jx + i - j]; + q[i] = fw; + } + jz += k; + goto recompute; + } + } + if (z == 0.0) { + jz -= 1; + q0 -= 24; + while (iq[jz] == 0) { + jz--; + q0 -= 24; + } + } else { + z = std::scalbn(z, -q0); + if (z >= two24) { + fw = static_cast(static_cast(twon24 * z)); + iq[jz] = static_cast(z - two24 * fw); + jz += 1; + q0 += 24; + iq[jz] = static_cast(fw); + } else { + iq[jz] = static_cast(z); + } + } + fw = std::scalbn(1.0, q0); + for (i = jz; i >= 0; i--) { + q[i] = fw * static_cast(iq[i]); + fw *= twon24; + } + for (i = jz; i >= 0; i--) { + for (fw = 0.0, k = 0; k <= jp && k <= jz - i; k++) fw += TrigfPio2Parts[k] * q[i + k]; + fq[jz - i] = fw; + } + fw = 0.0; + for (i = jz; i >= 0; i--) fw += fq[i]; + y[0] = ih == 0 ? fw : -fw; + return n & 7; +} + static int TrigfRemPio2(float x, double* y) { const double invpio2 = 6.36619772367581382433e-01, pio2_1 = 1.57079631090164184570e+00, pio2_1t = 1.58932547735281966916e-08; const std::int32_t hx = std::bit_cast(x); @@ -1321,7 +1448,7 @@ static int TrigfRemPio2(float x, double* y) { const std::int32_t e0 = (ix >> 23) - 150; const float z = std::bit_cast(ix - static_cast(static_cast(e0) << 23)); double tx[1] = {z}, ty[1]; - const int n = KernelRemPio2(tx, ty, e0, 1, 0); + const int n = TrigfKernelRemPio2(tx, ty, e0); if (hx < 0) { *y = -ty[0]; return -n; From 13c396c3c4eb5ddad2502a269bcae1509b286245 Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Tue, 3 Nov 2026 07:04:07 -0800 Subject: [PATCH 311/612] test(libressl): pin the 8-byte minimum of PKCS#1 type 1 padding RSA_padding_check_PKCS1_type_1 refuses a block with fewer than eight 0xFF bytes, but no case reached that check: lowering it to one byte still passed. RSA_public_decrypt now gets a type 1 block with seven 0xFF bytes, which must be refused, and one with eight, which must give its payload. The RSA_flags entry also drops its sentence that no export creates a key: RSA_new and d2i_RSA_PUBKEY do now. --- core/libs/tests/GuestLibreSslRsa.cpp | 6 ++++++ docs/dev/debt/libSceLibreSsl.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/libs/tests/GuestLibreSslRsa.cpp b/core/libs/tests/GuestLibreSslRsa.cpp index c6ccfbeaa0..063ad517f2 100644 --- a/core/libs/tests/GuestLibreSslRsa.cpp +++ b/core/libs/tests/GuestLibreSslRsa.cpp @@ -497,6 +497,12 @@ static void TestPublicDecrypt() { for (int padding : {0, PaddingSslv23, PaddingOaep, 6, 7}) Require(RSA_public_decrypt_nid_postfix(256, signature.data(), out.data(), rsa, padding) == -1); RequireThrows([&] { RSA_public_decrypt_nid_postfix(256, nullptr, out.data(), rsa, PaddingPkcs1); }); RequireThrows([&] { RSA_public_decrypt_nid_postfix(256, signature.data(), nullptr, rsa, PaddingPkcs1); }); + const auto payload = Pattern(245, 5); + const auto sevenPadding = Recover(Concat({{0x00, 0x01}, Bytes(7, 0xFF), {0x00}, Pattern(246, 5)})); + const auto eightPadding = Recover(Concat({{0x00, 0x01}, Bytes(8, 0xFF), {0x00}, payload})); + Require(RSA_public_decrypt_nid_postfix(256, sevenPadding.data(), out.data(), rsa, PaddingPkcs1) == -1); + Require(RSA_public_decrypt_nid_postfix(256, eightPadding.data(), out.data(), rsa, PaddingPkcs1) == 245); + Require(Bytes(out.begin(), out.begin() + 245) == payload); RSA_free_nid_postfix(rsa); } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 884494698a..10f2cf4c64 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -21,7 +21,7 @@ - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. - [sk_num, sk_value, sk_pop, sk_pop_free](../../../core/libs/prx/libSceLibreSsl/src/Stack.cpp) (libSceLibreSsl) - the 32-byte `_STACK` layout (`num`, `data`, `sorted`, `num_alloc`, `comp`) and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/stack) `stack.h` and `stack.c`, the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214); nothing is checked on the console. `sk_pop_free` releases the element array and the stack with the guest `free`, as the module would through libc. No export of this module creates a stack yet, so only stacks the caller builds reach them. A null element array that LibreSSL would read through, and a null free function with a non-null element, throw where LibreSSL faults -- [RSA_flags, RSA_size](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `rsa.h`, `rsa_crpt.c` and `bn_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a key yet, so only keys the caller builds reach them. `RSA_size` counts the modulus bytes from its top word, as `BN_num_bytes`, so a zero top word is not skipped. A null method, a null key or modulus, and a positive word count with null words, which LibreSSL dereferences, throw, as do a negative word count and a bit count that overflows `int` +- [RSA_flags, RSA_size](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `rsa.h`, `rsa_crpt.c` and `bn_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). `RSA_size` counts the modulus bytes from its top word, as `BN_num_bytes`, so a zero top word is not skipped. A null method, a null key or modulus, and a positive word count with null words, which LibreSSL dereferences, throw, as do a negative word count and a bit count that overflows `int` - [RSA_new, RSA_free](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. They follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/rsa) `rsa_lib.c` and `rsa_eay.c`: the key comes from the guest `malloc` with the default method `"Eric Young's PKCS#1 RSA"` (method flags 0, key flags `RSA_FLAG_CACHE_PUBLIC | RSA_FLAG_CACHE_PRIVATE`) and one reference, and `RSA_free` drops a reference, runs the method's `finish` and clears and frees each `BIGNUM` as `BN_clear_free` does. The method's slots are host functions; its private-key operations, `rsa_mod_exp` and `bn_mod_exp` throw, as no export sets a private key yet. With no `ENGINE`, ex_data or blinding implemented, `RSA_free` throws for a key holding one, and for a Montgomery context, which this module never caches - [d2i_RSA_PUBKEY](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. It decodes a SubjectPublicKeyInfo as [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto)'s `x_pubkey.c`, `rsa_ameth.c`, `rsa_asn1.c` and `tasn_dec.c` do, without building the `X509_PUBKEY` and `EVP_PKEY` they use on the way: the `rsaEncryption` or `rsa` OID, any well-formed parameter, the masked BIT STRING and `n` and `e` read as unsigned `BIGNUM`s on a fresh `RSA_new` key, with the length, tag and header quirks of that decoder. The BER forms it also accepts throw: indefinite lengths, constructed strings and parameters, and a BIT STRING with 32 or more unused bits, whose mask is undefined C. A null input pointer, and a null input with a positive length, throw - [RSA_public_encrypt, RSA_public_decrypt](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. They call the key method's slots; the default ones follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/rsa) `rsa_eay.c`, `rsa_pk1.c`, `rsa_oaep.c` and `rsa_none.c`: the modulus and exponent limits, encryption with PKCS#1 v1.5, OAEP (SHA-1, empty label) or no padding, decryption checking PKCS#1 v1.5 type 1 or no padding, and -1 for other paddings, without the error codes LibreSSL queues. Random padding comes from host entropy and the arithmetic from mbedtls, whose 8192-bit limit makes a larger modulus throw; the Montgomery context is never cached. `RSA_SSLV23_PADDING` encryption and `RSA_X931_PADDING` decryption throw, as do a method with its own `bn_mod_exp`, a negative `BIGNUM` and a null key, method or slot From bbdaf8501bd3ee1891c5fb72c8132ddc9561a899 Mon Sep 17 00:00:00 2001 From: brumskysb <235197310.brumskysb@users.noreply.github.com> Date: Mon, 2 Nov 2026 16:09:12 -0700 Subject: [PATCH 312/612] docs(shader): record FORCE_DEGAMMA at wave64 for a 2D descriptor --- docs/dev/debt/shader.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index ebd15dd050..1a9f6190ad 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -203,7 +203,7 @@ - [`ds_ordered_count`](../../../core/shader/recompiler/Translation/src/MemoryInstructions/SharedMemoryInstructions.cpp) (GDS only) reads its operand fields as LLVM encodes them for `llvm.amdgcn.ds.ordered.add` ([SIISelLowering.cpp](https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/AMDGPU/SIISelLowering.cpp#L8844-L8894)): `offset0` is the counter's byte offset (index times 4) and the whole GDS address, `offset1` bit 0 wave release, bit 1 wave done, bits 2-3 the shader type, bit 4 swap instead of add and bits 6-7 the dword count less one. `M0` holds the ordered wave ID, not a GDS address as for `ds_append` (radeonsi passes `TG_SIZE` bits 6-17 as the intrinsic's pointer in [si_compute_prim_discard.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-20.3.0/src/gallium/drivers/radeonsi/si_compute_prim_discard.c) and `gs_tg_info` bits 0-11 in [gfx10_shader_ngg.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-21.3.0/src/gallium/drivers/radeonsi/gfx10_shader_ngg.c), with "The GDS address is always 0 with ordered append"); it only orders the waves, which is not modeled, so it is ignored. Each wave adds the value of its first active lane to the counter with one device-scope atomic and every active lane gets the counter's previous value (the `ORDERED_APPEND_ENBL` dispatch modifier is accepted for it). The hardware also serializes the waves of a dispatch in launch order, which is not modeled: the waves add in any order, so counters a title reads back as ordered offsets give the same totals but may hand out ranges in another order. Which lane's value is added is not documented; the first active lane is assumed and not measured on hardware. A swap (`offset1` bit 4) stores the first active lane's value in place of the counter with one device-scope atomic exchange and returns the counter's previous value to every active lane; with the waves unordered, which wave's value the counter keeps and which previous value each wave gets need not follow the launch order the hardware gives. Counts of more than one dword and LDS forms throw. Covered by [GdsAppendConsume.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/GdsAppendConsume.cpp). - [s_swappc_b64](../../../core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp) writes the link pair with the same model as `s_getpc_b64`: the guest return address `pc + 4` as a byte offset over `GetShaderBase`. Any other use of the link pair (arithmetic, hash, store) throws, apart from the overwrites, SOPC compares and shared links that [static scalar calls](../../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) allow; constant-offset folding over the link is not modeled. The special encoding `s_swappc_b64` with SDST 125 still decodes as `s_setpc_b64`, which matches the hardware: measured on RDNA2 (gfx1036) in wave32 and wave64, it branches to the source pair and leaves a marker SGPR pair untouched, and with an SGPR destination the link is `PC + 4`. - [2D biased sampling through a one-slice, single-level 3D T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) supplies a zero third coordinate and retains the bias operation, with normalized point minification and magnification and no linear mip filtering. Measured on RDNA2 (AMD integrated gfx1036, driver 32.0.21036.18, Windows 11) by running an OpenCL-loaded native kernel with `image_sample_b` DIM 2D against 16x16x1 R32_UINT and RGBA8_UNORM 3D images: biases -16, -0.5, 0, 0.5 and 16 read the (u,v) texel in slice zero, including wrapped coordinates, point min/max reduction and the title's sampler with Z border addressing. Wave32, IEEE off, DX10 clamp on, denormals preserved, round to nearest even, FP16 overflow off; wave64 and the PS5's gfx1013 are not measured. Pixel-stage, direct, non-depth, unpacked samples whose only use is the plain 32-bit bias form are accepted; extra slices, any nonzero mip range, linear or anisotropic filtering and unnormalized coordinates throw. Vulkan readback is checked on NVIDIA RTX 5070 and AMD integrated graphics for positive and negative bias and point weighted-average/min/max reduction. -- [FORCE_DEGAMMA on RGBA8_UINT](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) materializes a direct image sampled only by plain biased pixel accesses as RGBA8_SRGB when every paired sampler forces degamma. Measured on AMD integrated RDNA2 gfx1036 (driver 32.0.21036.18, Windows 11, wave32, IEEE off, DX10 clamp on, denormals preserved, round to nearest even, FP16 overflow off): a native `image_sample_b` DIM 2D on a 16x16x1 RGBA8_UINT 3D T# returns integer channels without the bit, but the same floating-point decoded values as RGBA8_UNORM with it, including point min/max reduction and the title's exact sampler. The metadata and image view must both become floating-point sRGB; ignoring the sampler bit or retaining a UINT heap is incorrect. The accepted case requires one slice, mip zero, normalized point sampling and uniform FORCE_DEGAMMA across its samplers. Other image accesses, mixed sampler states, indirect descriptors and other integer formats remain refused. Wave64, other filtering modes and the PS5's gfx1013 are not measured. +- [FORCE_DEGAMMA on RGBA8_UINT](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) materializes a direct image sampled only by plain biased pixel accesses as RGBA8_SRGB when every paired sampler forces degamma. Measured on AMD integrated RDNA2 gfx1036 (driver 32.0.21036.18, Windows 11, wave32, IEEE off, DX10 clamp on, denormals preserved, round to nearest even, FP16 overflow off): a native `image_sample_b` DIM 2D on a 16x16x1 RGBA8_UINT 3D T# returns integer channels without the bit, but the same floating-point decoded values as RGBA8_UNORM with it, including point min/max reduction and the title's exact sampler. The metadata and image view must both become floating-point sRGB; ignoring the sampler bit or retaining a UINT heap is incorrect. The accepted case requires one slice, mip zero, normalized point sampling and uniform FORCE_DEGAMMA across its samplers. Other image accesses, mixed sampler states, indirect descriptors and other integer formats remain refused. Wave64 matches wave32 for a 2D descriptor; the one-slice 3D descriptor, other filtering modes and the PS5's gfx1013 are not measured. - The [s_swappc_b64 fetch-shader call at vertex/local/tessellation/mesh entry](../../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) assumes the called fetch-shader blob ends in `s_setpc_b64` to the link pair (the GNM fetch-shader shape shadPS4's `EmitFetch` copies); the blob is not part of the shader program, so its return closes the pairing by assumption and any in-program reader of the link pair throws. Attribute fetches are inlined from the request's parsed fetch-shader plan through the same `GetAttribute` path as the embedded fetch analyzer, honoring the buffer resource `dst_sel`; an attribute writing more than four registers, or an entry call without a parsed plan, throws. The vertex/instance offset accumulator inside the fetch blob is not replayed for external blobs. - The scalar aperture sources [src_shared_base, src_shared_limit, src_private_base, src_private_limit](../../../core/shader/recompiler/RdnaDecoder/src/RdnaInstructionDecoder.cpp) (core/shader/recompiler, scalar source codes 235 to 238) read as the 64-bit apertures the flat routing uses: a base is the aperture's high dword (`SharedApertureHigh`, `PrivateApertureHigh`) over a low dword of 0, a limit is the same high dword over 0xffffffff, as a PS5 (13.40, measured 2026-10-19 by a gfx1013 kernel run from inside a retail title) read them (0x80000000_00000000 / 0x80000000_FFFFFFFF shared, 0x70000000_00000000 / 0x70000000_FFFFFFFF private). A 64-bit read (`s_mov_b64`, a 64-bit VALU source, a wave64 lane mask) gets the whole value and a 32-bit read the low dword, after LLVM's `SIRegisterInfo.td` and `getSegmentAperture` and the gfx1030 and gfx1035 readouts on [#1190](https://github.com/boykopovar/AnyPS5/pull/1190) and [#3803](https://github.com/boykopovar/AnyPS5/issues/3803). The 32-bit read and the wave's address mode (taken to be 64-bit, PTR32 clear) were not measured on the console - [EmitSetAttribute](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - a primitive with a vertex whose clip position is (0,0,0,0) is clipped away through an extra `gl_ClipDistance` element on vertex and tessellation evaluation shaders, matching what a Radeon 610M (RDNA2, RADV) and llvmpipe rasterise; NVIDIA 615.78.08 without it draws the vertex at the viewport centre. Measured for triangles (strip and list) and for a point at (0,0,0,0), which the Radeon 610M does not draw, NVIDIA draws at the viewport centre and llvmpipe at the top-left corner; lines with such a vertex are clipped by the same element and were not measured. Mesh, geometry and the generated barycentric geometry stage do not write it, the generated rect-list and barycentric tessellation stages forward the vertex shader's array, and a vertex or tessellation evaluation shader whose guest clip and cull distances already take all eight does not write it either, so NVIDIA 615.78.08 draws such a vertex at the viewport centre; a device without `shaderClipDistance` cannot compile vertex shaders that write it (the compile throws). The PS5 rasteriser was not measured. From f46976d011087e482b876f979f17ee9fa520ffac Mon Sep 17 00:00:00 2001 From: slopgobblercobbler Date: Sun, 1 Nov 2026 18:34:50 +0100 Subject: [PATCH 313/612] test(libkernel): cover invalid handle in sceKernelAddUserEvent --- core/libs/tests/GuestEqueueEvents.cpp | 6 ++++++ pr_body.txt | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 pr_body.txt diff --git a/core/libs/tests/GuestEqueueEvents.cpp b/core/libs/tests/GuestEqueueEvents.cpp index ec5b994361..5a038a3d4a 100644 --- a/core/libs/tests/GuestEqueueEvents.cpp +++ b/core/libs/tests/GuestEqueueEvents.cpp @@ -500,4 +500,10 @@ int main() { Require(RejectsNull(sceKernelGetEventFilter)); Require(RejectsNull(sceKernelGetEventId)); Require(RejectsNull(sceKernelGetEventUserData)); + + KernelEqueue closedEq = 0; + Require(sceKernelCreateEqueue(&closedEq, "closed") == SCE_OK); + Require(sceKernelDeleteEqueue(closedEq) == SCE_OK); + Require(sceKernelAddUserEvent(closedEq, 7) == SCE_KERNEL_ERROR_EBADF); + Require(sceKernelAddUserEvent(0, 7) == SCE_KERNEL_ERROR_EBADF); } diff --git a/pr_body.txt b/pr_body.txt new file mode 100644 index 0000000000..15115bd8f8 --- /dev/null +++ b/pr_body.txt @@ -0,0 +1,19 @@ +### What +Add negative test coverage in core/libs/tests/GuestEqueueEvents.cpp verifying invalid or closed equeue handles in sceKernelAddUserEvent. + +### Tested +Ran python tools/check_conventions.py --base upstream/main --head HEAD with 0 findings. + +### Checklist +- [x] Based on current `main`; no other open PR implements the same functions +- [x] One topic per PR; follow-ups go in a new PR +- [x] No comments in code except [technical debt](https://github.com/boykopovar/AnyPS5/blob/main/docs/dev/CONVENTIONS.md) +- [x] Unimplemented paths throw (`NotImplemented_nid_no_patch`); silent stubs are listed under Silent stubs in their library's [technical debt](https://github.com/boykopovar/AnyPS5/tree/main/docs/dev/debt) file +- [x] No notes, investigation `.md` files or images added to the repository (attach them to this PR) +- [x] New third-party code is a submodule built from source +- [x] Generic behaviour, not specific to one title +- [x] Depends on: none +- [x] Fixes: none +- [x] Unblocks: none +- [x] GPUs tested: n/a +- [x] AI-assisted: yes From 5deb99b4a8bfff41a674edc9cc101e9f109437db Mon Sep 17 00:00:00 2001 From: slopgobblercobbler Date: Wed, 4 Nov 2026 00:17:08 +0100 Subject: [PATCH 314/612] fix(libkernel): remove stray pr_body.txt from repository root --- pr_body.txt | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 pr_body.txt diff --git a/pr_body.txt b/pr_body.txt deleted file mode 100644 index 15115bd8f8..0000000000 --- a/pr_body.txt +++ /dev/null @@ -1,19 +0,0 @@ -### What -Add negative test coverage in core/libs/tests/GuestEqueueEvents.cpp verifying invalid or closed equeue handles in sceKernelAddUserEvent. - -### Tested -Ran python tools/check_conventions.py --base upstream/main --head HEAD with 0 findings. - -### Checklist -- [x] Based on current `main`; no other open PR implements the same functions -- [x] One topic per PR; follow-ups go in a new PR -- [x] No comments in code except [technical debt](https://github.com/boykopovar/AnyPS5/blob/main/docs/dev/CONVENTIONS.md) -- [x] Unimplemented paths throw (`NotImplemented_nid_no_patch`); silent stubs are listed under Silent stubs in their library's [technical debt](https://github.com/boykopovar/AnyPS5/tree/main/docs/dev/debt) file -- [x] No notes, investigation `.md` files or images added to the repository (attach them to this PR) -- [x] New third-party code is a submodule built from source -- [x] Generic behaviour, not specific to one title -- [x] Depends on: none -- [x] Fixes: none -- [x] Unblocks: none -- [x] GPUs tested: n/a -- [x] AI-assisted: yes From 1446abba83d1be4d3c766e8dad4164ae764d4c1f Mon Sep 17 00:00:00 2001 From: hyvvs Date: Wed, 4 Nov 2026 04:26:05 +0100 Subject: [PATCH 315/612] fix(libcairo): report native subsurface type --- core/libs/prx/libcairo/Surface.cpp | 6 ++++-- core/libs/tests/GuestCairoSurface.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libcairo/Surface.cpp b/core/libs/prx/libcairo/Surface.cpp index 5700249c1f..2e3dd3e2be 100644 --- a/core/libs/prx/libcairo/Surface.cpp +++ b/core/libs/prx/libcairo/Surface.cpp @@ -19,6 +19,7 @@ constexpr int kColor = 0x1000; constexpr int kAlpha = 0x2000; constexpr int kColorAlpha = 0x3000; constexpr int kImageSurfaceType = 0; +constexpr int kSubsurfaceType = 23; constexpr int kInvalidFormatValue = -1; int BitsPerPixel(int format) { @@ -231,8 +232,9 @@ int APS5_VABI cairo_surface_status(CairoSurface* surface) { return surface ? State(surface)->status : kNullPointer; } -int APS5_VABI cairo_surface_get_type(CairoSurface*) { - return kImageSurfaceType; +int APS5_VABI cairo_surface_get_type(CairoSurface* surface) { + return surface && State(surface)->status == kSuccess && !State(surface)->isImage + ? kSubsurfaceType : kImageSurfaceType; } int APS5_VABI cairo_surface_get_content(CairoSurface* surface) { diff --git a/core/libs/tests/GuestCairoSurface.cpp b/core/libs/tests/GuestCairoSurface.cpp index cae240d087..ec9d1082c7 100644 --- a/core/libs/tests/GuestCairoSurface.cpp +++ b/core/libs/tests/GuestCairoSurface.cpp @@ -185,7 +185,7 @@ void CheckSimilarAndSubsurfaces() { } CairoSurface* subsection = cairo_surface_create_for_rectangle(parent, 1.5, 2.5, 3.5, 4.5); assert(subsection && cairo_surface_status(subsection) == 0); - assert(cairo_surface_get_type(subsection) == 0); + assert(cairo_surface_get_type(subsection) == 23); assert(cairo_surface_get_content(subsection) == 0x3000); assert(cairo_image_surface_get_format(subsection) == -1); assert(cairo_image_surface_get_data(subsection) == nullptr); From 468c07712c5aa7478a77b78257eb4f4b7d4725b8 Mon Sep 17 00:00:00 2001 From: Cesar Mora Date: Sat, 31 Oct 2026 20:15:26 -0600 Subject: [PATCH 316/612] feat(libs): let relinked Linux games opt into mimalloc With APS5_MIMALLOC set, libkernel restarts the title with libmimalloc.so from libs/ first in LD_PRELOAD, as it does on macOS with DYLD_INSERT_LIBRARIES. The macOS restart becomes one implementation with platform pieces (MimallocRestart.cpp): on Linux the executable is the target of /proc/self/exe and the arguments those of /proc/self/cmdline. The restart is a priority 101 constructor, so it runs before libkernel's other initializers. It gives the standard streams back from an ANYPS5_LOG_FILE copy before execv, which also fixes the macOS restart under that variable, and refuses a path the preload list cannot hold. The build copies libmimalloc.so beside the libraries except in ASan and TSan builds, the Linux release archives ship it, and the restart tests run on Linux too, with a new case for ANYPS5_LOG_FILE. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015W3M6NbYpmwWkzkL8YNLaF --- CMakeLists.txt | 4 +- core/libs/CMakeLists.txt | 9 +- core/libs/prx/libkernel/CMakeLists.txt | 10 +- .../libkernel/System/src/MimallocMacOs.cpp | 57 ---------- .../libkernel/System/src/MimallocRestart.cpp | 104 ++++++++++++++++++ core/libs/tests/MimallocRestart.cpp | 5 +- docs/dev/ARCHITECTURE.md | 19 ++-- docs/dev/ENVIRONMENT.md | 4 +- docs/dev/debt/libkernel.md | 1 + docs/user/USAGE.md | 2 + tools/package_release.py | 2 + tools/tests/test_package_release.py | 35 +++++- 12 files changed, 170 insertions(+), 82 deletions(-) delete mode 100644 core/libs/prx/libkernel/System/src/MimallocMacOs.cpp create mode 100644 core/libs/prx/libkernel/System/src/MimallocRestart.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f135ac95ff..38c014db0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -657,7 +657,7 @@ set_target_properties(usb-1.0 PROPERTIES POSITION_INDEPENDENT_CODE ON) include(cmake/ZArchive.cmake) include(cmake/Opus.cmake) -if(WIN32 OR APPLE) +if(WIN32 OR APPLE OR (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT ANYPS5_ENABLE_ASAN AND NOT ANYPS5_ENABLE_TSAN)) set(MI_BUILD_SHARED ON CACHE BOOL "" FORCE) set(MI_BUILD_STATIC OFF CACHE BOOL "" FORCE) set(MI_BUILD_OBJECT OFF CACHE BOOL "" FORCE) @@ -671,6 +671,8 @@ if(WIN32 OR APPLE) target_link_options(mimalloc PRIVATE -static LINKER:--wrap=malloc,--wrap=calloc,--wrap=realloc,--wrap=free,--wrap=strdup,--wrap=_strdup) target_sources(mimalloc PRIVATE ${CMAKE_SOURCE_DIR}/core/libs/allocator/RuntimeHeap.c) + elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_options(mimalloc PRIVATE LINKER:--as-needed) endif() endif() diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 7230150c09..9c080408e8 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -289,13 +289,14 @@ if(TARGET mimalloc AND WIN32) ) list(APPEND PATCHED_OUTPUTS ${PATCHED_DIR}/mimalloc.dll ${PATCHED_DIR}/mimalloc-redirect.dll) elseif(TARGET mimalloc) + set(MIMALLOC_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}mimalloc${CMAKE_SHARED_LIBRARY_SUFFIX}) add_custom_command( - OUTPUT ${PATCHED_DIR}/libmimalloc.dylib - COMMAND ${CMAKE_COMMAND} -E copy $ ${PATCHED_DIR}/libmimalloc.dylib + OUTPUT ${PATCHED_DIR}/${MIMALLOC_NAME} + COMMAND ${CMAKE_COMMAND} -E copy $ ${PATCHED_DIR}/${MIMALLOC_NAME} DEPENDS mimalloc - COMMENT "Copying libmimalloc.dylib beside the libraries" + COMMENT "Copying ${MIMALLOC_NAME} beside the libraries" ) - list(APPEND PATCHED_OUTPUTS ${PATCHED_DIR}/libmimalloc.dylib) + list(APPEND PATCHED_OUTPUTS ${PATCHED_DIR}/${MIMALLOC_NAME}) endif() include(prx/libSceFreeType/BuildInfo.cmake) diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index c86cd81800..3eedd5eecd 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -74,7 +74,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/System/src/HostPerformance.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/HostPerformanceProcess.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/HostPerformanceStartup.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/System/src/MimallocMacOs.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/System/src/MimallocRestart.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/SystemConfiguration.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Login.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Getlogin.cpp @@ -1711,17 +1711,21 @@ if(BUILD_TESTING) set_tests_properties(guest_system_configuration PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() - if(APPLE AND TARGET mimalloc) + if(NOT WIN32 AND TARGET mimalloc) add_test_executable(mimalloc_restart_tests ${LIBS_INCLUDE_DIR}/tests/MimallocRestart.cpp) target_link_libraries(mimalloc_restart_tests PRIVATE libkernel libc) add_custom_command(TARGET mimalloc_restart_tests POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy $ $/libmimalloc.dylib) + COMMAND ${CMAKE_COMMAND} -E copy $ $/${CMAKE_SHARED_LIBRARY_PREFIX}mimalloc${CMAKE_SHARED_LIBRARY_SUFFIX}) add_test(NAME mimalloc_restart_off COMMAND mimalloc_restart_tests off) set_tests_properties(mimalloc_restart_off PROPERTIES ENVIRONMENT_MODIFICATION "APS5_MIMALLOC=unset:") add_test(NAME mimalloc_restart_on COMMAND mimalloc_restart_tests on) set_tests_properties(mimalloc_restart_on PROPERTIES ENVIRONMENT "APS5_MIMALLOC=1") add_test(NAME mimalloc_restart_once COMMAND mimalloc_restart_tests once) set_tests_properties(mimalloc_restart_once PROPERTIES ENVIRONMENT "APS5_MIMALLOC=1;APS5_MIMALLOC_RESTARTED=1") + add_test(NAME mimalloc_restart_log COMMAND mimalloc_restart_tests log) + set_tests_properties(mimalloc_restart_log PROPERTIES + ENVIRONMENT "APS5_MIMALLOC=1;ANYPS5_LOG_FILE=${CMAKE_CURRENT_BINARY_DIR}/mimalloc_restart_log.txt" + PASS_REGULAR_EXPRESSION "mimalloc_restart_tests: the restarted process writes to its standard output") endif() add_test_executable(guest_software_version_tests ${LIBS_INCLUDE_DIR}/tests/GuestSoftwareVersion.cpp) target_include_directories(guest_software_version_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/prx/libkernel/System/src/MimallocMacOs.cpp b/core/libs/prx/libkernel/System/src/MimallocMacOs.cpp deleted file mode 100644 index 3171d3b3d6..0000000000 --- a/core/libs/prx/libkernel/System/src/MimallocMacOs.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#ifdef __APPLE__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - -std::filesystem::path Allocator() { - Dl_info info{}; - if (dladdr(reinterpret_cast(&Allocator), &info) == 0 || info.dli_fname == nullptr) return {}; - return std::filesystem::path(info.dli_fname).parent_path() / "libmimalloc.dylib"; -} - -std::string Executable() { - std::uint32_t size = 0; - _NSGetExecutablePath(nullptr, &size); - std::vector executable(size); - if (_NSGetExecutablePath(executable.data(), &size) != 0) return {}; - return executable.data(); -} - -[[gnu::constructor]] void RestartWithMimalloc() { - const bool restarted = std::getenv("APS5_MIMALLOC_RESTARTED") != nullptr; - unsetenv("APS5_MIMALLOC_RESTARTED"); - if (std::getenv("APS5_MIMALLOC") == nullptr || dlsym(RTLD_DEFAULT, "mi_version") != nullptr) return; - if (restarted) { - std::fprintf(stderr, "[memory] APS5_MIMALLOC: dyld did not load libmimalloc.dylib, the title keeps the system allocator\n"); - return; - } - const auto allocator = Allocator(); - std::error_code error; - if (allocator.empty() || !std::filesystem::is_regular_file(allocator, error)) return; - const auto executable = Executable(); - if (executable.empty()) return; - const char* inserted = std::getenv("DYLD_INSERT_LIBRARIES"); - const std::string previous = inserted == nullptr ? std::string() : inserted; - const auto libraries = previous.empty() ? allocator.string() : allocator.string() + ":" + previous; - setenv("DYLD_INSERT_LIBRARIES", libraries.c_str(), 1); - setenv("APS5_MIMALLOC_RESTARTED", "1", 1); - execv(executable.c_str(), *_NSGetArgv()); - std::perror("[memory] APS5_MIMALLOC: execv"); - unsetenv("APS5_MIMALLOC_RESTARTED"); - if (inserted == nullptr) unsetenv("DYLD_INSERT_LIBRARIES"); - else setenv("DYLD_INSERT_LIBRARIES", previous.c_str(), 1); -} - -} - -#endif diff --git a/core/libs/prx/libkernel/System/src/MimallocRestart.cpp b/core/libs/prx/libkernel/System/src/MimallocRestart.cpp new file mode 100644 index 0000000000..3cc543cba4 --- /dev/null +++ b/core/libs/prx/libkernel/System/src/MimallocRestart.cpp @@ -0,0 +1,104 @@ +#if defined(__APPLE__) || defined(__linux__) + +#include "prx/libc/include/Shutdown.hpp" +#include +#include +#include +#include +#include +#include +#include +#ifdef __APPLE__ +#include +#include +#include +#else +#include +#endif + +namespace { + +#ifdef __APPLE__ +constexpr const char* Loader = "dyld"; +constexpr const char* AllocatorName = "libmimalloc.dylib"; +constexpr const char* PreloadVariable = "DYLD_INSERT_LIBRARIES"; +constexpr const char* PreloadSeparators = ":"; +#else +constexpr const char* Loader = "ld.so"; +constexpr const char* AllocatorName = "libmimalloc.so"; +constexpr const char* PreloadVariable = "LD_PRELOAD"; +constexpr const char* PreloadSeparators = ": "; +#endif + +std::filesystem::path Allocator() { + Dl_info info{}; + if (dladdr(reinterpret_cast(&Allocator), &info) == 0 || info.dli_fname == nullptr) return {}; + std::error_code error; + return std::filesystem::absolute(std::filesystem::path(info.dli_fname).parent_path() / AllocatorName, error); +} + +#ifdef __APPLE__ +std::string Executable() { + std::uint32_t size = 0; + _NSGetExecutablePath(nullptr, &size); + std::vector executable(size); + if (_NSGetExecutablePath(executable.data(), &size) != 0) return {}; + return executable.data(); +} + +std::vector Arguments() { + std::vector arguments; + for (char** argument = *_NSGetArgv(); *argument != nullptr; ++argument) arguments.emplace_back(*argument); + return arguments; +} +#else +std::string Executable() { + std::error_code error; + return std::filesystem::read_symlink("/proc/self/exe", error).string(); +} + +std::vector Arguments() { + std::ifstream file("/proc/self/cmdline", std::ios::binary); + std::vector arguments; + for (std::string argument; std::getline(file, argument, '\0');) arguments.push_back(argument); + return arguments; +} +#endif + +[[gnu::constructor(101)]] void RestartWithMimalloc() { + const bool restarted = std::getenv("APS5_MIMALLOC_RESTARTED") != nullptr; + unsetenv("APS5_MIMALLOC_RESTARTED"); + if (std::getenv("APS5_MIMALLOC") == nullptr || dlsym(RTLD_DEFAULT, "mi_version") != nullptr) return; + if (restarted) { + std::fprintf(stderr, "[memory] APS5_MIMALLOC: %s did not load %s, the title keeps the system allocator\n", Loader, AllocatorName); + return; + } + const auto allocator = Allocator(); + std::error_code error; + if (allocator.empty() || !std::filesystem::is_regular_file(allocator, error)) return; + if (allocator.string().find_first_of(PreloadSeparators) != std::string::npos) { + std::fprintf(stderr, "[memory] APS5_MIMALLOC: the path %s has a separator of %s, the title keeps the system allocator\n", allocator.c_str(), PreloadVariable); + return; + } + const auto executable = Executable(); + auto arguments = Arguments(); + if (executable.empty() || arguments.empty()) return; + std::vector values; + for (auto& argument : arguments) values.push_back(argument.data()); + values.push_back(nullptr); + const char* inserted = std::getenv(PreloadVariable); + const std::string previous = inserted == nullptr ? std::string() : inserted; + const auto libraries = previous.empty() ? allocator.string() : allocator.string() + ":" + previous; + setenv(PreloadVariable, libraries.c_str(), 1); + setenv("APS5_MIMALLOC_RESTARTED", "1", 1); + LogFileRelease_nid_no_patch(); + execv(executable.c_str(), values.data()); + std::perror("[memory] APS5_MIMALLOC: execv"); + unsetenv("APS5_MIMALLOC_RESTARTED"); + if (inserted == nullptr) unsetenv(PreloadVariable); + else setenv(PreloadVariable, previous.c_str(), 1); +} + +} + +#endif diff --git a/core/libs/tests/MimallocRestart.cpp b/core/libs/tests/MimallocRestart.cpp index 2d033bf463..d1e9675d7d 100644 --- a/core/libs/tests/MimallocRestart.cpp +++ b/core/libs/tests/MimallocRestart.cpp @@ -15,12 +15,12 @@ void Require(bool condition, const char* message) { } int main(int argc, char** argv) { - Require(argc == 2, "usage: mimalloc_restart_tests off|on|once"); + Require(argc == 2, "usage: mimalloc_restart_tests off|on|once|log"); const std::string_view mode = argv[1]; Require(std::getenv("APS5_MIMALLOC_RESTARTED") == nullptr, "the restart marker stayed in the environment"); using InHeap = bool (*)(const void*); const auto inHeap = reinterpret_cast(dlsym(RTLD_DEFAULT, "mi_is_in_heap_region")); - if (mode != "on") { + if (mode != "on" && mode != "log") { Require(inHeap == nullptr, "mimalloc was loaded without APS5_MIMALLOC or after a restart"); return 0; } @@ -31,5 +31,6 @@ int main(int argc, char** argv) { Require(inHeap(block), "malloc did not allocate through mimalloc"); Require(inHeap(object.get()) && inHeap(array.get()), "operator new did not allocate through mimalloc"); std::free(block); + if (mode == "log") std::printf("mimalloc_restart_tests: the restarted process writes to its standard output\n"); return 0; } diff --git a/docs/dev/ARCHITECTURE.md b/docs/dev/ARCHITECTURE.md index 92a2e5d7b0..833393ba70 100644 --- a/docs/dev/ARCHITECTURE.md +++ b/docs/dev/ARCHITECTURE.md @@ -83,15 +83,20 @@ On Windows, a relinked game run with `APS5_MIMALLOC` set (to any value) allocate - After the redirection these calls would enter mimalloc again, before the calling thread's mimalloc state exists. - Linker wraps send them to [`RuntimeHeap.c`](../../core/libs/allocator/RuntimeHeap.c), which allocates from the process heap (`HeapAlloc` on `GetProcessHeap()`). The same code frees these blocks through the same wraps. -## macOS allocator +## macOS and Linux allocator -On macOS, a relinked game run with `APS5_MIMALLOC` set (to any value) allocates through [mimalloc](../../3rdparty/mimalloc) instead of the system allocator: its libraries, the C and C++ runtime and the system frameworks they call. Without the variable the game keeps the system allocator. +On macOS and Linux, a relinked game run with `APS5_MIMALLOC` set (to any value) allocates through [mimalloc](../../3rdparty/mimalloc) instead of the system allocator: its libraries, the C and C++ runtime and the system libraries and frameworks they call, the Vulkan driver among them. Without the variable the game keeps the system allocator. -- The build copies `libmimalloc.dylib` beside the libraries ([`core/libs/CMakeLists.txt`](../../core/libs/CMakeLists.txt)). It keeps mimalloc's macOS defaults: it interposes the C allocation functions and `operator new` and `operator delete`, and replaces the default malloc zone. As on Windows, `mi_free` checks the pointers it gets. -- dyld interposes only for libraries loaded at launch, so mimalloc has to be in `DYLD_INSERT_LIBRARIES` when the process starts. When `APS5_MIMALLOC` is set and mimalloc is not loaded, a constructor in libkernel ([`MimallocMacOs.cpp`](../../core/libs/prx/libkernel/System/src/MimallocMacOs.cpp)) puts the `libmimalloc.dylib` beside libkernel first in `DYLD_INSERT_LIBRARIES`, sets `APS5_MIMALLOC_RESTARTED` and runs the same executable again with `execv`, with the same arguments and the same process ID. The restarted process removes `APS5_MIMALLOC_RESTARTED` from its environment. - - Without `libmimalloc.dylib` beside libkernel the title does not restart and keeps the system allocator. - - When dyld ignores `DYLD_INSERT_LIBRARIES`, as for an executable with the hardened runtime, the restarted process prints one line and keeps the system allocator instead of restarting again. - - `DYLD_INSERT_LIBRARIES` stays set, so a process the title starts also allocates through mimalloc. +- The build copies `libmimalloc.dylib` (macOS) or `libmimalloc.so` (Linux) beside the libraries ([`core/libs/CMakeLists.txt`](../../core/libs/CMakeLists.txt)). It keeps mimalloc's defaults: it interposes the C allocation functions and `operator new` and `operator delete`, on macOS also the default malloc zone and on Linux also glibc's `__libc_malloc` family. As on Windows, `mi_free` checks the pointers it gets. + - On Linux, glibc's `malloc_trim`, which the shader registry calls, then has nothing to trim; mimalloc returns freed memory to the system itself. + - A Linux build with `ANYPS5_ENABLE_ASAN` or `ANYPS5_ENABLE_TSAN` has no mimalloc: the sanitizer's runtime replaces the allocation functions itself and has to be the first library loaded. +- The dynamic linker binds the allocation functions to mimalloc only when it loads mimalloc at launch, from `DYLD_INSERT_LIBRARIES` (macOS) or `LD_PRELOAD` (Linux). When `APS5_MIMALLOC` is set and mimalloc is not loaded, a constructor in libkernel ([`MimallocRestart.cpp`](../../core/libs/prx/libkernel/System/src/MimallocRestart.cpp)) puts the mimalloc library beside libkernel first in that variable, sets `APS5_MIMALLOC_RESTARTED` and runs the same executable again with `execv`, with the same arguments and the same process ID. On Linux the executable is the target of `/proc/self/exe` and the arguments are those of `/proc/self/cmdline`, so a title started through `ld.so` starts through it again. The restarted process removes `APS5_MIMALLOC_RESTARTED` from its environment. + - The constructor has priority 101, so it runs before libkernel's other initializers, which have no priority, and none of their work is done twice or carried into the restarted process. It calls only the host C and C++ runtimes and libc, separate libraries that libkernel depends on, so they have initialized before it. + - libc's `ANYPS5_LOG_FILE` copy has taken the standard streams by then. libkernel gives them back before `execv`, as LoadExec does, and the restarted process starts the copy again. + - Without the mimalloc library beside libkernel the title does not restart and keeps the system allocator. + - The variable cannot list a path that has its separator, a colon (and on Linux a space): the title prints one line and keeps the system allocator. + - When the dynamic linker ignores the variable, as for an executable with the hardened runtime on macOS or one that runs in secure-execution mode on Linux (set-user-ID or with file capabilities), the restarted process prints one line and keeps the system allocator instead of restarting again. + - The variable stays set, so a process the title starts also allocates through mimalloc. - mimalloc asks for its arenas at addresses from 2 TiB up, above the guest arena's range; the few blocks it maps without that hint come from the system like any other host mapping. Guest code allocates through libc's guest heap, as on Windows. ## Thread cancellation diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index 5ba567b648..5e87f7fcc1 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -663,8 +663,8 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_GAME_MODE` | string | `core/libs/prx/libkernel/System/src/HostPerformanceProcess.cpp` | off | asks the host for its game or performance mode while the title runs, read when libkernel loads: `off`, `on` or `auto`; on Linux it requests Feral GameMode (`libgamemode.so.0`, loaded with dlopen) and on macOS it holds a user-initiated, latency-critical `NSProcessInfo` activity; `on` logs a missing library or backend once, `auto` stays silent about it, and a refused request is logged once in both; nothing here is fatal, and any other value stops the title with an error naming the variable | | `APS5_JOB_AFFINITY` | presence | `core/libs/prx/libkernel/Pthread/src/Thread.cpp` | | on Windows, pins the title's job worker threads to the efficiency cores of a hybrid CPU | | `APS5_JOB_AFFINITY_MASK` | integer | `core/libs/prx/libkernel/Pthread/src/Thread.cpp` | | on Windows, hex core mask that job worker threads are pinned to | -| `APS5_MIMALLOC` | presence | `core/libs/prx/libkernel/System/src/MimallocMacOs.cpp` | | makes the process allocate through mimalloc instead of the system allocator: on Windows the entry stub (`core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp`) loads `mimalloc.dll`, on macOS libkernel restarts the title with `libmimalloc.dylib` in `DYLD_INSERT_LIBRARIES` | -| `APS5_MIMALLOC_RESTARTED` | presence | `core/libs/prx/libkernel/System/src/MimallocMacOs.cpp` | | on macOS, set by libkernel for the restart that loads mimalloc and removed at start-up, so a restart that did not load it is not repeated | +| `APS5_MIMALLOC` | presence | `core/libs/prx/libkernel/System/src/MimallocRestart.cpp` | | makes the process allocate through mimalloc instead of the system allocator: on Windows the entry stub (`core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp`) loads `mimalloc.dll`, on macOS libkernel restarts the title with `libmimalloc.dylib` in `DYLD_INSERT_LIBRARIES` and on Linux with `libmimalloc.so` in `LD_PRELOAD` | +| `APS5_MIMALLOC_RESTARTED` | presence | `core/libs/prx/libkernel/System/src/MimallocRestart.cpp` | | on macOS and Linux, set by libkernel for the restart that loads mimalloc and removed at start-up, so a restart that did not load it is not repeated | | `APS5_NO_DIRECT_MEMORY_SECTION_POOL` | presence | `core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp` | | on Windows, creates a section for every direct-memory allocation and closes it at release instead of reusing released sections that never held guest data | | `APS5_NO_JOB_AFFINITY` | presence | `core/libs/prx/libkernel/Pthread/src/Thread.cpp` | | on Windows, disables pinning of job worker threads | | `APS5_NO_SLEEP_WAKE` | presence | `core/libs/prx/libkernel/Time/SleepWake.cpp` | | keeps every sleep of 1 ms or less in full instead of ending it at a GPU batch, batch completion action or flip completion | diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 7ee8d74fba..9c4a7c94f4 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -379,3 +379,4 @@ - [send and recv](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) pass the guest buffer to the host call without a guest copy helper, so an unreadable or unwritable buffer is whatever the host does (a host fault signal is possible), where `sendto` and `recvfrom` fail with `EFAULT`. On Windows a stream receive whose writable prefix is shorter than the queued data fails with `EFAULT`, but data arriving after the check, or `MSG_WAITALL`, can return the prefix as a short success where FreeBSD fails. Linux and macOS return the bytes copied before a fault on a stream, where FreeBSD returns `EFAULT`. - [equeue pending-event bound](../../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) - an undrained registration keeps the armed event plus at most 64 queued events: a trigger past the cap drops the oldest queued event and keeps the latest payload, and an `AddEvent` carrying more than 64 queued events keeps the latest 64. Drained bursts report as before. The console's queue depth and which completion it keeps are unknown - [title sandbox app0 alias](../../../core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp) (libkernel) - aliases `/mnt/sandbox/_000/app0` to the application directory `/app0` when metadata is loaded; console jail sandbox mounts for other virtual directories under `/mnt/sandbox` remain unmeasured. +- [mimalloc restart](../../../core/libs/prx/libkernel/System/src/MimallocRestart.cpp) (libkernel) - when the `execv` that restarts the title with `APS5_MIMALLOC` fails, the title prints the error and goes on with the system allocator, but an `ANYPS5_LOG_FILE` copy stays stopped: libkernel gave the standard streams back to the terminal before the `execv`, and libc cannot take them again, so the rest of the output reaches the terminal only. LoadExec stops the title when its `execv` fails. diff --git a/docs/user/USAGE.md b/docs/user/USAGE.md index 99ec93356a..6189515646 100644 --- a/docs/user/USAGE.md +++ b/docs/user/USAGE.md @@ -259,6 +259,8 @@ On Windows, a self-built `libs/` also needs `libgcc_s_seh-1.dll`, `libstdc++-6.d On macOS, with `APS5_MIMALLOC` set, the title starts again at once with `libmimalloc.dylib` from `libs/` loaded and allocates through mimalloc; the self-built `build/core/libs/libs` already contains it. Without the file the title keeps the system allocator and prints nothing. +On Linux, with `APS5_MIMALLOC` set, the title starts again at once with `libmimalloc.so` from `libs/` in `LD_PRELOAD` and allocates through mimalloc; the release archives and the self-built `build/core/libs/libs` contain it. Without the file the title keeps the system allocator and prints nothing. A `libs/` path with a space or a colon cannot be listed in `LD_PRELOAD`: the title then prints one line and keeps the system allocator. + On Windows, direct memory (`sceKernelAllocateDirectMemory`, up to 13824 MiB per title) is committed when the title maps it, not when the title allocates it or first uses its pages. The system commit limit (installed memory plus page file size, the second value of Committed in Task Manager) must cover the mapped direct memory together with all other committed memory. Otherwise `sceKernelMapDirectMemory` throws `replace guest placeholder with resident direct memory` with the Windows error (`commit direct memory pages` when a page is mapped at a second address, `keep unmapped direct memory` when it is unmapped); enlarge the page file or close other applications. On Linux, when the Vulkan driver imports dma-buf memory (not the NVIDIA proprietary driver), shared direct memory is imported through `/dev/udmabuf`, and the user who runs the game needs read-write access to it. Many distributions create it as `root:kvm` with mode `0660`: add the user to the `kvm` group and log in again (an ACL such as `setfacl -m u:$USER:rw /dev/udmabuf` lasts until the next reboot). Without access, startup prints `[gpu] open /dev/udmabuf: Permission denied`, these ranges are copied instead of imported, and GPU stores to them through FLAT/GLOBAL addresses fail with `BDA access failed`. A range larger than 64 MiB is imported as one sparse buffer over 64 MiB udmabuf buffers when the device binds sparse memory (RADV, ANV). Without sparse binding, or when the device's sparse block size is larger than 64 KiB, ranges above udmabuf's `size_limit_mb` (64 MiB by default) are copied as well. diff --git a/tools/package_release.py b/tools/package_release.py index c143ee7d8d..ae237d451a 100644 --- a/tools/package_release.py +++ b/tools/package_release.py @@ -90,6 +90,8 @@ def package(platform, build, output, version): runtime = windows_runtime(build) files.extend(runtime / name for name in ("libgcc_s_seh-1.dll", "libstdc++-6.dll", "libwinpthread-1.dll")) files.extend(build / "core/libs/libs" / name for name in ("mimalloc.dll", "mimalloc-redirect.dll")) + else: + files.append(build / "core/libs/libs/libmimalloc.so") files.extend(tracy_runtime(platform, build)) unpatched = [(build / "core/libs/libs/unpatched" / name, "libs/" + name) for name in sorted(expected)] unpatched.extend((file, "libs/" + file.name) for file in files if file.suffix != ".prx") diff --git a/tools/tests/test_package_release.py b/tools/tests/test_package_release.py index ded9c3b83c..0d6e13668b 100644 --- a/tools/tests/test_package_release.py +++ b/tools/tests/test_package_release.py @@ -135,6 +135,8 @@ class ReleasePackageTests(unittest.TestCase): libraries.mkdir(parents=True) self.library = libraries / "libc.prx" self.library.write_bytes(b"library bytes") + self.allocator = libraries / "libmimalloc.so" + self.allocator.write_bytes(b"allocator bytes") (libraries / "unpatched").mkdir() self.unpatched = libraries / "unpatched/libc.prx" self.unpatched.write_bytes(b"original exports") @@ -154,10 +156,10 @@ class ReleasePackageTests(unittest.TestCase): before = {path: (path.read_bytes(), path.stat().st_mtime_ns) for path in (self.library, self.unpatched, self.implib)} package_release.package("linux", self.build, self.output, "v1") with zipfile.ZipFile(self.output / "prx-linux-unpatched-v1.zip") as archive: - self.assertEqual(archive.namelist(), ["libs/libc.prx"]) + self.assertEqual(archive.namelist(), ["libs/libc.prx", "libs/libmimalloc.so"]) self.assertEqual(archive.read("libs/libc.prx"), b"original exports") with tarfile.open(self.output / "prx-linux-unpatched-v1.tar.gz") as archive: - self.assertEqual(archive.getnames(), ["libs/libc.prx"]) + self.assertEqual(archive.getnames(), ["libs/libc.prx", "libs/libmimalloc.so"]) self.assertEqual(archive.extractfile("libs/libc.prx").read(), b"original exports") for path, (content, timestamp) in before.items(): self.assertEqual(path.read_bytes(), content) @@ -203,6 +205,24 @@ class ReleasePackageTests(unittest.TestCase): self.assertEqual(list(self.output.iterdir()), [previous]) self.assertEqual(previous.read_bytes(), b"previous release") + def test_linux_archives_include_mimalloc(self): + package_release.package("linux", self.build, self.output, "v1") + for stem in ("prx-linux-v1", "prx-linux-unpatched-v1"): + with zipfile.ZipFile(self.output / (stem + ".zip")) as archive: + self.assertEqual(archive.read("libs/libmimalloc.so"), b"allocator bytes") + with tarfile.open(self.output / (stem + ".tar.gz")) as archive: + self.assertEqual(archive.extractfile("libs/libmimalloc.so").read(), b"allocator bytes") + + def test_linux_package_without_mimalloc_preserves_existing_outputs(self): + self.allocator.unlink() + self.output.mkdir() + previous = self.output / "prx-linux-v1.zip" + previous.write_bytes(b"previous release") + with self.assertRaisesRegex(RuntimeError, "Missing or empty release file: .*libmimalloc\\.so"): + package_release.package("linux", self.build, self.output, "v1") + self.assertEqual(list(self.output.iterdir()), [previous]) + self.assertEqual(previous.read_bytes(), b"previous release") + def test_ajm_vibration_decoder_license_in_archives(self): (self.source / "core/libs/prx/libSceAjm.native").mkdir() (self.unpatched.parent / "libSceAjm.native.prx").write_bytes(b"unpatched library") @@ -339,6 +359,8 @@ class ReleasePackageTests(unittest.TestCase): client.write_bytes(platform.encode() + b" Tracy client") cache = "APS5_ENABLE_TRACY:BOOL=ON\n" expected = {"libs/libc.prx", "libs/" + name} + if platform == "linux": + expected.add("libs/libmimalloc.so") if platform == "windows": runtime = self.root / "toolchain" runtime.mkdir() @@ -368,7 +390,7 @@ class ReleasePackageTests(unittest.TestCase): output = self.output / str(index) package_release.package("linux", self.build, output, "v1") with zipfile.ZipFile(output / "prx-linux-v1.zip") as archive: - self.assertEqual(archive.namelist(), ["libs/libc.prx"]) + self.assertEqual(archive.namelist(), ["libs/libc.prx", "libs/libmimalloc.so"]) def test_missing_or_ambiguous_tracy_client_preserves_existing_assets(self): (self.build / "CMakeCache.txt").write_text("APS5_ENABLE_TRACY:BOOL=ON\n", encoding="utf-8") @@ -396,17 +418,17 @@ class ReleasePackageTests(unittest.TestCase): with working_directory(self.source): package_release.package("linux", self.build, self.output, "v1.0") with zipfile.ZipFile(self.output / "prx-linux-v1.0.zip") as archive: - self.assertEqual(archive.namelist(), ["libs/libc.prx"]) + self.assertEqual(archive.namelist(), ["libs/libc.prx", "libs/libmimalloc.so"]) def test_removed_libraries_are_not_packaged_from_a_reused_build(self): stale = self.library.with_name("libRemoved.prx") stale.write_bytes(b"obsolete library") package_release.package("linux", self.build, self.output, "v1.0") with zipfile.ZipFile(self.output / "prx-linux-v1.0.zip") as archive: - self.assertEqual(archive.namelist(), ["libs/libc.prx"]) + self.assertEqual(archive.namelist(), ["libs/libc.prx", "libs/libmimalloc.so"]) self.assertEqual(archive.read("libs/libc.prx"), b"library bytes") with tarfile.open(self.output / "prx-linux-v1.0.tar.gz") as archive: - self.assertEqual(archive.getnames(), ["libs/libc.prx"]) + self.assertEqual(archive.getnames(), ["libs/libc.prx", "libs/libmimalloc.so"]) self.assertEqual(archive.extractfile("libs/libc.prx").read(), b"library bytes") self.assertEqual(stale.read_bytes(), b"obsolete library") @@ -736,6 +758,7 @@ class ReleaseAssetsTests(unittest.TestCase): build = root / "build" (build / "core/libs/libs").mkdir(parents=True) (build / "core/libs/libs/libc.prx").write_bytes(b"new library") + (build / "core/libs/libs/libmimalloc.so").write_bytes(b"new allocator") (build / "core/libs/libs/unpatched").mkdir() (build / "core/libs/libs/unpatched/libc.prx").write_bytes(b"original library") (build / "core/relinker").mkdir(parents=True) From cd4549d0cc7790dcc000d1613b6e9a3eaf0a3e5a Mon Sep 17 00:00:00 2001 From: Cesar Mora Date: Sun, 1 Nov 2026 16:51:02 -0600 Subject: [PATCH 317/612] fix(libs): ship mimalloc's licence and document the inherited preload The release archives carry mimalloc's binaries on Linux and Windows, so they now also carry its MIT licence as libs/licenses/mimalloc.txt, and packaging fails without it. USAGE.md and ENVIRONMENT.md say that APS5_MIMALLOC and LD_PRELOAD stay set after the restart and state the memory cost measured with it, and the restart's debt entry says that only the log-file copy is handed back before execv. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015W3M6NbYpmwWkzkL8YNLaF --- docs/dev/ENVIRONMENT.md | 2 +- docs/dev/debt/libkernel.md | 2 +- docs/user/USAGE.md | 2 +- tools/package_release.py | 1 + tools/tests/test_package_release.py | 41 ++++++++++++++++++++++------- 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index 5e87f7fcc1..1bf61095d6 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -663,7 +663,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_GAME_MODE` | string | `core/libs/prx/libkernel/System/src/HostPerformanceProcess.cpp` | off | asks the host for its game or performance mode while the title runs, read when libkernel loads: `off`, `on` or `auto`; on Linux it requests Feral GameMode (`libgamemode.so.0`, loaded with dlopen) and on macOS it holds a user-initiated, latency-critical `NSProcessInfo` activity; `on` logs a missing library or backend once, `auto` stays silent about it, and a refused request is logged once in both; nothing here is fatal, and any other value stops the title with an error naming the variable | | `APS5_JOB_AFFINITY` | presence | `core/libs/prx/libkernel/Pthread/src/Thread.cpp` | | on Windows, pins the title's job worker threads to the efficiency cores of a hybrid CPU | | `APS5_JOB_AFFINITY_MASK` | integer | `core/libs/prx/libkernel/Pthread/src/Thread.cpp` | | on Windows, hex core mask that job worker threads are pinned to | -| `APS5_MIMALLOC` | presence | `core/libs/prx/libkernel/System/src/MimallocRestart.cpp` | | makes the process allocate through mimalloc instead of the system allocator: on Windows the entry stub (`core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp`) loads `mimalloc.dll`, on macOS libkernel restarts the title with `libmimalloc.dylib` in `DYLD_INSERT_LIBRARIES` and on Linux with `libmimalloc.so` in `LD_PRELOAD` | +| `APS5_MIMALLOC` | presence | `core/libs/prx/libkernel/System/src/MimallocRestart.cpp` | | makes the process allocate through mimalloc instead of the system allocator: on Windows the entry stub (`core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp`) loads `mimalloc.dll`, on macOS libkernel restarts the title with `libmimalloc.dylib` in `DYLD_INSERT_LIBRARIES` and on Linux with `libmimalloc.so` in `LD_PRELOAD`; on macOS and Linux both variables stay set after the restart, so processes the title starts inherit them | | `APS5_MIMALLOC_RESTARTED` | presence | `core/libs/prx/libkernel/System/src/MimallocRestart.cpp` | | on macOS and Linux, set by libkernel for the restart that loads mimalloc and removed at start-up, so a restart that did not load it is not repeated | | `APS5_NO_DIRECT_MEMORY_SECTION_POOL` | presence | `core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp` | | on Windows, creates a section for every direct-memory allocation and closes it at release instead of reusing released sections that never held guest data | | `APS5_NO_JOB_AFFINITY` | presence | `core/libs/prx/libkernel/Pthread/src/Thread.cpp` | | on Windows, disables pinning of job worker threads | diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 9c4a7c94f4..fd29068d4c 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -379,4 +379,4 @@ - [send and recv](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) pass the guest buffer to the host call without a guest copy helper, so an unreadable or unwritable buffer is whatever the host does (a host fault signal is possible), where `sendto` and `recvfrom` fail with `EFAULT`. On Windows a stream receive whose writable prefix is shorter than the queued data fails with `EFAULT`, but data arriving after the check, or `MSG_WAITALL`, can return the prefix as a short success where FreeBSD fails. Linux and macOS return the bytes copied before a fault on a stream, where FreeBSD returns `EFAULT`. - [equeue pending-event bound](../../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) - an undrained registration keeps the armed event plus at most 64 queued events: a trigger past the cap drops the oldest queued event and keeps the latest payload, and an `AddEvent` carrying more than 64 queued events keeps the latest 64. Drained bursts report as before. The console's queue depth and which completion it keeps are unknown - [title sandbox app0 alias](../../../core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp) (libkernel) - aliases `/mnt/sandbox/_000/app0` to the application directory `/app0` when metadata is loaded; console jail sandbox mounts for other virtual directories under `/mnt/sandbox` remain unmeasured. -- [mimalloc restart](../../../core/libs/prx/libkernel/System/src/MimallocRestart.cpp) (libkernel) - when the `execv` that restarts the title with `APS5_MIMALLOC` fails, the title prints the error and goes on with the system allocator, but an `ANYPS5_LOG_FILE` copy stays stopped: libkernel gave the standard streams back to the terminal before the `execv`, and libc cannot take them again, so the rest of the output reaches the terminal only. LoadExec stops the title when its `execv` fails. +- [mimalloc restart](../../../core/libs/prx/libkernel/System/src/MimallocRestart.cpp) (libkernel) - when the `execv` that restarts the title with `APS5_MIMALLOC` fails, the title prints the error and goes on with the system allocator, but an `ANYPS5_LOG_FILE` copy stays stopped: libkernel gave the standard streams back to the terminal before the `execv`, and libc cannot take them again, so the rest of the output reaches the terminal only. LoadExec stops the title when its `execv` fails. Only that log-file copy is handed back before the `execv`: a descriptor without close-on-exec or a lock that another libc initializer took before libkernel's constructor runs is carried into the restarted process. diff --git a/docs/user/USAGE.md b/docs/user/USAGE.md index 6189515646..a0adcd4266 100644 --- a/docs/user/USAGE.md +++ b/docs/user/USAGE.md @@ -259,7 +259,7 @@ On Windows, a self-built `libs/` also needs `libgcc_s_seh-1.dll`, `libstdc++-6.d On macOS, with `APS5_MIMALLOC` set, the title starts again at once with `libmimalloc.dylib` from `libs/` loaded and allocates through mimalloc; the self-built `build/core/libs/libs` already contains it. Without the file the title keeps the system allocator and prints nothing. -On Linux, with `APS5_MIMALLOC` set, the title starts again at once with `libmimalloc.so` from `libs/` in `LD_PRELOAD` and allocates through mimalloc; the release archives and the self-built `build/core/libs/libs` contain it. Without the file the title keeps the system allocator and prints nothing. A `libs/` path with a space or a colon cannot be listed in `LD_PRELOAD`: the title then prints one line and keeps the system allocator. +On Linux, with `APS5_MIMALLOC` set, the title starts again at once with `libmimalloc.so` from `libs/` in `LD_PRELOAD` and allocates through mimalloc; the release archives and the self-built `build/core/libs/libs` contain it. Without the file the title keeps the system allocator and prints nothing. A `libs/` path with a space or a colon cannot be listed in `LD_PRELOAD`: the title then prints one line and keeps the system allocator. After the restart `APS5_MIMALLOC` and `LD_PRELOAD` stay set, so the title's own environment and every process it starts carry them. mimalloc keeps more memory than the system allocator: in ASTRO BOT's Aerial Garden about 0.9 GiB less host memory was available with it. On Windows, direct memory (`sceKernelAllocateDirectMemory`, up to 13824 MiB per title) is committed when the title maps it, not when the title allocates it or first uses its pages. The system commit limit (installed memory plus page file size, the second value of Committed in Task Manager) must cover the mapped direct memory together with all other committed memory. Otherwise `sceKernelMapDirectMemory` throws `replace guest placeholder with resident direct memory` with the Windows error (`commit direct memory pages` when a page is mapped at a second address, `keep unmapped direct memory` when it is unmapped); enlarge the page file or close other applications. diff --git a/tools/package_release.py b/tools/package_release.py index ae237d451a..a8c369b84e 100644 --- a/tools/package_release.py +++ b/tools/package_release.py @@ -92,6 +92,7 @@ def package(platform, build, output, version): files.extend(build / "core/libs/libs" / name for name in ("mimalloc.dll", "mimalloc-redirect.dll")) else: files.append(build / "core/libs/libs/libmimalloc.so") + licenses.append((ROOT / "3rdparty/mimalloc/LICENSE", "libs/licenses/mimalloc.txt")) files.extend(tracy_runtime(platform, build)) unpatched = [(build / "core/libs/libs/unpatched" / name, "libs/" + name) for name in sorted(expected)] unpatched.extend((file, "libs/" + file.name) for file in files if file.suffix != ".prx") diff --git a/tools/tests/test_package_release.py b/tools/tests/test_package_release.py index 0d6e13668b..fce673a536 100644 --- a/tools/tests/test_package_release.py +++ b/tools/tests/test_package_release.py @@ -137,6 +137,9 @@ class ReleasePackageTests(unittest.TestCase): self.library.write_bytes(b"library bytes") self.allocator = libraries / "libmimalloc.so" self.allocator.write_bytes(b"allocator bytes") + self.allocator_license = self.source / "3rdparty/mimalloc/LICENSE" + self.allocator_license.parent.mkdir(parents=True) + self.allocator_license.write_bytes(b"allocator license") (libraries / "unpatched").mkdir() self.unpatched = libraries / "unpatched/libc.prx" self.unpatched.write_bytes(b"original exports") @@ -156,10 +159,10 @@ class ReleasePackageTests(unittest.TestCase): before = {path: (path.read_bytes(), path.stat().st_mtime_ns) for path in (self.library, self.unpatched, self.implib)} package_release.package("linux", self.build, self.output, "v1") with zipfile.ZipFile(self.output / "prx-linux-unpatched-v1.zip") as archive: - self.assertEqual(archive.namelist(), ["libs/libc.prx", "libs/libmimalloc.so"]) + self.assertEqual(archive.namelist(), ["libs/libc.prx", "libs/libmimalloc.so", "libs/licenses/mimalloc.txt"]) self.assertEqual(archive.read("libs/libc.prx"), b"original exports") with tarfile.open(self.output / "prx-linux-unpatched-v1.tar.gz") as archive: - self.assertEqual(archive.getnames(), ["libs/libc.prx", "libs/libmimalloc.so"]) + self.assertEqual(archive.getnames(), ["libs/libc.prx", "libs/libmimalloc.so", "libs/licenses/mimalloc.txt"]) self.assertEqual(archive.extractfile("libs/libc.prx").read(), b"original exports") for path, (content, timestamp) in before.items(): self.assertEqual(path.read_bytes(), content) @@ -223,6 +226,24 @@ class ReleasePackageTests(unittest.TestCase): self.assertEqual(list(self.output.iterdir()), [previous]) self.assertEqual(previous.read_bytes(), b"previous release") + def test_mimalloc_license_in_archives(self): + package_release.package("linux", self.build, self.output, "v1") + for stem in ("prx-linux-v1", "prx-linux-unpatched-v1"): + with zipfile.ZipFile(self.output / (stem + ".zip")) as archive: + self.assertEqual(archive.read("libs/licenses/mimalloc.txt"), b"allocator license") + with tarfile.open(self.output / (stem + ".tar.gz")) as archive: + self.assertEqual(archive.extractfile("libs/licenses/mimalloc.txt").read(), b"allocator license") + + def test_package_without_mimalloc_license_preserves_existing_outputs(self): + self.allocator_license.unlink() + self.output.mkdir() + previous = self.output / "prx-linux-v1.zip" + previous.write_bytes(b"previous release") + with self.assertRaisesRegex(RuntimeError, r"Missing or empty release file: .*mimalloc[\\/]LICENSE"): + package_release.package("linux", self.build, self.output, "v1") + self.assertEqual(list(self.output.iterdir()), [previous]) + self.assertEqual(previous.read_bytes(), b"previous release") + def test_ajm_vibration_decoder_license_in_archives(self): (self.source / "core/libs/prx/libSceAjm.native").mkdir() (self.unpatched.parent / "libSceAjm.native.prx").write_bytes(b"unpatched library") @@ -266,7 +287,7 @@ class ReleasePackageTests(unittest.TestCase): (self.build / "core/relinker/relinker.exe").write_bytes(b"Windows executable") package_release.package("windows", self.build, self.output, "v1") with zipfile.ZipFile(self.output / "prx-windows-v1.zip") as archive: - self.assertEqual(set(archive.namelist()), {"libs/libc.prx", *("libs/" + name for name in names + allocator)}) + self.assertEqual(set(archive.namelist()), {"libs/libc.prx", "libs/licenses/mimalloc.txt", *("libs/" + name for name in names + allocator)}) for name in names + allocator: self.assertEqual(archive.read("libs/" + name), name.encode()) with tarfile.open(self.output / "prx-windows-v1.tar.gz") as archive: @@ -276,7 +297,7 @@ class ReleasePackageTests(unittest.TestCase): with zipfile.ZipFile(self.output / "prx-windows-unpatched-v1.zip") as archive: self.assertEqual(archive.read("libs/libc.prx"), b"original exports") self.assertEqual(archive.read("libs/implib/libc.dll.a"), b"import archive") - self.assertEqual(set(archive.namelist()), {"libs/libc.prx", "libs/implib/libc.dll.a", *("libs/" + name for name in names + allocator)}) + self.assertEqual(set(archive.namelist()), {"libs/libc.prx", "libs/implib/libc.dll.a", "libs/licenses/mimalloc.txt", *("libs/" + name for name in names + allocator)}) with tarfile.open(self.output / "prx-windows-unpatched-v1.tar.gz") as archive: self.assertEqual(archive.extractfile("libs/libc.prx").read(), b"original exports") self.assertEqual(archive.extractfile("libs/implib/libc.dll.a").read(), b"import archive") @@ -358,7 +379,7 @@ class ReleasePackageTests(unittest.TestCase): client = directory / name client.write_bytes(platform.encode() + b" Tracy client") cache = "APS5_ENABLE_TRACY:BOOL=ON\n" - expected = {"libs/libc.prx", "libs/" + name} + expected = {"libs/libc.prx", "libs/" + name, "libs/licenses/mimalloc.txt"} if platform == "linux": expected.add("libs/libmimalloc.so") if platform == "windows": @@ -390,7 +411,7 @@ class ReleasePackageTests(unittest.TestCase): output = self.output / str(index) package_release.package("linux", self.build, output, "v1") with zipfile.ZipFile(output / "prx-linux-v1.zip") as archive: - self.assertEqual(archive.namelist(), ["libs/libc.prx", "libs/libmimalloc.so"]) + self.assertEqual(archive.namelist(), ["libs/libc.prx", "libs/libmimalloc.so", "libs/licenses/mimalloc.txt"]) def test_missing_or_ambiguous_tracy_client_preserves_existing_assets(self): (self.build / "CMakeCache.txt").write_text("APS5_ENABLE_TRACY:BOOL=ON\n", encoding="utf-8") @@ -418,17 +439,17 @@ class ReleasePackageTests(unittest.TestCase): with working_directory(self.source): package_release.package("linux", self.build, self.output, "v1.0") with zipfile.ZipFile(self.output / "prx-linux-v1.0.zip") as archive: - self.assertEqual(archive.namelist(), ["libs/libc.prx", "libs/libmimalloc.so"]) + self.assertEqual(archive.namelist(), ["libs/libc.prx", "libs/libmimalloc.so", "libs/licenses/mimalloc.txt"]) def test_removed_libraries_are_not_packaged_from_a_reused_build(self): stale = self.library.with_name("libRemoved.prx") stale.write_bytes(b"obsolete library") package_release.package("linux", self.build, self.output, "v1.0") with zipfile.ZipFile(self.output / "prx-linux-v1.0.zip") as archive: - self.assertEqual(archive.namelist(), ["libs/libc.prx", "libs/libmimalloc.so"]) + self.assertEqual(archive.namelist(), ["libs/libc.prx", "libs/libmimalloc.so", "libs/licenses/mimalloc.txt"]) self.assertEqual(archive.read("libs/libc.prx"), b"library bytes") with tarfile.open(self.output / "prx-linux-v1.0.tar.gz") as archive: - self.assertEqual(archive.getnames(), ["libs/libc.prx", "libs/libmimalloc.so"]) + self.assertEqual(archive.getnames(), ["libs/libc.prx", "libs/libmimalloc.so", "libs/licenses/mimalloc.txt"]) self.assertEqual(archive.extractfile("libs/libc.prx").read(), b"library bytes") self.assertEqual(stale.read_bytes(), b"obsolete library") @@ -755,6 +776,8 @@ class ReleaseAssetsTests(unittest.TestCase): root = Path(tmp) source = root / "source" (source / "core/libs/prx/libc").mkdir(parents=True) + (source / "3rdparty/mimalloc").mkdir(parents=True) + (source / "3rdparty/mimalloc/LICENSE").write_bytes(b"allocator license") build = root / "build" (build / "core/libs/libs").mkdir(parents=True) (build / "core/libs/libs/libc.prx").write_bytes(b"new library") From dd5ee9e4c66d8fb716b0d6426fd6bd94822f58eb Mon Sep 17 00:00:00 2001 From: javierUDP Date: Wed, 4 Nov 2026 03:23:23 -0300 Subject: [PATCH 318/612] fix(libkernel): keep the process start block mapped when the main stack moves On macOS under Rosetta, Aps5StartGuest moves the main thread off the host stack and releases the old range to the guest arena. dyld keeps its own pointers into the process start block at the top of that range and offers no call that redirects them, where argv and environ have one: _NSGetExecutablePath copies the path string out of the block on every call, so the first call after the move read unmapped memory and the process died with KERN_PROTECTION_FAILURE. A title does not have to call it: libxpc reaches it from xpc_connection_resume, and ShaderCacheDirectory calls it for the shader disk cache. Release the old stack only up to the pages that hold the block, and only while argv still points into the stack. They are the top two pages, 8 KiB of the 8372224-byte stack here, so a title that fixes a mapping over that range now fails with a host region overlap; the rest is free as before. guest_main_stack_move asked for a 64 KiB guest mapping aligned down from a local of main, which falls inside the kept pages. It now probes the 64 KiB below them, requires the kept pages to be refused, and calls _NSGetExecutablePath after the move, which segfaults without this change. --- .../libkernel/Pthread/src/MainThreadMacOs.cpp | 20 ++++++++++++---- core/libs/tests/GuestMainStackMove.cpp | 24 ++++++++++++++++--- docs/dev/debt/libkernel.md | 2 +- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libkernel/Pthread/src/MainThreadMacOs.cpp b/core/libs/prx/libkernel/Pthread/src/MainThreadMacOs.cpp index a86e2fbcba..441bd64db0 100644 --- a/core/libs/prx/libkernel/Pthread/src/MainThreadMacOs.cpp +++ b/core/libs/prx/libkernel/Pthread/src/MainThreadMacOs.cpp @@ -34,6 +34,7 @@ constexpr std::size_t MovedStackBytes = std::size_t {16} << 20; std::atomic mainLoopRunning {false}; GuestStart guestStart; HostStack oldStack; +std::uintptr_t keptLow = 0; std::size_t guestStackBytes = 0; void* RunGuest(void* argument) { @@ -47,11 +48,14 @@ void KeepAlive(CFRunLoopTimerRef, void*) {} [[noreturn]] void ServeMainQueue(void*) { if (oldStack.high != oldStack.low) { - if (mach_vm_deallocate(mach_task_self(), oldStack.low, oldStack.high - oldStack.low) != KERN_SUCCESS) { - std::fprintf(stderr, "cannot release the host stack\n"); - std::abort(); + const std::uintptr_t end = keptLow > oldStack.low && keptLow < oldStack.high ? keptLow : oldStack.high; + if (end > oldStack.low) { + if (mach_vm_deallocate(mach_task_self(), oldStack.low, end - oldStack.low) != KERN_SUCCESS) { + std::fprintf(stderr, "cannot release the host stack\n"); + std::abort(); + } + GuestArena::GuestArenaReleaseHostRegion_nid_no_patch(reinterpret_cast(oldStack.low), end - oldStack.low); } - GuestArena::GuestArenaReleaseHostRegion_nid_no_patch(reinterpret_cast(oldStack.low), oldStack.high - oldStack.low); } pthread_attr_t attributes; if (pthread_attr_init(&attributes) != 0) std::abort(); @@ -122,6 +126,13 @@ void* CopyBlock(void* block) { return copy; } +std::uintptr_t ProcessStartBlockLow() { + const auto argv = reinterpret_cast(*_NSGetArgv()); + if (argv <= oldStack.low || argv >= oldStack.high) return 0; + const auto page = static_cast(getpagesize()); + return (argv - sizeof(char*)) & ~(page - 1); +} + [[noreturn]] void RunOnStack(void* top, void (*function)(void*)) { asm volatile("mov %0, %%rsp\n\txor %%edi, %%edi\n\tcall *%1\n\tud2" : : "r"(top), "r"(function) : "rdi", "memory"); __builtin_unreachable(); @@ -144,6 +155,7 @@ void MoveHostStack() { oldStack = {}; return; } + keptLow = ProcessStartBlockLow(); guestStart.block = CopyBlock(guestStart.block); *_NSGetEnviron() = CopyStrings(*_NSGetEnviron()); *_NSGetArgv() = CopyStrings(*_NSGetArgv()); diff --git a/core/libs/tests/GuestMainStackMove.cpp b/core/libs/tests/GuestMainStackMove.cpp index 1030bb792a..cd775b4d1d 100644 --- a/core/libs/tests/GuestMainStackMove.cpp +++ b/core/libs/tests/GuestMainStackMove.cpp @@ -1,5 +1,6 @@ #include "prx/libc/include/GuestArena.hpp" #include +#include #include #include #include @@ -20,6 +21,7 @@ constexpr std::size_t NeighbourBytes = std::size_t {1} << 20; std::uintptr_t originalStack = 0; std::string originalArgument; +std::string originalPath; bool originalInArena = false; mach_vm_address_t neighbour = 0; @@ -67,17 +69,30 @@ bool InArena(std::uintptr_t address) { void CheckMainThread(void*) { int local = 0; Require(pthread_main_np() != 0, "the check runs on the main thread"); + char path[4096]; + std::uint32_t pathBytes = sizeof(path); + Require(_NSGetExecutablePath(path, &pathBytes) == 0, "the executable path is readable after the move"); + Require(originalPath == path, "the executable path survived the move"); const auto address = reinterpret_cast(&local); if (originalInArena) { Require(!InArena(address), "the main thread stack is outside the guest arena"); Require(address != originalStack, "the main thread stack moved"); - const auto old = reinterpret_cast(originalStack & ~(MappingAlignment - 1)); + const auto kept = originalStack & ~(MappingAlignment - 1); + const auto released = reinterpret_cast(kept - MappingBytes); try { - GuestArena::GuestArenaMarkUsed_nid_postfix(old, MappingBytes); + GuestArena::GuestArenaMarkUsed_nid_postfix(released, MappingBytes); } catch (const std::runtime_error&) { Require(false, "a fixed guest mapping fits where the main thread stack was"); } - GuestArena::GuestArenaRelease_nid_postfix(old, MappingBytes); + GuestArena::GuestArenaRelease_nid_postfix(released, MappingBytes); + bool refused = false; + try { + GuestArena::GuestArenaMarkUsed_nid_postfix(reinterpret_cast(kept), MappingBytes); + } catch (const std::runtime_error&) { + refused = true; + } + if (!refused) GuestArena::GuestArenaRelease_nid_postfix(reinterpret_cast(kept), MappingBytes); + Require(refused, "the process start block stays mapped where dyld points"); } if (neighbour != 0) { mach_vm_address_t begin = 0, end = 0; @@ -100,6 +115,9 @@ int main(int, char** argv) { originalStack = reinterpret_cast(&local); originalInArena = InArena(originalStack); originalArgument = argv[0]; + char path[4096]; + std::uint32_t pathBytes = sizeof(path); + if (_NSGetExecutablePath(path, &pathBytes) == 0) originalPath = path; if (originalInArena) neighbour = MapStackNeighbour(originalStack); Aps5StartGuest_nid_no_patch(Guest, nullptr); } diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 7ee8d74fba..1818ab3216 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -236,7 +236,7 @@ - [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) does not consult the target thread's signal mask - [pthread_sigmask](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) shares the process-wide mask of `sigprocmask` and `_sigprocmask`; FreeBSD keeps a mask per thread - On macOS the guest runs on a second thread and the main thread serves the main dispatch queue from its run loop: AppKit (SDL's Cocoa windows, Metal surfaces, event pumping and game controllers) only works on the main thread, and a guest never hands its thread back. The macOS entry stub of the relinker starts the guest through `Aps5StartGuest` ([MainThreadMacOs.cpp](../../../core/libs/prx/libkernel/Pthread/src/MainThreadMacOs.cpp)), on a thread with the stack size of the main thread (at least 8 MiB), and the guest ends the process with `exit`; a guest entry that returns aborts. libSceVideoOut sends its SDL calls to the main thread with `Aps5RunOnMainThread` (`OnMainThread` in [MainThread.hpp](../../../core/libs/prx/libSceVideoOut/include/MainThread.hpp)), which waits for them and rethrows their exceptions; a host program that never called `Aps5StartGuest` runs them in place -- On macOS the Rosetta main stack and the stacks of the host threads lie inside the guest arena, where a title can map fixed addresses. `Aps5StartGuest` moves the main thread onto a 16 MiB stack at `0x600000000000`, copies `argv`, `environ`, the program name and the initial block off the old stack, and releases the old stack range to the arena ([MainThreadMacOs.cpp](../../../core/libs/prx/libkernel/Pthread/src/MainThreadMacOs.cpp)). The guest thread and the other host threads still take their stacks from the system, so a title that fixes a mapping over one of those stacks fails with a host region overlap. The move is skipped when the main stack lies outside the arena, when the new stack cannot be placed outside it, and on hosts that are not x86-64 +- On macOS the Rosetta main stack and the stacks of the host threads lie inside the guest arena, where a title can map fixed addresses. `Aps5StartGuest` moves the main thread onto a 16 MiB stack at `0x600000000000`, copies `argv`, `environ`, the program name and the initial block off the old stack, and releases the old stack range to the arena up to the pages that hold the process start block ([MainThreadMacOs.cpp](../../../core/libs/prx/libkernel/Pthread/src/MainThreadMacOs.cpp)). Those pages stay mapped because dyld keeps its own pointers into the block and offers no call that redirects them, where `argv` and `environ` have one: `_NSGetExecutablePath` copies the path string out of the block on every call, and libxpc, Cocoa and `ShaderCacheDirectory` reach it on paths a title does not control, so releasing them faulted there. They are the top two pages, 8 KiB of the 8372224-byte stack on macOS 27.2 (Apple M5, x86-64 under Rosetta); the rest of the old stack is released as before, and the block is kept whole only when `argv` still points into the stack. The guest thread and the other host threads still take their stacks from the system, so a title that fixes a mapping over one of those stacks fails with a host region overlap. The move is skipped when the main stack lies outside the arena, when the new stack cannot be placed outside it, and on hosts that are not x86-64 - [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) reaches a thread that waits in an alertable `TimedWait` wait through an APC. A thread blocked anywhere else, including all waits under `APS5_COARSE_TIMED_WAITS` and host waits inside the emulator, is redirected and runs the handler only when that wait returns - [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) on Windows runs a queued delivery (an APC) in the first alertable wait of libkernel's TimedWait the target enters, or where libkernel drains APCs after releasing its address-wait or park queue lock or after the spin that ends a sleep (each is taken under a delivery deferral). Host code that holds a lock of its own across such a wait keeps it held while a waiting handler runs - [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) leaves the signal pending on a target thread that was created suspended and is not resumed yet, and delivers it on that thread once it is resumed, before its start routine; further raises while it is pending are merged into that one delivery. FreeBSD 11.4 libthr starts such a thread with every signal but `SIGTRAP` blocked ([`thr_create.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_create.c#L177-L183)) and restores its mask only once it is resumed ([`thread_start`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_create.c#L252-L294)), so a signal sent to it stays pending until then. The console's behaviour is unverified From 5208b6c11eb99f77c7e3de67dd8ee0f04fd7b805 Mon Sep 17 00:00:00 2001 From: javierUDP Date: Wed, 4 Nov 2026 03:49:33 -0300 Subject: [PATCH 319/612] build(libSceNpCommon): compile the runtime test on macOS TestTitleSecretObjects calls the deleting destructor through reinterpret_cast(owned->vtable[1]). The vtable entry is a const void*, and AppleClang rejects a reinterpret_cast that casts the qualifier away. The other six vtable calls in this file go through const_cast first; this one now does too. --- core/libs/tests/GuestNpCommonRuntime.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/libs/tests/GuestNpCommonRuntime.cpp b/core/libs/tests/GuestNpCommonRuntime.cpp index 660ce02fd9..5840aebce8 100644 --- a/core/libs/tests/GuestNpCommonRuntime.cpp +++ b/core/libs/tests/GuestNpCommonRuntime.cpp @@ -528,7 +528,7 @@ void TestTitleSecretObjects() { auto* owned = static_cast(_ZN3sce2np6ObjectnwEmR14SceNpAllocator(sizeof(NpTitleSecretObject), &allocator)); _ZN3sce2np13NpTitleSecretC1Ev(owned); using Destructor = void APS5_VABI (*)(NpTitleSecretObject*); - reinterpret_cast(owned->vtable[1])(owned); + reinterpret_cast(const_cast(owned->vtable[1]))(owned); Require(record.released == record.block.data(), "the deleting destructor frees through the stored allocator"); } From 53c1c85a3b69a0d3dcccaa12c6d5a3d526480d7e Mon Sep 17 00:00:00 2001 From: JJenkx Date: Wed, 4 Nov 2026 02:30:27 -0500 Subject: [PATCH 320/612] feat(libSceNet): register resolvers in an epoll and report finished async lookups A resolver id can now be added to an epoll. A lookup started with SCE_NET_RESOLVER_ASYNC returns 0, keeps its result for sceNetResolverGetError, and is reported once to each epoll that waits for EPOLLIN on it, as EPOLLIN | EPOLLDESCID (plus EPOLLHUP after an abort). Destroying the resolver drops its registrations. Coherent NetLib in PPSA08194 adds a resolver to an epoll before each lookup and got EBADF. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceNet/Export.cpp | 50 +++++++++++++++++++++--- core/libs/tests/GuestSceNet.cpp | 63 ++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 243ed713ab..8bbe2cba90 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -153,6 +153,10 @@ constexpr int NET_SOCK_RAW = 3; constexpr int NET_EPOLL_CTL_ADD = 1; constexpr int NET_EPOLL_CTL_MOD = 2; constexpr int NET_EPOLL_CTL_DEL = 3; +constexpr std::uint32_t NET_EPOLLIN = 0x1; +constexpr std::uint32_t NET_EPOLLHUP = 0x10; +constexpr std::uint32_t NET_EPOLLDESCID = 0x10000; +constexpr int NET_RESOLVER_ASYNC = 0x1; constexpr int NET_SOL_SOCKET = 0xFFFF; constexpr int NET_IPPROTO_TCP = 6; constexpr int NET_IPPROTO_IPV6 = 41; @@ -382,8 +386,11 @@ struct Resolver { std::uint64_t aborts = 0; bool abort_ntoa = false; bool abort_aton = false; + std::uint64_t completions = 0; + bool hangup = false; }; std::map g_resolvers; +std::map> g_epoll_resolvers_reported; int g_next_sock = 32; int g_next_epoll = 0x4000; int g_next_pool = 1; @@ -539,6 +546,17 @@ int end_lookup(int rid, std::uint64_t generation, int error) { return error; } +int complete_async_lookup(int rid, int error) { + std::lock_guard lk(g_mutex); + const auto resolver = g_resolvers.find(rid); + if (resolver == g_resolvers.end()) return error; + resolver->second.error = error; + resolver->second.hangup = error == (NET_ERROR_BASE | NET_EINTR); + ++resolver->second.completions; + g_cv.NotifyAll(); + return 0; +} + void log_soft(const char* func, const char* what) { static std::mutex mtx; static std::map hits; @@ -2260,6 +2278,7 @@ int APS5_VABI sceNetEpollDestroy(int eid) { } g_epoll_names.erase(eid); g_epoll_socks.erase(eid); + g_epoll_resolvers_reported.erase(eid); g_epoll_aborted.erase(eid); g_cv.NotifyAll(); return 0; @@ -2289,13 +2308,16 @@ int APS5_VABI sceNetEpollControl(int eid, int op, int id, const NetEpollEvent* e } if (op != NET_EPOLL_CTL_ADD && op != NET_EPOLL_CTL_MOD && op != NET_EPOLL_CTL_DEL) return fail(NET_EINVAL); if (op != NET_EPOLL_CTL_DEL && !event) return fail(NET_EINVAL); - if (!kernel && g_socks.count(id) == 0) return fail(NET_EBADF); + const bool resolver = g_resolvers.count(id) != 0; + if (!kernel && !resolver && g_socks.count(id) == 0) return fail(NET_EBADF); auto& registrations = g_epoll_socks[eid]; if (op == NET_EPOLL_CTL_ADD) { if (registrations.count(id) != 0) return fail(NET_EADDRINUSE); registrations.emplace(id, std::make_shared(*event)); + if (resolver) g_epoll_resolvers_reported[eid][id] = 0; } else if (op == NET_EPOLL_CTL_DEL) { if (registrations.erase(id) == 0) return fail(NET_ENOENT); + g_epoll_resolvers_reported[eid].erase(id); } else { auto registration = registrations.find(id); if (registration == registrations.end()) return fail(NET_ENOENT); @@ -2326,7 +2348,22 @@ int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int if (g_epoll_aborted.erase(eid) != 0) return fail(NET_ECONNABORTED); const auto registration = g_epoll_socks.find(eid); if (registration != g_epoll_socks.end()) { + int count = 0; + auto& reported = g_epoll_resolvers_reported[eid]; for (const auto& entry : registration->second) { + const auto resolver = g_resolvers.find(entry.first); + if (resolver == g_resolvers.end() || count >= maxevents) continue; + if ((entry.second->events & NET_EPOLLIN) == 0 || reported[entry.first] == resolver->second.completions) continue; + reported[entry.first] = resolver->second.completions; + events[count] = *entry.second; + events[count].ident = static_cast(entry.first); + events[count].reserved = 0; + events[count].events = NET_EPOLLIN | NET_EPOLLDESCID | (resolver->second.hangup ? NET_EPOLLHUP : 0u); + ++count; + } + if (count > 0) return count; + for (const auto& entry : registration->second) { + if (g_resolvers.count(entry.first) != 0) continue; const auto socket = g_socks.find(entry.first); if (socket == g_socks.end()) kernelWatches.push_back({entry.first, entry.second, Sock{}, nullptr, INVALID_NATIVE_SOCKET}); else watches.push_back({entry.first, entry.second, socket->second, nullptr, socket->second.native->value}); @@ -2560,7 +2597,10 @@ int APS5_VABI sceNetResolverCreate(const char* name, int memid, int flags) { int APS5_VABI sceNetResolverDestroy(int rid) { std::lock_guard lk(g_mutex); - return g_resolvers.erase(rid) != 0 ? 0 : fail(NET_EBADF); + if (g_resolvers.erase(rid) == 0) return fail(NET_EBADF); + for (auto& epoll : g_epoll_socks) epoll.second.erase(rid); + for (auto& epoll : g_epoll_resolvers_reported) epoll.second.erase(rid); + return 0; } int lookup_host(int rid, const char* hostname, const char* func, int family, std::vector>& addresses) { @@ -2616,9 +2656,9 @@ int APS5_VABI sceNetResolverStartNtoa(int rid, const char* hostname, void* addr, return fail(NET_EINVAL); if (!hostname || !addr) return fail(NET_EINVAL); std::vector addresses; - if (const int error = lookup_ipv4(rid, hostname, __func__, addresses)) return error; - std::memcpy(addr, addresses.data(), sizeof(addresses[0])); - return 0; + int error = lookup_ipv4(rid, hostname, __func__, addresses); + if (error == 0) std::memcpy(addr, addresses.data(), sizeof(addresses[0])); + return (flags & NET_RESOLVER_ASYNC) != 0 ? complete_async_lookup(rid, error) : error; } int APS5_VABI sceNetResolverStartNtoa6(int rid, const char* hostname, void* addr, int timeout, int retry, int flags) { diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index b7b66f02db..622bf5d92e 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -60,6 +60,12 @@ static bool FailResolverLookup(const char* hostname, int family, std::vector>& addresses) { + if (hostname == nullptr || std::strcmp(hostname, "guest-sce-net.resolver-epoll") != 0 || family != AF_INET) return false; + addresses.push_back({127, 0, 0, 1}); + return true; +} + class ScopedResolverLookupOverride { public: explicit ScopedResolverLookupOverride(GuestSceNetTest::ResolverLookup lookup) { @@ -775,6 +781,62 @@ static void CheckEmptyEpoll() { } } +static void CheckResolverEpoll() { + constexpr std::uint32_t in = 0x1, descid = 0x10000; + constexpr int async = 0x1; + ScopedResolverLookupOverride lookupOverride(LoopbackResolverLookup); + const int resolver = sceNetResolverCreate("epoll-resolver", 0, 0); + const int epoll = sceNetEpollCreate("resolver-epoll", 0); + Require(resolver >= 0 && epoll >= 0); + NetEpollEvent registration{}; + registration.events = in; + registration.data.u64 = 0x1234; + Require(sceNetEpollControl(epoll, 1, resolver, ®istration) == 0); + Require(Failed(sceNetEpollControl(epoll, 1, resolver, ®istration), 48)); + NetEpollEvent ready{}; + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 0); + + std::uint32_t address = 0; + Require(sceNetResolverStartNtoa(resolver, "guest-sce-net.resolver-epoll", &address, 0, 0, 0) == 0); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 0); + address = 0; + Require(sceNetResolverStartNtoa(resolver, "guest-sce-net.resolver-epoll", &address, 0, 0, async) == 0); + Require(address == 0x0100007fu); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 1); + Require(ready.events == (in | descid) && ready.ident == static_cast(resolver) && ready.data.u64 == 0x1234 && ready.reserved == 0); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 0); + + Require(sceNetResolverStartNtoa(resolver, "guest-sce-net.unknown", &address, 0, 0, async) == 0); + int status = 0; + Require(sceNetResolverGetError(resolver, &status) == 0 && status == static_cast(0x804101E1u)); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 1 && ready.events == (in | descid)); + + auto waiter = std::async(std::launch::async, [epoll] { + NetEpollEvent woken{}; + return std::make_pair(sceNetEpollWait(epoll, &woken, 1, -1), woken.events); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + Require(waiter.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout); + Require(sceNetResolverStartNtoa(resolver, "guest-sce-net.resolver-epoll", &address, 0, 0, async) == 0); + Require(waiter.wait_for(std::chrono::seconds(1)) == std::future_status::ready); + Require(waiter.get() == std::make_pair(1, in | descid)); + + const int writer = sceNetEpollCreate("resolver-writer", 0); + registration.events = 0x2; + Require(sceNetEpollControl(writer, 1, resolver, ®istration) == 0); + Require(sceNetResolverStartNtoa(resolver, "guest-sce-net.resolver-epoll", &address, 0, 0, async) == 0); + Require(sceNetEpollWait(writer, &ready, 1, 0) == 0); + Require(sceNetEpollControl(writer, 3, resolver, nullptr) == 0); + Require(Failed(sceNetEpollControl(writer, 3, resolver, nullptr), 2)); + Require(sceNetEpollDestroy(writer) == 0); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 1); + + Require(sceNetResolverDestroy(resolver) == 0); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 0); + Require(Failed(sceNetEpollControl(epoll, 1, resolver, ®istration), 9)); + Require(sceNetEpollDestroy(epoll) == 0); +} + static void CheckRejectedArguments() { Require(Failed(sceNetEpollCreate(nullptr, 0), 22)); for (const int flags : {1, 0x10000, -1}) Require(Failed(sceNetEpollCreate("rejected-epoll", flags), 22)); @@ -2393,6 +2455,7 @@ int main() { CheckNetMemory(); CheckNetstat(); CheckEmptyEpoll(); + CheckResolverEpoll(); CheckIfFlags(); CheckRejectedArguments(); CheckDontWait(); From 979dbad6e8622b433e71f4761f1822875c4a1ade Mon Sep 17 00:00:00 2001 From: JJenkx Date: Wed, 4 Nov 2026 02:30:54 -0500 Subject: [PATCH 321/612] test(libSceNet): report a resolver aborted during an async lookup as hung up Co-Authored-By: Claude Opus 5.5 --- core/libs/tests/GuestSceNet.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 622bf5d92e..4360a31a5a 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -60,8 +60,15 @@ static bool FailResolverLookup(const char* hostname, int family, std::vector blockedResolverLookup{0}; + static bool LoopbackResolverLookup(const char* hostname, int family, std::vector>& addresses) { - if (hostname == nullptr || std::strcmp(hostname, "guest-sce-net.resolver-epoll") != 0 || family != AF_INET) return false; + if (hostname != nullptr && std::strcmp(hostname, "guest-sce-net.resolver-blocked") == 0) { + blockedResolverLookup = 1; + while (blockedResolverLookup == 1) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else if (hostname == nullptr || std::strcmp(hostname, "guest-sce-net.resolver-epoll") != 0 || family != AF_INET) { + return false; + } addresses.push_back({127, 0, 0, 1}); return true; } @@ -782,7 +789,7 @@ static void CheckEmptyEpoll() { } static void CheckResolverEpoll() { - constexpr std::uint32_t in = 0x1, descid = 0x10000; + constexpr std::uint32_t in = 0x1, hup = 0x10, descid = 0x10000; constexpr int async = 0x1; ScopedResolverLookupOverride lookupOverride(LoopbackResolverLookup); const int resolver = sceNetResolverCreate("epoll-resolver", 0, 0); @@ -831,6 +838,16 @@ static void CheckResolverEpoll() { Require(sceNetEpollDestroy(writer) == 0); Require(sceNetEpollWait(epoll, &ready, 1, 0) == 1); + auto aborted = std::async(std::launch::async, [resolver, &address] { + return sceNetResolverStartNtoa(resolver, "guest-sce-net.resolver-blocked", &address, 0, 0, async); + }); + while (blockedResolverLookup != 1) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Require(sceNetResolverAbort(resolver, 0) == 0); + blockedResolverLookup = 0; + Require(aborted.get() == 0); + Require(sceNetResolverGetError(resolver, &status) == 0 && status == static_cast(0x80410104u)); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 1 && ready.events == (in | descid | hup)); + Require(sceNetResolverDestroy(resolver) == 0); Require(sceNetEpollWait(epoll, &ready, 1, 0) == 0); Require(Failed(sceNetEpollControl(epoll, 1, resolver, ®istration), 9)); From 8708bbf555970e1f7ce85a7a7da5731dc4335053 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Wed, 4 Nov 2026 02:31:14 -0500 Subject: [PATCH 322/612] test(libSceNet): include atomic for the blocked resolver lookup Co-Authored-By: Claude Opus 5.5 --- core/libs/tests/GuestSceNet.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 4360a31a5a..d3f4d36d48 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -23,6 +23,7 @@ #include "tests/TemporaryFile.hpp" #include #include +#include #include #include #include From 32e4399e696369769c19b3b19434a996e90d8a4f Mon Sep 17 00:00:00 2001 From: JJenkx Date: Wed, 4 Nov 2026 02:36:21 -0500 Subject: [PATCH 323/612] docs(debt): describe the async resolver flag in libSceNet Co-Authored-By: Claude Opus 5.5 --- docs/dev/debt/libSceNet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index a71541edcb..365b12d176 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -97,7 +97,7 @@ - [sceNetResolverStartNtoa6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) resolves a host name to its first IPv6 address through the host resolver (`getaddrinfo` with `AF_INET6`) and writes the 16-byte address; the `(rid, hostname, addr, timeout, retry, flags)` signature follows `sceNetResolverStartNtoa`. A name with no IPv6 address fails with `RESOLVER_ENODNS` (`0x804101E1`) like a failed lookup; whether the console returns IPv4-mapped addresses instead is not verified. Timeout and retry are ignored as in `sceNetResolverStartNtoa`, and any nonzero flags throw because their meaning for this call is not known. Unverified on hardware. - [sceNetInetPton](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - an IPv6 conversion with a null `dst` returns `NET_EINVAL` here, where it crashed a payload process on a PS5 (firmware 13.60). On that PS5 and on PS4 firmware 10.71 an IPv4 address ends at the first space, tab, LF, VT, FF or CR and the rest is ignored (`"1.2.3.4 x"` converts), and a failed conversion leaves the destination and `sceNetErrnoLoc` unchanged. On the PS5 the dotted IPv4 part of IPv6 text is read the same way (`"::ffff:1.2.3.4 x"` converts, `"::ffff:01.2.3.4"` fails), and an IPv4 conversion with a null `dst` returns 1 or 0 without writing. Leading whitespace, any other trailing byte (`0x01`, `0xa0`) and whitespace after hexadecimal IPv6 text fail - [sceNetInetPton, sceNetInetNtop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) convert IPv6 text with libkernel's [inet_pton and inet_ntop](../../../core/libs/prx/libkernel/Socket/src/AddressConversion.cpp), so they take the same FreeBSD 11.4 text forms; beyond the PS5 measurements in the entry above, these are unverified on a console. IPv4 text keeps the host conversion. The checks and errors before the conversion are libSceNet's own and unchanged. In the executables of the 53 titles run here, 36 call `sceNetInetPton` at 107 call sites and 22 call `sceNetInetNtop` at 67; every one of those call sites loads the family 2 (`AF_INET`) into the first argument, none loads 28 (`AF_INET6`) and none leaves it undetermined, so the IPv6 text conversion that is unverified on a console is not reached by a direct call in those executables (read from the instructions, not measured; calls from the titles' library modules and through function pointers are not seen). -- [sceNetResolverStartNtoa](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) ignores flag bits 0x1 and 0x10000 and does not apply the timeout or retry count: every lookup runs once, synchronously, through the host resolver. What those two bits do on a console is not known; the sceNetResolverAbort entry above gives shadPS4's names for them. A negative timeout or retry and any other flag bit fail with `EINVAL` before anything else is checked, as [prosper's reading of the libSceNet module](https://github.com/mattias800/prosper/blob/6c09a34a092a00898fcead5f855200ca7c368cd2/prosper/src/hle/net/hle_net.cpp#L579-L587) gives. A timeout from 1 to 999999 also fails with `EINVAL` before the resolver is looked up, and 0 counts as 1000000, as @BunnyFl's [13.42 run of the module](https://github.com/boykopovar/AnyPS5/pull/9727#issuecomment-6362795252) shows. Every direct call in the titles checked passes the constant timeout 0, retry 0 or 1 and flags 0 or 0x1, so none fails these checks. +- [sceNetResolverStartNtoa](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) ignores flag bit 0x10000, the timeout and the retry count: each lookup runs once, synchronously, on the host resolver. With bit 0x1 (shadPS4's `ASYNC`) it returns 0, leaves the result to sceNetResolverGetError and reports `EPOLLIN | EPOLLDESCID` (`EPOLLHUP` after an abort) once to each epoll holding it, as shadPS4 does (unmeasured). A negative timeout or retry and any other flag bit fail with `EINVAL` before anything else is checked, as [prosper's reading of the libSceNet module](https://github.com/mattias800/prosper/blob/6c09a34a092a00898fcead5f855200ca7c368cd2/prosper/src/hle/net/hle_net.cpp#L579-L587) gives. A timeout from 1 to 999999 also fails with `EINVAL` before the resolver is looked up, and 0 counts as 1000000, as @BunnyFl's [13.42 run of the module](https://github.com/boykopovar/AnyPS5/pull/9727#issuecomment-6362795252) shows. Titles checked pass timeout 0, retry 0 or 1 and flags 0 or 0x1. - [sceNetResolverCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - non-zero `flags` fail with `EINVAL`, as [prosper's reading of the libSceNet module](https://github.com/mattias800/prosper/blob/6c09a34a092a00898fcead5f855200ca7c368cd2/prosper/src/hle/net/hle_net.cpp#L564-L578) gives. Every direct call in the titles checked passes the constant 0 as flags, so none reaches the check; one title also reaches the function through a tail jump, which is not read. - [sceNetDhcpStart, sceNetAddrConfig6Start, sceNetDuplicateIpStart and sceNetInfoDumpStart](../../../core/libs/prx/libSceNet/Export.cpp) - argument widths and callback ABI are recovered. Address callbacks receive user data, a 32-bit event and a 32-bit error. DHCP configuration is mutable; the 512-byte IPv6 configuration is read-only. Their complete field layouts remain unknown. The two 32-bit thread parameters are stored but not consumed by this firmware thread wrapper, so their original names and units are unconfirmed. Information-dump options contain flags, an interval in seconds and a 64-byte path. All four exports remain unimplemented. From 53f3bd9a83d848d55c8b3d249dd4008714f7ed5c Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Sat, 24 Oct 2026 13:42:54 +0000 Subject: [PATCH 324/612] feat(libSceLibreSsl3): implement OBJ_cmp --- core/libs/prx/libSceLibreSsl3/CMakeLists.txt | 2 +- core/libs/prx/libSceLibreSsl3/Export.cpp | 5 ---- core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp | 29 ++++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp diff --git a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt index ceb598d372..be6d20e310 100644 --- a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl3 SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/IntegerDecoder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Key.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509PubkeyParam.cpp) +add_library(libSceLibreSsl3 SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/IntegerDecoder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Key.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509PubkeyParam.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/ObjCmp.cpp) target_include_directories(libSceLibreSsl3 PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl3 PRIVATE libSceLibreSsl libc) configure_windows_unwind(libSceLibreSsl3) diff --git a/core/libs/prx/libSceLibreSsl3/Export.cpp b/core/libs/prx/libSceLibreSsl3/Export.cpp index 8940b376d1..d7189c2444 100644 --- a/core/libs/prx/libSceLibreSsl3/Export.cpp +++ b/core/libs/prx/libSceLibreSsl3/Export.cpp @@ -194,11 +194,6 @@ int APS5_VABI EVP_sha224_nid_postfix(void) { return 0; } -int APS5_VABI OBJ_cmp_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI OBJ_txt2obj_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp b/core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp new file mode 100644 index 0000000000..6cccd4cdb7 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include "prx/libc/include/general/VabiMacros.hpp" + +namespace { +struct Object { + const char* shortName; + const char* longName; + int nid; + int length; + const unsigned char* data; + int flags; +}; +static_assert(sizeof(Object) == 40 && offsetof(Object, length) == 20 && offsetof(Object, data) == 24); +} + +extern "C" int APS5_VABI OBJ_cmp_nid_postfix(const void* first, const void* second) { + if (!first || !second) throw std::invalid_argument(std::string(__func__) + ": null object"); + const auto* left = static_cast(first); + const auto* right = static_cast(second); + if (const int difference = left->length - right->length) return difference; + if (left->length < 0 || (left->length > 0 && (!left->data || !right->data))) throw std::invalid_argument(std::string(__func__) + ": invalid encoded data"); + for (int index = 0; index < left->length; ++index) { + if (left->data[index] != right->data[index]) return static_cast(left->data[index]) - static_cast(right->data[index]); + } + return 0; +} From 0ccb95c598cb66743ad679894b595d3751ead190 Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Sat, 24 Oct 2026 13:43:34 +0000 Subject: [PATCH 325/612] test(libSceLibreSsl3): cover OBJ_cmp --- core/libs/prx/libSceLibreSsl3/CMakeLists.txt | 8 +++ core/libs/tests/GuestLibreSsl3ObjCmp.cpp | 65 ++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 core/libs/tests/GuestLibreSsl3ObjCmp.cpp diff --git a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt index be6d20e310..3323d82d42 100644 --- a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt @@ -33,6 +33,14 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl3_pkey_getters PROPERTIES TIMEOUT 20) if(WIN32) set_tests_properties(guest_libressl3_pkey_getters PROPERTIES ENVIRONMENT_MODIFICATION + add_test_executable(guest_libressl3_obj_cmp_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSsl3ObjCmp.cpp) + target_include_directories(guest_libressl3_obj_cmp_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl3_obj_cmp_tests PRIVATE libSceLibreSsl3 libc) + configure_windows_unwind(guest_libressl3_obj_cmp_tests) + add_test(NAME guest_libressl3_obj_cmp COMMAND guest_libressl3_obj_cmp_tests) + set_tests_properties(guest_libressl3_obj_cmp PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl3_obj_cmp PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test_executable(guest_crypto_memcmp_tests ${LIBS_INCLUDE_DIR}/tests/GuestCryptoMemcmp.cpp) diff --git a/core/libs/tests/GuestLibreSsl3ObjCmp.cpp b/core/libs/tests/GuestLibreSsl3ObjCmp.cpp new file mode 100644 index 0000000000..2bc49664e9 --- /dev/null +++ b/core/libs/tests/GuestLibreSsl3ObjCmp.cpp @@ -0,0 +1,65 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" int APS5_VABI OBJ_cmp_nid_postfix(const void*, const void*); + +namespace { +struct Object { + const char* shortName; + const char* longName; + int nid; + int length; + const unsigned char* data; + int flags; +}; + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "failed: %s\n", message); + std::terminate(); +} + +int Sign(int value) { + return value < 0 ? -1 : value > 0 ? 1 : 0; +} +} + +int main() { + const unsigned char short1[] = {0x2a, 0x86, 0x48}; + const unsigned char same1[] = {0x2a, 0x86, 0x48}; + const unsigned char low[] = {0x2a, 0x86, 0x47}; + const unsigned char high[] = {0x2a, 0x86, 0x49}; + const unsigned char longer[] = {0x2a, 0x86, 0x48, 0x01}; + const unsigned char first[] = {0x01, 0xff, 0xff}; + const unsigned char second[] = {0x02, 0x00, 0x00}; + const Object a{nullptr, nullptr, 0, 3, short1, 0}; + const Object b{nullptr, nullptr, 0, 3, same1, 0}; + const Object c{nullptr, nullptr, 0, 3, low, 0}; + const Object d{nullptr, nullptr, 0, 3, high, 0}; + const Object e{nullptr, nullptr, 0, 4, longer, 0}; + const Object f{nullptr, nullptr, 0, 3, first, 0}; + const Object g{nullptr, nullptr, 0, 3, second, 0}; + const Object emptyA{nullptr, nullptr, 0, 0, nullptr, 0}; + const Object emptyB{nullptr, nullptr, 0, 0, short1, 0}; + Require(OBJ_cmp_nid_postfix(&a, &a) == 0 && OBJ_cmp_nid_postfix(&a, &b) == 0, "equal encodings compare equal"); + Require(OBJ_cmp_nid_postfix(&a, &c) == 1 && OBJ_cmp_nid_postfix(&c, &a) == -1, "the first differing byte decides, as its difference"); + Require(OBJ_cmp_nid_postfix(&a, &d) == -1 && OBJ_cmp_nid_postfix(&d, &a) == 1, "a higher byte compares greater"); + Require(OBJ_cmp_nid_postfix(&a, &e) == -1 && OBJ_cmp_nid_postfix(&e, &a) == 1, "the length difference is returned before the bytes"); + Require(Sign(OBJ_cmp_nid_postfix(&f, &g)) == -1 && OBJ_cmp_nid_postfix(&f, &g) == -1, "bytes are compared as unsigned values"); + Require(OBJ_cmp_nid_postfix(&emptyA, &emptyB) == 0, "empty encodings compare equal without reading data"); + auto throws = [](const void* left, const void* right) { + try { + static_cast(OBJ_cmp_nid_postfix(left, right)); + } catch (const std::exception&) { + return true; + } + return false; + }; + Require(throws(nullptr, &a) && throws(&a, nullptr), "null objects are rejected"); + const Object missing{nullptr, nullptr, 0, 3, nullptr, 0}; + const Object negative{nullptr, nullptr, 0, -1, short1, 0}; + Require(throws(&missing, &missing) && throws(&negative, &negative), "missing data and negative lengths are rejected"); + return 0; +} From 704a03d7b0902b18a272f7c97641e0b34241668c Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Sat, 24 Oct 2026 13:43:35 +0000 Subject: [PATCH 326/612] docs(libSceLibreSsl3): record OBJ_cmp --- docs/dev/debt/libSceLibreSsl3.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceLibreSsl3.md b/docs/dev/debt/libSceLibreSsl3.md index dac8e3e8ae..512c73e74c 100644 --- a/docs/dev/debt/libSceLibreSsl3.md +++ b/docs/dev/debt/libSceLibreSsl3.md @@ -1,5 +1,6 @@ # libSceLibreSsl3 technical debt +- [OBJ_cmp](../../../core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp) (libSceLibreSsl3) - PS5 behavior and the library's LibreSSL version are not verified. The 40-byte LP64 `ASN1_OBJECT` layout and the comparison (length difference first, then the first differing byte as a difference of unsigned values) follow the public [LibreSSL source](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/objects/obj_dat.c); the magnitude of a byte mismatch is the byte difference rather than whatever the console's `memcmp` returns. Null objects, null data with a nonzero length and negative lengths throw where LibreSSL faults - [`X509_PUBKEY_get0_param`](../../../core/libs/prx/libSceLibreSsl3/src/X509PubkeyParam.cpp) follows LibreSSL 4.0.0 `crypto/asn1/x_pubkey.c` at [`ce488b78e0bd994780569df718596b9776ff681a`](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/asn1/x_pubkey.c) and its `x509_local.h`, `x509.h` and `asn1.h` layouts. The function returns borrowed pointers and writes the encoded key length only when the key output is requested. The PS5 layout and malformed-object behavior are not verified on hardware; layout assertions match the pinned x86-64 source declarations. ### Functional @@ -9,7 +10,7 @@ ### Unknown function info -- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 75 named callable exports, excluding `CRYPTO_memcmp`, `MD5`, `SHA1`, `d2i_ASN1_INTEGER`, `EVP_PKEY_up_ref`, `EC_KEY_up_ref`, `RSA_up_ref`, `EVP_PKEY_get0_RSA`, `EVP_PKEY_get0_DSA`, `EVP_PKEY_get0_DH`, `OpenSSL_version` and `X509_PUBKEY_get0_param`, remain unimplemented from the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl3.c), cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 74 named callable exports, excluding `CRYPTO_memcmp`, `EC_KEY_up_ref`, `EVP_PKEY_get0_DH`, `EVP_PKEY_get0_DSA`, `EVP_PKEY_get0_RSA`, `EVP_PKEY_up_ref`, `MD5`, `OBJ_cmp`, `OpenSSL_version`, `RSA_up_ref`, `SHA1`, `X509_PUBKEY_get0_param` and `d2i_ASN1_INTEGER`, remain unimplemented from the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl3.c), cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. ### Functional From 4b7a38cb84e90ef07c5fd48185f16efaedb705bc Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Sat, 24 Oct 2026 14:09:13 +0000 Subject: [PATCH 327/612] fix(libSceLibreSsl3): reject negative object lengths safely --- core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp | 3 ++- core/libs/tests/GuestLibreSsl3ObjCmp.cpp | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp b/core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp index 6cccd4cdb7..17487a7ec0 100644 --- a/core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp +++ b/core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp @@ -20,8 +20,9 @@ extern "C" int APS5_VABI OBJ_cmp_nid_postfix(const void* first, const void* seco if (!first || !second) throw std::invalid_argument(std::string(__func__) + ": null object"); const auto* left = static_cast(first); const auto* right = static_cast(second); + if (left->length < 0 || right->length < 0) throw std::invalid_argument(std::string(__func__) + ": negative encoded length"); if (const int difference = left->length - right->length) return difference; - if (left->length < 0 || (left->length > 0 && (!left->data || !right->data))) throw std::invalid_argument(std::string(__func__) + ": invalid encoded data"); + if (left->length > 0 && (!left->data || !right->data)) throw std::invalid_argument(std::string(__func__) + ": invalid encoded data"); for (int index = 0; index < left->length; ++index) { if (left->data[index] != right->data[index]) return static_cast(left->data[index]) - static_cast(right->data[index]); } diff --git a/core/libs/tests/GuestLibreSsl3ObjCmp.cpp b/core/libs/tests/GuestLibreSsl3ObjCmp.cpp index 2bc49664e9..2d5c59a000 100644 --- a/core/libs/tests/GuestLibreSsl3ObjCmp.cpp +++ b/core/libs/tests/GuestLibreSsl3ObjCmp.cpp @@ -1,6 +1,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include #include extern "C" int APS5_VABI OBJ_cmp_nid_postfix(const void*, const void*); @@ -60,6 +61,10 @@ int main() { Require(throws(nullptr, &a) && throws(&a, nullptr), "null objects are rejected"); const Object missing{nullptr, nullptr, 0, 3, nullptr, 0}; const Object negative{nullptr, nullptr, 0, -1, short1, 0}; - Require(throws(&missing, &missing) && throws(&negative, &negative), "missing data and negative lengths are rejected"); + const Object empty{nullptr, nullptr, 0, 0, nullptr, 0}; + const Object minimum{nullptr, nullptr, 0, std::numeric_limits::min(), nullptr, 0}; + const Object one{nullptr, nullptr, 0, 1, short1, 0}; + Require(throws(&missing, &missing) && throws(&negative, &negative) && throws(&negative, &empty), "missing data and negative lengths are rejected"); + Require(throws(&minimum, &one), "extreme negative lengths are rejected without overflowing"); return 0; } From 6592b88240298e9463cce3e31828a0b469f587a7 Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Sat, 24 Oct 2026 15:38:23 +0000 Subject: [PATCH 328/612] docs(libSceLibreSsl3): correct OBJ_cmp source and gap --- docs/dev/debt/libSceLibreSsl3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceLibreSsl3.md b/docs/dev/debt/libSceLibreSsl3.md index 512c73e74c..16e0d4a9d7 100644 --- a/docs/dev/debt/libSceLibreSsl3.md +++ b/docs/dev/debt/libSceLibreSsl3.md @@ -1,6 +1,6 @@ # libSceLibreSsl3 technical debt -- [OBJ_cmp](../../../core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp) (libSceLibreSsl3) - PS5 behavior and the library's LibreSSL version are not verified. The 40-byte LP64 `ASN1_OBJECT` layout and the comparison (length difference first, then the first differing byte as a difference of unsigned values) follow the public [LibreSSL source](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/objects/obj_dat.c); the magnitude of a byte mismatch is the byte difference rather than whatever the console's `memcmp` returns. Null objects, null data with a nonzero length and negative lengths throw where LibreSSL faults +- [OBJ_cmp](../../../core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp) (libSceLibreSsl3) - PS5 behavior and the library's LibreSSL version are not verified. The 40-byte LP64 `ASN1_OBJECT` layout and the comparison (length difference first, then the first differing byte as a difference of unsigned values) follow the public [LibreSSL source](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/objects/obj_lib.c); the magnitude of a byte mismatch is the byte difference rather than whatever the console's `memcmp` returns. Null objects, null data with a nonzero length and negative lengths throw. LibreSSL subtracts unequal lengths before reading data, so unequal negative lengths do not necessarily fault; only equal negative lengths reach its byte comparison. - [`X509_PUBKEY_get0_param`](../../../core/libs/prx/libSceLibreSsl3/src/X509PubkeyParam.cpp) follows LibreSSL 4.0.0 `crypto/asn1/x_pubkey.c` at [`ce488b78e0bd994780569df718596b9776ff681a`](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/asn1/x_pubkey.c) and its `x509_local.h`, `x509.h` and `asn1.h` layouts. The function returns borrowed pointers and writes the encoded key length only when the key output is requested. The PS5 layout and malformed-object behavior are not verified on hardware; layout assertions match the pinned x86-64 source declarations. ### Functional From c4ae7283cb5333bf6278f4d5c9eac3e0e2cb7e47 Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Tue, 27 Oct 2026 02:20:09 +0000 Subject: [PATCH 329/612] refactor: keep one declaration of each export of this PR, in its library header The public header audit wants exactly one declaration per export, in the library header: the exports the PR adds are declared in Api.hpp with their definitions' signatures, a placeholder with no parameters takes the definition's signature, and the copies in tests and other sources are replaced by an include of the header. --- core/libs/prx/libSceLibreSsl3/include/Api.hpp | 2 +- core/libs/tests/GuestLibreSsl3ObjCmp.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl3/include/Api.hpp b/core/libs/prx/libSceLibreSsl3/include/Api.hpp index f4a8b07350..3e936f88a2 100644 --- a/core/libs/prx/libSceLibreSsl3/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl3/include/Api.hpp @@ -73,7 +73,7 @@ int APS5_VABI EVP_aes_192_wrap_nid_postfix(void); int APS5_VABI EVP_aes_256_wrap_nid_postfix(void); int APS5_VABI EVP_sha1_nid_postfix(void); int APS5_VABI EVP_sha224_nid_postfix(void); -int APS5_VABI OBJ_cmp_nid_postfix(void); +int APS5_VABI OBJ_cmp_nid_postfix(const void* first, const void* second); int APS5_VABI OBJ_txt2obj_nid_postfix(void); int APS5_VABI OCSP_CERTID_free_nid_postfix(void); int APS5_VABI OCSP_cert_to_id_nid_postfix(void); diff --git a/core/libs/tests/GuestLibreSsl3ObjCmp.cpp b/core/libs/tests/GuestLibreSsl3ObjCmp.cpp index 2d5c59a000..0080650c68 100644 --- a/core/libs/tests/GuestLibreSsl3ObjCmp.cpp +++ b/core/libs/tests/GuestLibreSsl3ObjCmp.cpp @@ -1,10 +1,10 @@ +#include "prx/libSceLibreSsl3/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include -extern "C" int APS5_VABI OBJ_cmp_nid_postfix(const void*, const void*); namespace { struct Object { From 690af29ced715a8b69b0b6c6469132a4fee6e6be Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Fri, 30 Oct 2026 00:26:31 +1300 Subject: [PATCH 330/612] fix(libressl3): include public API in OBJ_cmp implementation --- core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp b/core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp index 17487a7ec0..ffc2cd3530 100644 --- a/core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp +++ b/core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp @@ -3,6 +3,7 @@ #include #include #include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceLibreSsl3/include/Api.hpp" namespace { struct Object { From 661f46a42c6267d7a8c97dc42a5a0872b81d886c Mon Sep 17 00:00:00 2001 From: fogydes Date: Wed, 4 Nov 2026 14:48:46 +0530 Subject: [PATCH 331/612] fix(libkernel_sys): return ESRCH for long module names and document pointer host safety --- core/libs/prx/libkernel_sys/Export.cpp | 7 ++++--- core/libs/tests/GuestKernelSysModuleInternal.cpp | 2 ++ docs/dev/debt/libkernel_sys.md | 7 +++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index db691dc2c6..62a6131b3b 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -539,12 +539,13 @@ KernelModule APS5_VABI sceKernelLoadStartModuleForSysmodule(const char* moduleFi int APS5_VABI sceKernelIsModuleLoaded(const char* name) { if (!name) throw std::invalid_argument("sceKernelIsModuleLoaded: null name"); - std::string copy; - const int copied = CopyGuestString_nid_no_patch(name, 256, copy); - if (copied != 0) return SCE_KERNEL_ERROR_EFAULT; KernelModule handles[0x300]; std::size_t count = 0; if (const int result = sceKernelGetModuleListInternal(handles, std::size(handles), &count)) return result; + std::string copy; + const int copied = CopyGuestString_nid_no_patch(name, 256, copy); + if (copied == 63) return SCE_KERNEL_ERROR_ESRCH; + if (copied != 0) return SCE_KERNEL_ERROR_EFAULT; for (std::size_t i = 0; i < count; ++i) { ModuleInfo info{}; info.st_size = sizeof(info); diff --git a/core/libs/tests/GuestKernelSysModuleInternal.cpp b/core/libs/tests/GuestKernelSysModuleInternal.cpp index a44415d8da..c7d68fb6b0 100644 --- a/core/libs/tests/GuestKernelSysModuleInternal.cpp +++ b/core/libs/tests/GuestKernelSysModuleInternal.cpp @@ -104,6 +104,8 @@ static void CheckIsModuleLoaded() { } Require(sceKernelIsModuleLoaded("libSceNoSuchModule.sprx") == SCE_KERNEL_ERROR_ESRCH); Require(sceKernelIsModuleLoaded("") == expected("")); + const std::string longName(300, 'a'); + Require(sceKernelIsModuleLoaded(longName.c_str()) == SCE_KERNEL_ERROR_ESRCH); Require(sceKernelIsModuleLoaded(reinterpret_cast(0x10)) == SCE_KERNEL_ERROR_EFAULT); } diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 595008346d..098061c54a 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -339,7 +339,9 @@ - [sceKernelGetHwModelName](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - throws. On a CFI-2014 PS5 (firmware 13.60) a payload process got 0 from `sceKernelGetHwModelName(char*)`, which wrote "CFI-2014 A01X" and a terminator (14 bytes). The model string belongs to one console and the buffer size and null behaviour are not known, so it is not implemented - [sceKernelGetCompiledSdkVersion](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - which SDK version the console returns is unmeasured. The [PS5 payload SDK](https://github.com/ps5-payload-dev/sdk/blob/a7685ce8e97ce1f8a4f38253a41d54241c9124f1/crt/kernel.c#L257-L275) declares the process parameters with a 32-bit PS4 SDK version at offset `0x10` and a 32-bit PS5 SDK version at `0x14`; this export returns the field at `0x10`, which [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) reads and [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/615bcba5e21b82dc59588575f7ecbce8da09ca2c/src/emulator.cpp#L110-L138) reads from the eboot. The `int (int32_t*)` signature and `EINVAL` (`0x80020016`) for null follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/615bcba5e21b82dc59588575f7ecbce8da09ca2c/src/core/libraries/kernel/process.cpp#L56-L62); `SCE_KERNEL_ERROR_EFAULT` for an unwritable output pointer is a host-safety fallback so the process does not crash, not a measured PS5 result, and the console's behaviour for such a pointer is unmeasured. Closes when: measured on a PS5 -- [scePthreadCondattrGetclock](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - a null clock pointer with a valid attribute throws `std::invalid_argument`, where a payload process measured on a device faults. A null or destroyed attribute with a null output returns `SCE_KERNEL_ERROR_EINVAL` (`0x80020016`) because [FreeBSD 11.4 libthr](https://github.com/freebsd/freebsd-src/blob/d106002967/lib/libthr/thread/thr_condattr.c#L82-L90) checks the attribute first; that pair was not measured. an unwritable clock pointer returns `SCE_KERNEL_ERROR_EFAULT` (`0x8002000E`). Stale handles are not validated +- [sceKernelGetCpumodeGame](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - returning `SCE_KERNEL_ERROR_EFAULT` (`0x8002000E`) for an unwritable mode pointer is a host-safety fallback; firmware 13.42 disassembly stores the result directly (`16a8c: mov %eax,(%r14)`), so the console faults +- [sceKernelIsModuleLoaded](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - returning `SCE_KERNEL_ERROR_EFAULT` (`0x8002000E`) for an unreadable name pointer is a host-safety fallback; firmware 13.42 disassembly reads the caller string in place without copy checks, so the console faults +- [scePthreadCondattrGetclock](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - a null clock pointer with a valid attribute throws `std::invalid_argument`, where a payload process measured on a device faults. A null or destroyed attribute with a null output returns `SCE_KERNEL_ERROR_EINVAL` (`0x80020016`) because [FreeBSD 11.4 libthr](https://github.com/freebsd/freebsd-src/blob/d106002967/lib/libthr/thread/thr_condattr.c#L82-L90) checks the attribute first; that pair was not measured. Returning `SCE_KERNEL_ERROR_EFAULT` (`0x8002000E`) for an unwritable clock pointer is a host-safety fallback; firmware 13.42 disassembly stores the clock directly, so the console faults. Stale handles are not validated ### Functional @@ -394,7 +396,8 @@ - [AMM kernel write command checks](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - the map, map direct, unmap, remap, multimap and protection writers apply their libSceAmpr siblings' checks, not those of firmware 13.42's libkernel (compared under a CPU emulator, not measured on a console), so some arguments get a different return code. The console checks the range against the AMM virtual address ranges, accepts a size that wraps past the end of the address space, takes a different set of protection bits and, for maps and memory type changes, accepts only types 11 to 20. Where both accept a call, the recorded address, size, type and protection match - [sceKernelWriteMapCommand2, sceKernelWriteUnmapToPrtCommand, sceKernelWriteWriteAddressCommand, sceKernelWriteWriteCounterCommand, sceKernelWriteWaitOnAddressCommand, sceKernelWriteWaitOnCounterCommand and sceKernelWriteEventQueueOnCompletionCommand](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - they record this project's command layout, not the console's packed commands, so the reported sizes differ and the waits' flush bit is dropped. A PRT map with a non-zero type or protection and a recorded wait on an address that is not 8-byte aligned throw: their results are unknown. The checks follow firmware 13.42's libkernel and libkernel_sys, read from disassembly and compared under a CPU emulator, not measured on a console; without the PRT flag sceKernelWriteMapCommand2 is sceKernelWriteMapCommand. As in the firmware, the PRT range check accepts an end that wraps past the top of the address space, and only size bits 14 to 40 are recorded - [sceKernelWriteWaitOnAddressCommand and sceKernelWriteWaitOnCounterCommand](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - a negative compare passes the firmware's check (above 3 is refused) and is recorded as its low three bits, as in firmware 13.42, so -2 to -4 record modes 6 to 4. The command runner here (`_waitSatisfied` in `Apr.cpp`) implements modes 0 to 6, so a wait recorded with mode 7, for example from a compare of -1, is written successfully but throws when it runs -- [sceKernelCallIndirectBuffer, sceKernelCallIndirectBuffer2, sceKernelCallIndirectBuffer3, sceKernelGiveDirectMemoryToMapper and sceKernelMapperGetParam](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - follow firmware 13.42's libkernel_sys disassembly, not measured on a console. Submit calls forward to a kernel ioctl keeping 32 bits of dword count: priority > 2, null buffer or >= 2^30 dwords throws. sceKernelGiveDirectMemoryToMapper requires non-zero size and 2 MiB alignment. sceKernelMapperGetParam copies up to 0x38 bytes: size, four AMM ranges and two unknown values; requests past 0x28 bytes throw. An unwritable or unreadable pointer returns `SCE_KERNEL_ERROR_EFAULT` (`0x8002000E`). Device open failure returning 0x80020006 is not modelled +- [sceKernelCallIndirectBuffer, sceKernelCallIndirectBuffer2, sceKernelCallIndirectBuffer3, sceKernelGiveDirectMemoryToMapper and sceKernelMapperGetParam](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - follow firmware 13.42's libkernel_sys, read from disassembly and not measured on a console. The submit calls hand their arguments to a kernel ioctl, keeping 32 bits of the dword count, so the kernel's checks are unknown: a priority above 2, a null buffer or a dword count of 2^30 or more (the AMM queue here takes a 32-bit byte count) throws. sceKernelGiveDirectMemoryToMapper requires a non-zero size and a 2 MiB aligned size and alignment. sceKernelMapperGetParam copies up to 0x38 requested bytes of a block filled at startup: its size, the four AMM range values (firmware libSceAmpr reads them from there), then two unknown values, so a request past 0x28 bytes throws. The 0x80020006 returned when its device cannot be opened is not modelled +- [sceKernelMapperGetParam](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - returning `SCE_KERNEL_ERROR_EFAULT` (`0x8002000E`) for an unwritable output pointer or unreadable parameter struct is a host-safety fallback; firmware 13.42 disassembly accesses memory directly, so the console faults - [sceKernelWaitCommandBufferCompletion](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - signature taken from [ampr_emu's libkernel hook targets](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/include/ampr_libkernel_hook_targets.h), where `sceAmprAmmWaitCommandBufferCompletion` forwards to it; like that sibling it waits for an AMM submission and returns `SCE_KERNEL_ERROR_ESRCH` for an id that was never submitted, not confirmed on a title - [kldsym](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) follows FreeBSD 11.4's [`sys_kldsym`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_linker.c) in the order it checks its arguments: `EFAULT` for an unreadable lookup structure, `EINVAL` for a `version` other than the structure size (32 bytes on amd64) or a command other than `KLDSYM_LOOKUP`, `EFAULT` or `ENAMETOOLONG` for the symbol name, `ENOENT` for a file id that no file has. The linker file list is empty here, as in `kldnext` and `kldfind`, so every file id other than 0 gets `ENOENT`; modules a console preloads are not modelled. A lookup in id 0 (every file) throws, as no symbol table is modelled. Whether a title may query kernel symbols is unverified From 3e854f0e9e2c225fe254bae39f89010c086a2f03 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Wed, 4 Nov 2026 07:54:44 -0500 Subject: [PATCH 332/612] fix(libSceNet): give resolvers ids that sockets and epolls cannot take Resolver ids counted up from 0x100, which overlaps the socket numbers libkernel hands out below 1024. With resolvers now registered in epolls next to sockets, a socket numbered like a live resolver was taken for the resolver by sceNetEpollControl, skipped by sceNetEpollWait and dropped by sceNetResolverDestroy. A resolver now takes the lowest free id from 1024 to 0x3fff, between the socket numbers and the epoll ids. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceNet/Export.cpp | 7 +++++-- core/libs/tests/GuestSceNet.cpp | 7 ++++++- docs/dev/debt/libSceNet.md | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 8bbe2cba90..931c014946 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -394,7 +394,8 @@ std::map> g_epoll_resolvers_reported; int g_next_sock = 32; int g_next_epoll = 0x4000; int g_next_pool = 1; -int g_next_resolver = 0x100; +constexpr int NET_RESOLVER_ID_FIRST = 1024; +constexpr int NET_RESOLVER_ID_END = 0x4000; bool g_net_inited = false; bool g_net_init_param_set = false; std::array g_net_init_param{}; @@ -2590,7 +2591,9 @@ int APS5_VABI sceNetResolverCreate(const char* name, int memid, int flags) { (void)memid; if (flags != 0) return fail(NET_EINVAL); std::lock_guard lk(g_mutex); - const int id = g_next_resolver++; + int id = NET_RESOLVER_ID_FIRST; + for (auto it = g_resolvers.lower_bound(id); it != g_resolvers.end() && it->first == id; ++it) ++id; + if (id >= NET_RESOLVER_ID_END) throw std::runtime_error("sceNetResolverCreate: more than " + std::to_string(NET_RESOLVER_ID_END - NET_RESOLVER_ID_FIRST) + " live resolvers are not implemented"); g_resolvers[id] = {}; return id; } diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index d3f4d36d48..7918050f9a 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -795,7 +795,12 @@ static void CheckResolverEpoll() { ScopedResolverLookupOverride lookupOverride(LoopbackResolverLookup); const int resolver = sceNetResolverCreate("epoll-resolver", 0, 0); const int epoll = sceNetEpollCreate("resolver-epoll", 0); - Require(resolver >= 0 && epoll >= 0); + Require(resolver >= 1024 && resolver < 0x4000 && epoll >= 0x4000); + const int second = sceNetResolverCreate("second-resolver", 0, 0); + Require(second > resolver && second < 0x4000); + Require(sceNetResolverDestroy(second) == 0); + Require(sceNetResolverCreate("reused-resolver", 0, 0) == second); + Require(sceNetResolverDestroy(second) == 0); NetEpollEvent registration{}; registration.events = in; registration.data.u64 = 0x1234; diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index 365b12d176..af16ee85a1 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -98,7 +98,7 @@ - [sceNetInetPton](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - an IPv6 conversion with a null `dst` returns `NET_EINVAL` here, where it crashed a payload process on a PS5 (firmware 13.60). On that PS5 and on PS4 firmware 10.71 an IPv4 address ends at the first space, tab, LF, VT, FF or CR and the rest is ignored (`"1.2.3.4 x"` converts), and a failed conversion leaves the destination and `sceNetErrnoLoc` unchanged. On the PS5 the dotted IPv4 part of IPv6 text is read the same way (`"::ffff:1.2.3.4 x"` converts, `"::ffff:01.2.3.4"` fails), and an IPv4 conversion with a null `dst` returns 1 or 0 without writing. Leading whitespace, any other trailing byte (`0x01`, `0xa0`) and whitespace after hexadecimal IPv6 text fail - [sceNetInetPton, sceNetInetNtop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) convert IPv6 text with libkernel's [inet_pton and inet_ntop](../../../core/libs/prx/libkernel/Socket/src/AddressConversion.cpp), so they take the same FreeBSD 11.4 text forms; beyond the PS5 measurements in the entry above, these are unverified on a console. IPv4 text keeps the host conversion. The checks and errors before the conversion are libSceNet's own and unchanged. In the executables of the 53 titles run here, 36 call `sceNetInetPton` at 107 call sites and 22 call `sceNetInetNtop` at 67; every one of those call sites loads the family 2 (`AF_INET`) into the first argument, none loads 28 (`AF_INET6`) and none leaves it undetermined, so the IPv6 text conversion that is unverified on a console is not reached by a direct call in those executables (read from the instructions, not measured; calls from the titles' library modules and through function pointers are not seen). - [sceNetResolverStartNtoa](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) ignores flag bit 0x10000, the timeout and the retry count: each lookup runs once, synchronously, on the host resolver. With bit 0x1 (shadPS4's `ASYNC`) it returns 0, leaves the result to sceNetResolverGetError and reports `EPOLLIN | EPOLLDESCID` (`EPOLLHUP` after an abort) once to each epoll holding it, as shadPS4 does (unmeasured). A negative timeout or retry and any other flag bit fail with `EINVAL` before anything else is checked, as [prosper's reading of the libSceNet module](https://github.com/mattias800/prosper/blob/6c09a34a092a00898fcead5f855200ca7c368cd2/prosper/src/hle/net/hle_net.cpp#L579-L587) gives. A timeout from 1 to 999999 also fails with `EINVAL` before the resolver is looked up, and 0 counts as 1000000, as @BunnyFl's [13.42 run of the module](https://github.com/boykopovar/AnyPS5/pull/9727#issuecomment-6362795252) shows. Titles checked pass timeout 0, retry 0 or 1 and flags 0 or 0x1. -- [sceNetResolverCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - non-zero `flags` fail with `EINVAL`, as [prosper's reading of the libSceNet module](https://github.com/mattias800/prosper/blob/6c09a34a092a00898fcead5f855200ca7c368cd2/prosper/src/hle/net/hle_net.cpp#L564-L578) gives. Every direct call in the titles checked passes the constant 0 as flags, so none reaches the check; one title also reaches the function through a tail jump, which is not read. +- [sceNetResolverCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - non-zero `flags` fail with `EINVAL`, as [prosper's reading of the libSceNet module](https://github.com/mattias800/prosper/blob/6c09a34a092a00898fcead5f855200ca7c368cd2/prosper/src/hle/net/hle_net.cpp#L564-L578) gives. Every direct call in the titles checked passes the constant 0 as flags, so none reaches the check; one title also reaches the function through a tail jump, which is not read. Ids are the lowest free number from 1024 to 0x3fff, above libkernel's socket numbers (below 1024) and below epoll ids (from 0x4000), so a resolver and a socket in one epoll never share an id; the console's resolver ids are not known, and creating a resolver with all 15,360 ids live throws. - [sceNetDhcpStart, sceNetAddrConfig6Start, sceNetDuplicateIpStart and sceNetInfoDumpStart](../../../core/libs/prx/libSceNet/Export.cpp) - argument widths and callback ABI are recovered. Address callbacks receive user data, a 32-bit event and a 32-bit error. DHCP configuration is mutable; the 512-byte IPv6 configuration is read-only. Their complete field layouts remain unknown. The two 32-bit thread parameters are stored but not consumed by this firmware thread wrapper, so their original names and units are unconfirmed. Information-dump options contain flags, an interval in seconds and a 64-byte path. All four exports remain unimplemented. - [sceNetSetDnsInfo, sceNetSetDns6Info and sceNetGetDns6Info](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the resolver still uses the host's DNS servers; setters only store servers for the getters. Without an IPv6 override, the getter reports none. Counts follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0918faa0255e9816b795b62c9b09eaea51a1bc5e/src/core/libraries/net/net.cpp), not hardware measurements. All four DNS calls' initialization and argument errors match a reported [firmware 13.42 module probe](https://github.com/boykopovar/AnyPS5/pull/9506#issuecomment-6390574180): ENOTINIT first, then EINVAL for nonzero flags, null getter outputs or zero primary servers; null setters clear the override. Setters use a checked guest read and return EFAULT for unreadable non-null inputs, an AnyPS5 safety measure absent from the probed module. From d6a80388535aadb211a4d516bb944bbfecb15f15 Mon Sep 17 00:00:00 2001 From: JJenkx <31495916+JJenkx@users.noreply.github.com> Date: Mon, 2 Nov 2026 13:51:56 -0500 Subject: [PATCH 333/612] fix(http2): carry cookie boxes through HTTP1 transport (cherry picked from commit a73753f129c9c02b6eee9781afa370975e9f3b7d) Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceHttp2/CMakeLists.txt | 5 +- core/libs/prx/libSceHttp2/Transport.cpp | 22 +++++- core/libs/tests/GuestHttp2Transport.cpp | 96 ++++++++++++++++++++++-- docs/dev/debt/libSceHttp2.md | 3 +- 4 files changed, 115 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceHttp2/CMakeLists.txt b/core/libs/prx/libSceHttp2/CMakeLists.txt index 5b8dbc48e4..86ba10a2d1 100644 --- a/core/libs/prx/libSceHttp2/CMakeLists.txt +++ b/core/libs/prx/libSceHttp2/CMakeLists.txt @@ -74,9 +74,10 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() add_test(NAME guest_http2_http1 COMMAND guest_http2_transport_tests --http1) - set_tests_properties(guest_http2_http1 PROPERTIES TIMEOUT 30) + add_test(NAME guest_http2_http1_cookies COMMAND guest_http2_transport_tests --http1-cookies) + set_tests_properties(guest_http2_http1 guest_http2_http1_cookies PROPERTIES TIMEOUT 30) if(WIN32) - set_tests_properties(guest_http2_http1 PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_http2_http1 guest_http2_http1_cookies PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test(NAME guest_http2_ssl_callbacks COMMAND guest_http2_transport_tests --ssl-callbacks) diff --git a/core/libs/prx/libSceHttp2/Transport.cpp b/core/libs/prx/libSceHttp2/Transport.cpp index aac4cf235f..959c2fbe03 100644 --- a/core/libs/prx/libSceHttp2/Transport.cpp +++ b/core/libs/prx/libSceHttp2/Transport.cpp @@ -361,7 +361,7 @@ struct Request::State { ~State() { Close(); } - std::string Address() const { return "https://" + url.authority + url.path; } + std::string Address() const { return std::string(url.secure ? "https://" : "http://") + url.authority + url.path; } void Close() { callbackLock = nullptr; @@ -655,6 +655,8 @@ struct Request::State { Require(!hasLocation, "sceHttp2: duplicate HTTP1 Location"); hasLocation = true; location = value; + } else if (name == "set-cookie" && settings.cookieBox != 0) { + Http2StoreResponseCookie(settings.cookieBox, Address(), value); } } if (status >= 100 && status < 200) { @@ -678,7 +680,23 @@ struct Request::State { header += "Host: " + authority + "\r\nConnection: close\r\n"; if (!settings.userAgent.empty() && std::none_of(settings.headers.begin(), settings.headers.end(), [](const auto& field) { return field.first == "user-agent"; })) header += "User-Agent: " + settings.userAgent + "\r\n"; - for (const auto& [name, value] : settings.headers) if (name != "host") header += name + ": " + value + "\r\n"; + std::string cookies; + bool hasCookie = false; + for (const auto& [name, value] : settings.headers) { + if (name == "cookie") { + if (!cookies.empty() && !value.empty()) cookies += "; "; + cookies += value; + hasCookie = true; + } else if (name != "host") header += name + ": " + value + "\r\n"; + } + if (settings.cookieBox != 0) { + if (const auto stored = Http2CookieHeader(settings.cookieBox, Address())) { + if (!cookies.empty()) cookies += "; "; + cookies += *stored; + hasCookie = true; + } + } + if (hasCookie) header += "cookie: " + cookies + "\r\n"; const bool chunkUpload = settings.noContentLength && size; if (chunkUpload) header += "Transfer-Encoding: chunked\r\n"; else if (!settings.noContentLength) header += "Content-Length: " + std::to_string(size) + "\r\n"; diff --git a/core/libs/tests/GuestHttp2Transport.cpp b/core/libs/tests/GuestHttp2Transport.cpp index 3eb2cfbf99..eec4762de5 100644 --- a/core/libs/tests/GuestHttp2Transport.cpp +++ b/core/libs/tests/GuestHttp2Transport.cpp @@ -92,8 +92,9 @@ std::string StoredGzip(const std::string& payload) { class Server { public: - explicit Server(Mode mode, std::string location = "/another", int redirectStatus = 302) - : mode(mode), location(std::move(location)), redirectStatus(redirectStatus) { + explicit Server(Mode mode, std::string location = "/another", int redirectStatus = 302, + std::vector responseCookies = {}) + : mode(mode), location(std::move(location)), redirectStatus(redirectStatus), responseCookies(std::move(responseCookies)) { mbedtls_net_init(&listener); mbedtls_entropy_init(&entropy); mbedtls_ctr_drbg_init(&random); @@ -159,6 +160,7 @@ private: const Mode mode; const std::string location; const int redirectStatus; + const std::vector responseCookies; unsigned accepted = 0; mbedtls_net_context listener; mbedtls_entropy_context entropy; @@ -408,6 +410,7 @@ private: if (mode == Mode::Http11BadLength) response += "Content-Length: 14\r\n"; if (mode == Mode::Http11Ambiguous) response += "Transfer-Encoding: chunked\r\n"; if (mode == Mode::Http11Redirect) response += "Location: " + location + "\r\n"; + for (const auto& cookie : responseCookies) response += "Set-Cookie: " + cookie + "\r\n"; response += "X-Loopback: yes\r\nConnection: close\r\n\r\n"; if (mode == Mode::Http11StallBody) { write(response); Stage(2); Stall(); return; } if (headers.find(":method")->second != "HEAD") { @@ -521,10 +524,85 @@ void CookieBox(int ssl) { Require(sceHttp2DeleteCookieBox(box) == 0, "delete cookie box"); } -void CookieBoxDelete(int ssl) { - std::fprintf(stderr, "HTTP2 cookie box delete scenario\n"); - Server server(Mode::CookieFlood); - Client client(ssl); +void Http1CookieBox(int ssl, Mode mode) { + std::fprintf(stderr, "HTTP1 cookie box scenario %d\n", static_cast(mode)); + Server server(mode, "/another", 302, {"first=one; Path=/", "second=two; Path=/"}); + Client client(ssl, 1, 0, mode == Mode::Http10 ? 1 : 2); + const auto url = server.Url(); + const auto secureUrl = mode == Mode::Http1Plain ? "https" + url.substr(4) : url; + const int box = sceHttp2CreateCookieBox(0x10000); + Require(box > 0, "HTTP1 cookie box"); + for (const std::string cookie : {"stored=old; Path=/", "secure=secret; Path=/; Secure", "hidden=path; Path=/other"}) + Require(sceHttp2AddCookie(box, secureUrl.c_str(), cookie.c_str(), cookie.size()) == 0, "HTTP1 seed cookie"); + Require(sceHttp2SetCookieBox(client.tmpl, box) == 0, "HTTP1 bind template cookie box"); + Require(sceHttp2AddRequestHeader(client.tmpl, "Cookie", "replaced=template", 0) == 0, "HTTP1 template cookie"); + client.Create(url); + Require(sceHttp2AddRequestHeader(client.request, "Cookie", "manual=one", 0) == 0, "HTTP1 replace manual cookie"); + Require(sceHttp2AddRequestHeader(client.request, "Cookie", "extra=two", 1) == 0, "HTTP1 append manual cookie"); + Require(sceHttp2AddRequestHeader(client.request, "Host", "other.example", 0) == 0, "HTTP1 override host without changing cookie origin"); + Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "HTTP1 send with cookie box"); + std::array buffer{}; + while (sceHttp2ReadData(client.request, buffer.data(), buffer.size()) > 0) {} + server.Join(); + const auto& headers = server.Headers(); + Require(headers.count("cookie") == 1, "HTTP1 exactly one cookie header"); + const auto& sent = headers.find("cookie")->second; + Require(sent.starts_with("manual=one; extra=two; ") && sent.find("stored=old") != std::string::npos, + "HTTP1 manual and matching box cookies sent together"); + Require((sent.find("secure=secret") != std::string::npos) == (mode != Mode::Http1Plain), "HTTP1 secure cookie follows URL scheme"); + Require(sent.find("hidden=") == std::string::npos && sent.find("replaced=") == std::string::npos, + "HTTP1 path selection and manual replacement"); + std::size_t required = 0; + Require(sceHttp2GetCookie(box, url.c_str(), buffer.data(), &required, buffer.size()) == 0, "HTTP1 response cookie lookup"); + const std::string_view stored(buffer.data()); + Require(stored.find("first=one") != std::string_view::npos && stored.find("second=two") != std::string_view::npos, + "HTTP1 each response cookie stored"); + Require(sceHttp2DeleteCookieBox(box) == 0, "HTTP1 delete cookie box"); +} + +void Http1CookieRedirect(int ssl) { + Server target(Mode::Http1Plain); + Server redirect(Mode::Http11Redirect, target.Url(), 302, {"ordinary=next; Path=/", "secure=secret; Path=/; Secure"}); + Client client(ssl, 1, 0, 2); + const int box = sceHttp2CreateCookieBox(0x10000); + Require(box > 0 && sceHttp2SetCookieBox(client.tmpl, box) == 0, "HTTP1 redirect bind cookie box"); + client.Create(redirect.Url()); + Require(sceHttp2AddRequestHeader(client.request, "Cookie", "manual=origin", 0) == 0, "HTTP1 origin manual cookie"); + Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "HTTP1 cookie redirect"); + std::array buffer; + while (sceHttp2ReadData(client.request, buffer.data(), buffer.size()) > 0) {} + redirect.Join(); + target.Join(); + const auto& headers = target.Headers(); + Require(headers.count("cookie") == 1 && headers.find("cookie")->second == "ordinary=next", + "HTTP1 redirect stores cookies and excludes secure and origin-only manual cookies on plain target"); + Require(sceHttp2DeleteCookieBox(box) == 0, "HTTP1 redirect delete cookie box"); +} + +void Http1EmptyCookie(int ssl, bool explicitCookie) { + Server server(Mode::Http1Plain); + Client client(ssl, 1, 0, 2); + const int box = sceHttp2CreateCookieBox(0x10000); + Require(box > 0 && sceHttp2SetCookieBox(client.tmpl, box) == 0, "HTTP1 empty bind cookie box"); + client.Create(server.Url()); + if (explicitCookie) Require(sceHttp2AddRequestHeader(client.request, "Cookie", "", 0) == 0, "HTTP1 explicit empty cookie"); + Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "HTTP1 empty box send"); + std::array buffer; + while (sceHttp2ReadData(client.request, buffer.data(), buffer.size()) > 0) {} + server.Join(); + const auto& headers = server.Headers(); + Require(headers.count("cookie") == (explicitCookie ? 1u : 0u), "HTTP1 empty box does not invent a cookie field"); + if (explicitCookie) Require(headers.find("cookie")->second.empty(), "HTTP1 explicit empty cookie preserved"); + Require(sceHttp2DeleteCookieBox(box) == 0, "HTTP1 empty delete cookie box"); +} + +void CookieBoxDelete(int ssl, Mode mode = Mode::CookieFlood) { + std::fprintf(stderr, "cookie box delete scenario %d\n", static_cast(mode)); + std::vector responseCookies; + if (mode != Mode::CookieFlood) + for (int index = 0; index < 1500; ++index) responseCookies.push_back("item" + std::to_string(index) + "=value; Path=/"); + Server server(mode, "/another", 302, std::move(responseCookies)); + Client client(ssl, 4, 0, mode == Mode::CookieFlood ? 3 : 2); const std::string url = server.Url("127.0.0.1"); const int box = sceHttp2CreateCookieBox(0x10000); Require(box > 0 && sceHttp2SetCookieBox(client.tmpl, box) == 0, "bind cookie box"); @@ -1427,6 +1505,12 @@ int main(int argc, char** argv) { Http1Policies(ssl); Http1Cancellation(ssl); Http1BufferedDeadline(ssl); + } else if (argc == 2 && std::string_view(argv[1]) == "--http1-cookies") { + for (const auto mode : {Mode::Http10, Mode::Http11, Mode::Http11Chunked, Mode::Http11Close, Mode::Http1Plain}) Http1CookieBox(ssl, mode); + Http1CookieRedirect(ssl); + for (const bool explicitCookie : {false, true}) Http1EmptyCookie(ssl, explicitCookie); + CookieBoxDelete(ssl, Mode::Http11); + CookieBoxDelete(ssl, Mode::Http1Plain); } else if (argc == 2 && std::string_view(argv[1]) == "--ssl-callbacks") { SslCallbacks(ssl); } else if (argc == 2 && std::string_view(argv[1]) == "--redirects") { diff --git a/docs/dev/debt/libSceHttp2.md b/docs/dev/debt/libSceHttp2.md index 4aaed083ee..b8daf11e53 100644 --- a/docs/dev/debt/libSceHttp2.md +++ b/docs/dev/debt/libSceHttp2.md @@ -21,7 +21,8 @@ - [sceHttp2DeleteCookieBox](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - imported by PPSA35943's Unity player. The `(int cookieBoxId)` signature follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http2.cs#L215-L217). A box `sceHttp2CreateCookieBox` returned is discarded; an unknown or already deleted id returns the library's invalid-id error (0x817B1100), as [prosper](https://github.com/mattias800/prosper/blob/b1bec57186a0/prosper/src/hle/net/hle_http2.cpp#L512-L523) does; not measured on a console - [libSceHttp2 cookie boxes](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - `sceHttp2CreateCookieBox` takes a machine-sized pool size and requires at least 64 KiB. On 13.42 the pool is rounded up to 16 KiB (wrapping to 0 for `SIZE_MAX`) and mapped with `sceKernelMapNamedFlexibleMemory`, whose error is returned unchanged; the host maps it the same way under an assumed name, but does not reproduce native cookie-heap bookkeeping, identifier ranges or retained request references. Its delete signature and invalid-ID error are confirmed. `sceHttp2SetRequestNoContentLength` takes only the request id. `sceHttp2CookieFlush` empties a cookie box and answers 0 for any other id. -- [sceHttp2SetCookieBox and sceHttp2GetCookieBox](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - follow an emulator run of the 13.42 module: templates and requests accept a live box or 0 (unbind), other ids and unknown or deleted boxes return 0x817B1100, a new request copies its template's box, a deleted box stays reported by the requests holding it, and `sceHttp2GetCookieBox(id, int*)` returns 0x817B1225 for a null output before writing 0 and checking the id, then 0x817B1025 when no box is bound. Not measured (the run cannot reach the network): a request with a bound box sends the box's matching cookies as one extra `cookie` field next to any added by `sceHttp2AddRequestHeader`, and every `Set-Cookie` of the response is stored in the box with the rules of `sceHttp2AddCookie`, with rejected cookies dropped silently +- [sceHttp2SetCookieBox and sceHttp2GetCookieBox](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - follow an emulator run of the 13.42 module: templates and requests accept a live box or 0 (unbind), other ids and unknown or deleted boxes return 0x817B1100, a new request copies its template's box, a deleted box stays reported by the requests holding it, and `sceHttp2GetCookieBox(id, int*)` returns 0x817B1225 for a null output before writing 0 and checking the id, then 0x817B1025 when no box is bound. +- [libSceHttp2 cookie transport](../../../core/libs/prx/libSceHttp2/Transport.cpp) (libSceHttp2) - network behavior is unmeasured on the console; the 13.42 module run cannot reach the network. Matching box cookies accompany manual cookies as an extra HTTP/2 field or one combined HTTP/1 field following RFC 6265. Response `Set-Cookie` fields use `sceHttp2AddCookie` rules, silently dropping rejected cookies. Manual/automatic cookie composition and redirect behavior remain unverified - [libSceHttp2 cookie store](../../../core/libs/prx/libSceHttp2/Cookies.cpp) (libSceHttp2) - behavior follows a run of `GuestHttp2Cookies.cpp`'s sequence against 13.42's `libSceHttp2.sprx` ([#10534](https://github.com/boykopovar/AnyPS5/pull/10534)). Not reproduced: the `0x817B1076` stack check below 0x1FD0 bytes of stack, and the pool size is not enforced. Assumed, not measured on libSceHttp2: the default total limit of 3000 with the 10 oldest dropped past it, and the NUL, space, NUL written after the header (both libSceHttp's), which of no match and null `cookie`/`required` is checked first, public-suffix rules for `Domain`, and dates beyond the tested ones. Equal-length paths come out with the first stored last, a rule inferred from the runs From bb39df9465cf076675ed61a1529e5ba515ad663d Mon Sep 17 00:00:00 2001 From: KernelPanicNT Date: Wed, 4 Nov 2026 14:59:53 +0000 Subject: [PATCH 334/612] test(libSceCesCs): drop the Jiscs checks that pin unsupported forms to throw The test now covers only the implemented results, errors and measured sweeps; the Shift-JIS, EUC-JP, plane 1 and non-JIS profile forms stay documented in the debt entry instead of being required to throw. --- core/libs/tests/GuestCesJiscs.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/core/libs/tests/GuestCesJiscs.cpp b/core/libs/tests/GuestCesJiscs.cpp index 2b4dec5a91..9d292a959e 100644 --- a/core/libs/tests/GuestCesJiscs.cpp +++ b/core/libs/tests/GuestCesJiscs.cpp @@ -2,7 +2,6 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include #include -#include namespace { @@ -15,12 +14,6 @@ constexpr std::uint32_t poison = 0xDEADBEEF; void Require(bool value) { if (!value) std::abort(); } -template -void RequireThrows(Action action) { - try { action(); } catch (const std::runtime_error&) { return; } - std::abort(); -} - std::uint64_t Fnv(std::uint64_t hash, std::uint64_t value, int bytes) { for (int i = 0; i < bytes; ++i) { hash ^= value >> (8 * i) & 0xFF; @@ -146,11 +139,5 @@ int main() { Require(sceCesJiscsToUcs(profiles[0], 0x2422, nullptr) == invalidDstBuffer); Require(sceCesUcsToJiscs(0x3042, profiles[0], nullptr) == invalidDstBuffer); Require(sceCesJiscsToUcs(nullptr, 0x2422, nullptr) == invalidProfile); - - RequireThrows([&] { std::uint32_t out = 0; sceCesJiscsToUcs(profiles[0], 0x82A0, &out); }); - RequireThrows([&] { std::uint32_t out = 0; sceCesJiscsToUcs(profiles[4], 0x8FA2AF, &out); }); - RequireThrows([&] { std::uint32_t out = 0; sceCesUcsToJiscs(0x1F600, profiles[6], &out); }); - RequireThrows([&] { std::uint32_t out = 0; sceCesUcsToJiscs(0x20B9F, profiles[0], &out); }); - RequireThrows([&] { std::uint32_t out = 0; sceCesUcsToJiscs(0x3042, sceCesRefersUcsProfileCp1252(), &out); }); return 0; } From 6098f93a1280ce970b650f41cfba78936eb6ec17 Mon Sep 17 00:00:00 2001 From: Ashwin Date: Sun, 1 Nov 2026 12:18:27 +0530 Subject: [PATCH 335/612] feat(libSceNKWebKit): implement WTF::URL::lastPathComponent and strippedForUseAsReferrer Co-Authored-By: Claude Sonnet 5.5 --- core/libs/prx/libSceNKWebKit/Export.cpp | 8 -- .../include/InventoryExports.hpp | 2 - .../prx/libSceNKWebKit/include/WtfExports.hpp | 2 + .../libs/prx/libSceNKWebKit/src/WtfString.cpp | 46 +++++++++ core/libs/prx/libSceNKWebKit/tests/Wtf.cpp | 97 ++++++++++++++++++- docs/dev/debt/libSceNKWebKit.md | 3 +- 6 files changed, 145 insertions(+), 13 deletions(-) diff --git a/core/libs/prx/libSceNKWebKit/Export.cpp b/core/libs/prx/libSceNKWebKit/Export.cpp index 9a27cfdb7d..67dc1d91f8 100644 --- a/core/libs/prx/libSceNKWebKit/Export.cpp +++ b/core/libs/prx/libSceNKWebKit/Export.cpp @@ -36524,10 +36524,6 @@ int APS5_VABI _ZNK3WTF3URL16isMatchingDomainENS_10StringViewE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK3WTF3URL17lastPathComponentEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK3WTF3URL19protocolHostAndPortEv() { NotImplemented_nid_no_patch(__func__); } @@ -36536,10 +36532,6 @@ int APS5_VABI _ZNK3WTF3URL21truncatedForUseAsBaseEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK3WTF3URL24strippedForUseAsReferrerEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK3WTF3URL4dumpERNS_11PrintStreamE() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp index 240be99b7b..884ced81c6 100644 --- a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp @@ -6125,10 +6125,8 @@ int APS5_VABI _ZNK3WTF24TimeWithDynamicClockType4dumpERNS_11PrintStreamE(); int APS5_VABI _ZNK3WTF3URL11hostAndPortEv(); int APS5_VABI _ZNK3WTF3URL14fileSystemPathEv(); int APS5_VABI _ZNK3WTF3URL16isMatchingDomainENS_10StringViewE(); -int APS5_VABI _ZNK3WTF3URL17lastPathComponentEv(); int APS5_VABI _ZNK3WTF3URL19protocolHostAndPortEv(); int APS5_VABI _ZNK3WTF3URL21truncatedForUseAsBaseEv(); -int APS5_VABI _ZNK3WTF3URL24strippedForUseAsReferrerEv(); int APS5_VABI _ZNK3WTF3URL4dumpERNS_11PrintStreamE(); int APS5_VABI _ZNK3WTF3URL4userEv(); int APS5_VABI _ZNK3WTF3URL8passwordEv(); diff --git a/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp b/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp index 63c9dd727e..dd4ae5e5b6 100644 --- a/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp @@ -216,8 +216,10 @@ bool APS5_VABI _ZNK3WTF3URL12isAboutBlankEv(const NKWebKit::WtfAbi::URL* self); bool APS5_VABI _ZNK3WTF3URL13isAboutSrcDocEv(const NKWebKit::WtfAbi::URL* self); bool APS5_VABI _ZNK3WTF3URL14isHierarchicalEv(const NKWebKit::WtfAbi::URL* self); NKWebKit::WtfAbi::StringView APS5_VABI _ZNK3WTF3URL15encodedPasswordEv(const NKWebKit::WtfAbi::URL* self); +NKWebKit::WtfAbi::StringView APS5_VABI _ZNK3WTF3URL17lastPathComponentEv(const NKWebKit::WtfAbi::URL* self); NKWebKit::WtfAbi::StringView APS5_VABI _ZNK3WTF3URL18fragmentIdentifierEv(const NKWebKit::WtfAbi::URL* self); bool APS5_VABI _ZNK3WTF3URL20protocolIsJavaScriptEv(const NKWebKit::WtfAbi::URL* self); +NKWebKit::WtfAbi::String APS5_VABI _ZNK3WTF3URL24strippedForUseAsReferrerEv(const NKWebKit::WtfAbi::URL* self); NKWebKit::WtfAbi::StringView APS5_VABI _ZNK3WTF3URL28queryWithLeadingQuestionMarkEv(const NKWebKit::WtfAbi::URL* self); NKWebKit::WtfAbi::StringView APS5_VABI _ZNK3WTF3URL39fragmentIdentifierWithLeadingNumberSignEv(const NKWebKit::WtfAbi::URL* self); NKWebKit::WtfAbi::StringView APS5_VABI _ZNK3WTF3URL4hostEv(const NKWebKit::WtfAbi::URL* self); diff --git a/core/libs/prx/libSceNKWebKit/src/WtfString.cpp b/core/libs/prx/libSceNKWebKit/src/WtfString.cpp index 0ec203f32c..15c854a626 100644 --- a/core/libs/prx/libSceNKWebKit/src/WtfString.cpp +++ b/core/libs/prx/libSceNKWebKit/src/WtfString.cpp @@ -877,6 +877,12 @@ unsigned HostStart(const URL& url) { return url.passwordEnd == url.userStart ? url.passwordEnd : url.passwordEnd + 1; } +unsigned CredentialsEnd(const URL& url, StringView view) { + unsigned end = url.passwordEnd; + if (end != url.hostEnd && end < view.length && Unit(view, end) == '@') ++end; + return end; +} + unsigned PathStart(const URL& url) { unsigned start = url.hostEnd + ((url.bits >> 3) & 7); auto view = View(url.impl); @@ -990,6 +996,46 @@ StringView APS5_VABI _ZNK3WTF3URL4pathEv(const URL* self) { return Slice(View(url.impl), start, url.pathEnd - start); } +StringView APS5_VABI _ZNK3WTF3URL17lastPathComponentEv(const URL* self) { + const auto& url = Checked(self); + if (url.pathEnd <= PathStart(url)) return {nullptr, 0, true}; + auto view = View(url.impl); + if (url.pathEnd > view.length) throw std::out_of_range("invalid WTF URL path boundary"); + unsigned end = url.pathEnd - 1; + if (Unit(view, end) == '/') --end; + std::size_t start = NotFound; + for (std::size_t i = std::min(end, view.length - 1) + 1; i--;) { + if (Unit(view, static_cast(i)) == '/') { + start = i; + break; + } + } + if (start < PathStart(url)) return {nullptr, 0, true}; + ++start; + return Slice(view, static_cast(start), end - static_cast(start) + 1); +} + +String APS5_VABI _ZNK3WTF3URL24strippedForUseAsReferrerEv(const URL* self) { + const auto& url = Checked(self); + auto view = View(url.impl); + if (!(url.bits & 1)) return String(Retain(url.impl)); + if (url.userStart > url.passwordEnd || url.queryEnd > view.length) throw std::out_of_range("invalid WTF URL credential boundary"); + unsigned end = CredentialsEnd(url, view); + if (url.userStart == end && url.queryEnd == view.length) return String(Retain(url.impl)); + if (end > url.queryEnd) throw std::out_of_range("invalid WTF URL query boundary"); + unsigned length = url.userStart + (url.queryEnd - end); + void* output; + auto* impl = NewImpl(length, view.is8Bit, output); + String result(impl); + if (!length) return result; + unsigned width = view.is8Bit ? 1 : 2; + auto* source = static_cast(view.data); + auto* target = static_cast(output); + std::memcpy(target, source, std::size_t(url.userStart) * width); + std::memcpy(target + std::size_t(url.userStart) * width, source + std::size_t(end) * width, std::size_t(url.queryEnd - end) * width); + return result; +} + StringView APS5_VABI _ZNK3WTF3URL11encodedUserEv(const URL* self) { const auto& url = Checked(self); return Slice(View(url.impl), url.userStart, url.userEnd - url.userStart); diff --git a/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp b/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp index 8508298479..fb6c7052c0 100644 --- a/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp +++ b/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp @@ -779,9 +779,9 @@ void Run() { } // namespace Wtf2Tests -namespace Wtf2Tests { void TestURLsAndReallocation(); } +namespace Wtf2Tests { void TestURLsAndReallocation(); void TestURLLastPathComponentAndReferrer(); } -void TestAdditionalWtf() { Wtf2Tests::Run(); Wtf2Tests::TestURLsAndReallocation(); } +void TestAdditionalWtf() { Wtf2Tests::Run(); Wtf2Tests::TestURLsAndReallocation(); Wtf2Tests::TestURLLastPathComponentAndReferrer(); } namespace Wtf2Tests { @@ -806,6 +806,8 @@ const auto URLEqualIgnoringFragment = &_ZN3WTF31equalIgnoringFragmentIdentifierE const auto IsJavaScript = &_ZN3WTF20protocolIsJavaScriptENS_10StringViewE; const auto IsHTTP = &_ZN3WTF22protocolIsInHTTPFamilyENS_10StringViewE; const auto JSRealloc = &_ZN3WTF14jsValueReallocEPvm; +const auto URLLastPathComponent = &_ZNK3WTF3URL17lastPathComponentEv; +const auto URLStrippedForReferrer = &_ZNK3WTF3URL24strippedForUseAsReferrerEv; std::string ViewText(StringView view) { return Bytes(ViewUTF8(&view, 1)); } @@ -1044,4 +1046,95 @@ void TestURLsAndReallocation() { TestURLStringHelpers(); } +void TestURLLastPathComponentAndReferrer() { + auto httpBits = [](unsigned schemeEnd, unsigned portLength) { return 1U | 2U | (portLength << 3) | (schemeEnd << 6); }; + auto simple = FromUTF8Z(U8("http://h/a/b")); + URL url{simple.impl, httpBits(4, 0), 7, 7, 7, 8, 11, 12, 12}; + Check(ViewText(URLLastPathComponent(&url)) == "b"); + auto trailing = FromUTF8Z(U8("http://h/a/b/")); + URL slashed{trailing.impl, httpBits(4, 0), 7, 7, 7, 8, 13, 13, 13}; + Check(ViewText(URLLastPathComponent(&slashed)) == "b"); + auto root = FromUTF8Z(U8("http://h/")); + URL rootUrl{root.impl, httpBits(4, 0), 7, 7, 7, 8, 9, 9, 9}; + Check(!URLLastPathComponent(&rootUrl).data); + auto hostOnly = FromUTF8Z(U8("http://h")); + URL noPath{hostOnly.impl, httpBits(4, 0), 7, 7, 7, 8, 8, 8, 8}; + Check(!URLLastPathComponent(&noPath).data); + auto opaque = FromUTF8Z(U8("mailto:abc")); + URL mailto{opaque.impl, 1U | 4U | (6U << 6), 7, 7, 7, 7, 7, 10, 10}; + Check(ViewText(URLLastPathComponent(&mailto)) == "mailto:abc"); + auto wide = Create16(u"http://h/a/b", 12); + URL wideUrl{wide.impl, httpBits(4, 0), 7, 7, 7, 8, 11, 12, 12}; + auto wideResult = URLLastPathComponent(&wideUrl); + Check(!wideResult.is8Bit && wideResult.length == 1 && ViewText(wideResult) == "b"); + auto port = FromUTF8Z(U8("http://h:80/dir/file.txt?q")); + URL withPort{port.impl, httpBits(4, 3), 7, 7, 7, 8, 16, 24, 26}; + Check(ViewText(URLLastPathComponent(&withPort)) == "file.txt"); + auto slashDot = FromUTF8Z(U8("foo:/.//a")); + URL dotted{slashDot.impl, 1U | (3U << 6), 4, 4, 4, 4, 8, 9, 9}; + Check(ViewText(URLLastPathComponent(&dotted)) == "a"); + auto beforePath = FromUTF8Z(U8("foo:/.a")); + URL guarded{beforePath.impl, 1U | (3U << 6), 4, 4, 4, 4, 5, 7, 7}; + Check(!URLLastPathComponent(&guarded).data); + auto emptyComponent = FromUTF8Z(U8("http://h//")); + URL doubled{emptyComponent.impl, httpBits(4, 0), 7, 7, 7, 8, 10, 10, 10}; + auto emptyResult = URLLastPathComponent(&doubled); + Check(emptyResult.data && emptyResult.length == 0); + URL absent{}; + Check(!URLLastPathComponent(&absent).data); + URL pastEnd = url; + pastEnd.pathEnd = 50; + Throws([&] { URLLastPathComponent(&pastEnd); }); + Throws([&] { URLLastPathComponent(nullptr); }); + + auto full = FromUTF8Z(U8("https://u:p@h:80/a?q#f")); + URL credentialed{full.impl, httpBits(5, 3), 8, 9, 11, 13, 17, 18, 20}; + auto stripped = URLStrippedForReferrer(&credentialed); + Check(Bytes(UTF8(&stripped, 1)) == "https://h:80/a?q" && stripped.impl != full.impl && stripped.impl->length == 16); + auto plain = FromUTF8Z(U8("http://h/a?q")); + URL untouched{plain.impl, httpBits(4, 0), 7, 7, 7, 8, 9, 10, 12}; + auto same = URLStrippedForReferrer(&untouched); + Check(same.impl == plain.impl && plain.impl->refCount == 4); + auto fragment = FromUTF8Z(U8("http://h/a#f")); + URL fragmentOnly{fragment.impl, httpBits(4, 0), 7, 7, 7, 8, 9, 10, 10}; + auto noFragment = URLStrippedForReferrer(&fragmentOnly); + Check(Bytes(UTF8(&noFragment, 1)) == "http://h/a"); + auto userOnly = FromUTF8Z(U8("http://u@h/a")); + URL userUrl{userOnly.impl, httpBits(4, 0), 7, 8, 8, 10, 11, 12, 12}; + auto noUser = URLStrippedForReferrer(&userUrl); + Check(Bytes(UTF8(&noUser, 1)) == "http://h/a"); + auto wideFull = Create16(u"http://u@h/a#f", 14); + URL wideCredentialed{wideFull.impl, httpBits(4, 0), 7, 8, 8, 10, 11, 12, 12}; + auto wideStripped = URLStrippedForReferrer(&wideCredentialed); + Check(!(wideStripped.impl->hashAndFlags & 4) && wideStripped.impl->length == 10 && Bytes(UTF8(&wideStripped, 1)) == "http://h/a"); + URL invalid{plain.impl, 0, 0, 0, 0, 0, 0, 0, 0}; + { + const unsigned before = plain.impl->refCount; + auto kept = URLStrippedForReferrer(&invalid); + Check(kept.impl == plain.impl && plain.impl->refCount == before + 2); + } + Check(plain.impl->refCount == 4); + URL badBounds = untouched; + badBounds.queryEnd = 99; + Throws([&] { URLStrippedForReferrer(&badBounds); }); + auto passwordOnly = FromUTF8Z(U8("http://:p@h/")); + URL passwordUrl{passwordOnly.impl, httpBits(4, 0), 7, 7, 9, 11, 12, 12, 12}; + auto noPassword = URLStrippedForReferrer(&passwordUrl); + Check(Bytes(UTF8(&noPassword, 1)) == "http://h/"); + URL reversedCredentials = userUrl; + reversedCredentials.userStart = 200; + Throws([&] { URLStrippedForReferrer(&reversedCredentials); }); + auto hostless = FromUTF8Z(U8("mailto:@x")); + URL atSign{hostless.impl, 1U | 4U | (6U << 6), 7, 7, 7, 7, 7, 9, 9}; + Check(URLStrippedForReferrer(&atSign).impl == hostless.impl); + auto allCredentials = FromUTF8Z(U8("ab")); + URL consumed{allCredentials.impl, 1U, 0, 0, 0, 0, 0, 0, 0}; + auto vanished = URLStrippedForReferrer(&consumed); + Check(vanished.impl && vanished.impl->length == 0 && vanished.impl != allCredentials.impl); + URL queryBeforeCredentials = userUrl; + queryBeforeCredentials.queryEnd = 8; + Throws([&] { URLStrippedForReferrer(&queryBeforeCredentials); }); + Throws([&] { URLStrippedForReferrer(nullptr); }); +} + } // namespace Wtf2Tests diff --git a/docs/dev/debt/libSceNKWebKit.md b/docs/dev/debt/libSceNKWebKit.md index 537fa34590..7572afa9d8 100644 --- a/docs/dev/debt/libSceNKWebKit.md +++ b/docs/dev/debt/libSceNKWebKit.md @@ -17,6 +17,7 @@ Inventory prose retains historical counts; use the [generated progress report](h - [JavaScriptCore C API](../../../core/libs/prx/libSceNKWebKit/src/JscContext.cpp) (libSceNKWebKit) - `JS*` runs on QuickJS-NG (`3rdparty/quickjs`), not JavaScriptCore: there is no JIT and no conservative stack scan, and error messages, stack traces and property order come from QuickJS. An unprotected `JSObjectRef` held only in a native variable or heap structure across `JSGarbageCollect` is collected, where JavaScriptCore keeps it through its stack scan; `JSValueProtect`, a marking constraint, a JavaScript reference or an active callback frame keep it alive. `JSStringRef` values are `libSceJsc` strings. - [Dictionary key order](../../../core/libs/prx/libSceNKWebKit/src/Dictionary.cpp) (libSceNKWebKit) - `WKDictionaryCopyKeys` returns keys sorted by UTF-16 code unit, not in the `WTF::HashMap` iteration order of [WKDictionary.cpp](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebKit/Shared/API/c/WKDictionary.cpp). +- [URL boundary fields](../../../core/libs/prx/libSceNKWebKit/src/WtfString.cpp) (libSceNKWebKit) - `URL::lastPathComponent` and `URL::strippedForUseAsReferrer` follow the [pinned URL.cpp](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WTF/wtf/URL.cpp) for consistent URLs, but throw `std::out_of_range` where upstream clamps inconsistent boundary fields (`pathEnd` or `queryEnd` beyond the string, `userStart` after `passwordEnd`, a credential end after `queryEnd`). Closes when: the console behaviour for inconsistent fields is measured. - [Out-of-line colors](../../../core/libs/prx/libSceNKWebKit/src/WebCoreColor.cpp) (libSceNKWebKit) - `WebCore::Color` operations on an out-of-line value (extended color spaces in [Color.h](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebCore/platform/graphics/Color.h)) throw; only inline 8-bit sRGBA values are handled. - [WTF host services](../../../core/libs/prx/libSceNKWebKit/src/WtfSystem.cpp) (libSceNKWebKit) - `fastMalloc`, `bmalloc` and Gigacage allocations come from the host allocator, and `numberOfProcessorCores`, `ramSize`, the clocks and random values come from the host, not the console; they throw when the host cannot answer. `WTFCrash` and failed assertions abort the process. - [WebCore::ResourceResponseBase](../../../core/libs/prx/libSceNKWebKit/src/ResourceResponse.cpp) (libSceNKWebKit) - the object layout in [ResourceResponseAbi.hpp](../../../core/libs/prx/libSceNKWebKit/src/ResourceResponseAbi.hpp) comes from [ResourceResponseBase.h](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebCore/platform/network/ResourceResponseBase.h) and is not checked against the PS5 module: `URL` at 0, four `AtomString` and the 64-bit expected length up to offset 80, then a 32-byte `HTTPHeaderMap` (two vectors of inline capacity 0 as in [HTTPHeaderMap.h](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebCore/platform/network/HTTPHeaderMap.h)). Only these fields are modelled because `std::optional` follows the `Box` and the size of the PS5 `CertificateInfo` is unknown, so the offsets of `m_httpStatusCode`, `m_isNull`, `m_initLevel`, `m_type`, `m_source` and the flags are not established. The getters (`url`, `mimeType`, `expectedContentLength`, `textEncodingName`, `httpStatusText`, `httpVersion`, `httpHeaderFields`, `isInHTTPFamily`) skip `lazyInit` because the curl [ResourceResponse.h](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebCore/platform/network/curl/ResourceResponse.h) has no `platformLazyInit`; the PS5 `ResourceResponse` is not known to match. `isInHTTPFamily` reads the valid bit of `URL::bits` and the scheme text, not the cached `m_protocolIsInHTTPFamily` bit, whose position is unknown. The 30 other exports (`ResourceResponseBase` constructors, `setURL`, `setType`, `setExpectedContentLength`, `setHTTPStatusCode`, `httpStatusCode`, `source`, `isSuccessful`, `isRedirection`, `isHTTP09`, `isAttachment*`, `suggestedFilename`, `sanitize*`, `syntheticRedirectResponse`, `cacheControlContains*`, `hasCacheValidatorFields`, `lazyInit`, `httpHeaderField*`, `addHTTPHeaderField*`, `setHTTPHeaderField*`) still throw `NotImplemented_nid_no_patch`: they write `m_isNull`, read fields after `CertificateInfo`, or need `HTTPHeaderMap`, `CertificateInfo`, `Vector` or `ResourceResponse` layouts. @@ -33,7 +34,7 @@ Inventory prose retains historical counts; use the [generated progress report](h - The [revision-pinned identity inventory](https://gist.github.com/kyon-eth/d44995f181e1818977bd38f0e6fc0f34/8afcb14b0707a09b6274ff7f7bcf33ef46cfe2e7) records the module, library, symbol type and NIDs for the declarations in [Export.cpp](../../../core/libs/prx/libSceNKWebKit/Export.cpp); it is a metadata summary rather than independent verification. For the 255 absent from the public catalogue, [per-image ELF records](https://gist.github.com/kyon-eth/147be8dd556e9cced2577ccbed548972/1a659ecfe819e724675991a574a1aebb5fbb48e5) include the raw qualified names, module and library identities, symbol type, values and sizes; the record file SHA-256 is `44bdc4d6073192a8f41b7be359b75364b3d41dbbd11ce649924b8cebdd4ffe51`. Image labels are not authenticated. -- [Additional callable exports](../../../core/libs/prx/libSceNKWebKit/Export.cpp) - 7429 of the 8703 inventory declarations listed below throw through `NotImplemented_nid_no_patch`; signatures and behavior are unverified. The later 13.60-labelled image contains all 8703; 8448 match the public 3.20 catalogue, while 255 are absent and their presence in retail 3.20 is unverified. Per-image records show the latter in a separate 3.20-labelled image, but neither image label is authenticated. Identified names match NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). Data exports need separate analysis. +- [Additional callable exports](../../../core/libs/prx/libSceNKWebKit/Export.cpp) - 7427 of the 8703 inventory declarations listed below throw through NotImplemented_nid_no_patch; signatures and behavior are unverified. The later 13.60-labelled image contains all 8703; 8448 match the public 3.20 catalogue, while 255 are absent and their presence in retail 3.20 is unverified. Per-image records show the latter in a separate 3.20-labelled image, but neither image label is authenticated. Identified names match NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). Data exports need separate analysis. | Name | NID | | --- | --- | From ede1bb0eb43dd4092035882697285baa9f32ed98 Mon Sep 17 00:00:00 2001 From: Ashwin Date: Sun, 1 Nov 2026 20:50:48 +0530 Subject: [PATCH 336/612] feat(libSceNKWebKit): implement WTF::FileSystemImpl encodeForFileName, decodeFromFilename and lastComponentOfPathIgnoringTrailingSlash Co-Authored-By: Claude Sonnet 5.5 --- core/libs/prx/libSceNKWebKit/Export.cpp | 12 -- .../include/InventoryExports.hpp | 3 - .../prx/libSceNKWebKit/include/WtfExports.hpp | 3 + .../libs/prx/libSceNKWebKit/src/WtfString.cpp | 120 ++++++++++- core/libs/prx/libSceNKWebKit/tests/Wtf.cpp | 188 +++++++++++++++++- docs/dev/debt/libSceNKWebKit.md | 6 +- 6 files changed, 306 insertions(+), 26 deletions(-) diff --git a/core/libs/prx/libSceNKWebKit/Export.cpp b/core/libs/prx/libSceNKWebKit/Export.cpp index 9a27cfdb7d..43202f2640 100644 --- a/core/libs/prx/libSceNKWebKit/Export.cpp +++ b/core/libs/prx/libSceNKWebKit/Export.cpp @@ -19008,18 +19008,10 @@ int APS5_VABI _ZN3WTF14FileSystemImpl14setMetadataURLERKNS_6StringES3_S3_() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF14FileSystemImpl17encodeForFileNameERKNS_6StringE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF14FileSystemImpl18createSymbolicLinkERKNS_6StringES3_() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF14FileSystemImpl18decodeFromFilenameERKNS_6StringE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF14FileSystemImpl18hardLinkOrCopyFileERKNS_6StringES3_() { NotImplemented_nid_no_patch(__func__); } @@ -19064,10 +19056,6 @@ int APS5_VABI _ZN3WTF14FileSystemImpl34stringFromFileSystemRepresentationEPKc() NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF14FileSystemImpl40lastComponentOfPathIgnoringTrailingSlashERKNS_6StringE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF14FileSystemImpl8hardLinkERKNS_6StringES3_() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp index 240be99b7b..dc69399233 100644 --- a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp @@ -1708,9 +1708,7 @@ int APS5_VABI _ZN3WTF14FileSystemImpl14MappedFileDataC2ERKNS_6StringENS0_14Mappe int APS5_VABI _ZN3WTF14FileSystemImpl14MappedFileDataD1Ev(); int APS5_VABI _ZN3WTF14FileSystemImpl14MappedFileDataD2Ev(); int APS5_VABI _ZN3WTF14FileSystemImpl14setMetadataURLERKNS_6StringES3_S3_(); -int APS5_VABI _ZN3WTF14FileSystemImpl17encodeForFileNameERKNS_6StringE(); int APS5_VABI _ZN3WTF14FileSystemImpl18createSymbolicLinkERKNS_6StringES3_(); -int APS5_VABI _ZN3WTF14FileSystemImpl18decodeFromFilenameERKNS_6StringE(); int APS5_VABI _ZN3WTF14FileSystemImpl18hardLinkOrCopyFileERKNS_6StringES3_(); int APS5_VABI _ZN3WTF14FileSystemImpl18makeAllDirectoriesERKNS_6StringE(); int APS5_VABI _ZN3WTF14FileSystemImpl18unlockAndCloseFileEi(); @@ -1723,7 +1721,6 @@ int APS5_VABI _ZN3WTF14FileSystemImpl27isSafeToUseMemoryMapForPathERKNS_6StringE int APS5_VABI _ZN3WTF14FileSystemImpl29makeSafeToUseMemoryMapForPathERKNS_6StringE(); int APS5_VABI _ZN3WTF14FileSystemImpl30appendFileContentsToFileHandleERKNS_6StringERi(); int APS5_VABI _ZN3WTF14FileSystemImpl34stringFromFileSystemRepresentationEPKc(); -int APS5_VABI _ZN3WTF14FileSystemImpl40lastComponentOfPathIgnoringTrailingSlashERKNS_6StringE(); int APS5_VABI _ZN3WTF14FileSystemImpl8hardLinkERKNS_6StringES3_(); int APS5_VABI _ZN3WTF14FileSystemImpl8moveFileERKNS_6StringES3_(); int APS5_VABI _ZN3WTF14FileSystemImpl8openFileERKNS_6StringENS0_12FileOpenModeENS0_20FileAccessPermissionEb(); diff --git a/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp b/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp index 63c9dd727e..70de38996f 100644 --- a/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp @@ -210,6 +210,9 @@ bool APS5_VABI _ZNK3WTF24TimeWithDynamicClockTypeleERKS0_(const WTF::TimeWithDyn WTF::Seconds APS5_VABI _ZNK3WTF24TimeWithDynamicClockTypemiERKS0_(const WTF::TimeWithDynamicClockType* self, const WTF::TimeWithDynamicClockType& other); NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF19mimeTypeFromDataURLENS_10StringViewE(NKWebKit::WtfAbi::StringView dataURL); NKWebKit::WtfAbi::String APS5_VABI _ZNK3WTF3URL30stringCenterEllipsizedToLengthEj(const NKWebKit::WtfAbi::URL* self, unsigned length); +NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF14FileSystemImpl17encodeForFileNameERKNS_6StringE(const NKWebKit::WtfAbi::String* input); +NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF14FileSystemImpl18decodeFromFilenameERKNS_6StringE(const NKWebKit::WtfAbi::String* input); +NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF14FileSystemImpl40lastComponentOfPathIgnoringTrailingSlashERKNS_6StringE(const NKWebKit::WtfAbi::String* input); bool APS5_VABI _ZNK3WTF3URL10protocolIsENS_10StringViewE(const NKWebKit::WtfAbi::URL* self, NKWebKit::WtfAbi::StringView protocol); NKWebKit::WtfAbi::StringView APS5_VABI _ZNK3WTF3URL11encodedUserEv(const NKWebKit::WtfAbi::URL* self); bool APS5_VABI _ZNK3WTF3URL12isAboutBlankEv(const NKWebKit::WtfAbi::URL* self); diff --git a/core/libs/prx/libSceNKWebKit/src/WtfString.cpp b/core/libs/prx/libSceNKWebKit/src/WtfString.cpp index 0ec203f32c..bbbc4499c0 100644 --- a/core/libs/prx/libSceNKWebKit/src/WtfString.cpp +++ b/core/libs/prx/libSceNKWebKit/src/WtfString.cpp @@ -453,6 +453,13 @@ String SharedSubstring(const String* string, unsigned start, unsigned length) { return String(result); } +std::size_t ReverseFind(StringView view, char16_t value, unsigned start) { + if (!view.length) return NotFound; + std::size_t i = std::min(start, view.length - 1); + do { if (Unit(view, static_cast(i)) == value) return i; } while (i--); + return NotFound; +} + template String IntegerString(T value) { char buffer[32]; auto result = std::to_chars(buffer, buffer + sizeof(buffer), value); @@ -671,11 +678,7 @@ std::int32_t APS5_VABI _ZNK3WTF6String19characterStartingAtEj(const String* self } std::size_t APS5_VABI _ZN3WTF10StringImpl11reverseFindEDsj(const StringImpl* self, char16_t value, unsigned start) { - auto view = View(&Checked(self)); - if (!view.length) return NotFound; - std::size_t i = std::min(start, view.length - 1); - do { if (Unit(view, static_cast(i)) == value) return i; } while (i--); - return NotFound; + return ReverseFind(View(&Checked(self)), value, start); } String APS5_VABI _ZN3WTF10StringImpl9substringEjj(const StringImpl* self, unsigned start, unsigned length) { @@ -951,6 +954,43 @@ String ConcatViews(const StringView* parts, unsigned count) { return String(impl); } +bool IsLeadSurrogate(char16_t value) { return (value & 0xFC00) == 0xD800; } + +bool IsTrailSurrogate(char16_t value) { return (value & 0xFC00) == 0xDC00; } + +bool NeedsFileNameEscape(char16_t value, char16_t previous, char16_t next) { + if (value <= 127) { + if (value < 0x20 || value == 0x7F) return true; + switch (value) { + case ' ': case '"': case '%': case '*': case '/': case ':': case '<': case '>': case '?': case '\\': case '|': + return true; + default: + return false; + } + } + if (IsLeadSurrogate(value) && !IsTrailSurrogate(next)) return true; + return IsTrailSurrogate(value) && !IsLeadSurrogate(previous); +} + +void AppendHex(std::u16string& output, unsigned value, unsigned digits) { + static constexpr char16_t Digits[] = u"0123456789ABCDEF"; + for (unsigned shift = digits * 4; shift;) { + shift -= 4; + output.push_back(Digits[(value >> shift) & 15]); + } +} + +bool IsHexUnit(char16_t value) { + return (value >= '0' && value <= '9') || ((value | 32) >= 'a' && (value | 32) <= 'f'); +} + +unsigned HexUnitValue(char16_t value) { return value < 'A' ? value - '0' : (value - 'A' + 10) & 15; } + +String FinishFileName(const std::u16string& output) { + if (output.size() > MaxStringLength) throw std::length_error("WTF string exceeds maximum length"); + return String(CopyImpl({output.data(), static_cast(output.size()), false}, true)); +} + } // namespace extern "C" { @@ -1073,4 +1113,74 @@ String APS5_VABI _ZNK3WTF3URL30stringCenterEllipsizedToLengthEj(const URL* self, return ConcatViews(parts, 3); } +String APS5_VABI _ZN3WTF14FileSystemImpl17encodeForFileNameERKNS_6StringE(const String* input) { + const auto& string = Checked(input); + auto view = View(string.impl); + if (!view.length) return String(Retain(string.impl)); + std::u16string output; + output.reserve(view.length); + for (unsigned i = 0; i < view.length; ++i) { + auto value = Unit(view, i); + auto previous = i ? Unit(view, i - 1) : char16_t{0}; + auto next = i + 1 < view.length ? Unit(view, i + 1) : char16_t{0}; + if (!NeedsFileNameEscape(value, previous, next)) { + output.push_back(value); + continue; + } + if (value <= 255) { + output.push_back(u'%'); + AppendHex(output, value, 2); + } else { + output.push_back(u'%'); + output.push_back(u'+'); + AppendHex(output, value, 4); + } + if (output.size() > MaxStringLength) throw std::length_error("WTF string exceeds maximum length"); + } + return FinishFileName(output); +} + +String APS5_VABI _ZN3WTF14FileSystemImpl18decodeFromFilenameERKNS_6StringE(const String* input) { + const auto& string = Checked(input); + auto view = View(string.impl); + if (!view.length) return String(Retain(string.impl)); + std::u16string output; + output.reserve(view.length); + for (unsigned i = 0; i < view.length; ++i) { + if (Unit(view, i) != '%') { + output.push_back(Unit(view, i)); + continue; + } + if (std::size_t(i) + 2 >= view.length) return String(); + if (Unit(view, i + 1) != '+') { + if (!IsHexUnit(Unit(view, i + 1)) || !IsHexUnit(Unit(view, i + 2))) return String(); + output.push_back(static_cast(HexUnitValue(Unit(view, i + 1)) << 4 | HexUnitValue(Unit(view, i + 2)))); + i += 2; + continue; + } + if (std::size_t(i) + 5 >= view.length) return String(); + unsigned value = 0; + for (unsigned j = 2; j < 6; ++j) { + if (!IsHexUnit(Unit(view, i + j))) return String(); + value = value << 4 | HexUnitValue(Unit(view, i + j)); + } + output.push_back(static_cast(value)); + i += 5; + } + return FinishFileName(output); +} + +String APS5_VABI _ZN3WTF14FileSystemImpl40lastComponentOfPathIgnoringTrailingSlashERKNS_6StringE(const String* input) { + const auto& string = Checked(input); + auto view = View(string.impl); + std::size_t position = ReverseFind(view, u'/', 0xFFFFFFFFU); + if (position == NotFound) return String(Retain(string.impl)); + std::size_t end = view.length - 1; + if (position == end) { + --end; + position = ReverseFind(view, u'/', static_cast(end)); + } + return Substring(string.impl, static_cast(position + 1), static_cast(end - position)); +} + } diff --git a/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp b/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp index 8508298479..30b839fe1e 100644 --- a/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp +++ b/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp @@ -779,9 +779,9 @@ void Run() { } // namespace Wtf2Tests -namespace Wtf2Tests { void TestURLsAndReallocation(); } +namespace Wtf2Tests { void TestURLsAndReallocation(); void TestFileNameHelpers(); } -void TestAdditionalWtf() { Wtf2Tests::Run(); Wtf2Tests::TestURLsAndReallocation(); } +void TestAdditionalWtf() { Wtf2Tests::Run(); Wtf2Tests::TestURLsAndReallocation(); Wtf2Tests::TestFileNameHelpers(); } namespace Wtf2Tests { @@ -883,6 +883,190 @@ void TestURLStringHelpers() { Text(DataURLMime(ViewOf(widePlain)), "text/plain"); } +const auto EncodeName = &_ZN3WTF14FileSystemImpl17encodeForFileNameERKNS_6StringE; +const auto DecodeName = &_ZN3WTF14FileSystemImpl18decodeFromFilenameERKNS_6StringE; +const auto LastComponent = &_ZN3WTF14FileSystemImpl40lastComponentOfPathIgnoringTrailingSlashERKNS_6StringE; + +bool NameIs8Bit(const String& value) { return value.impl && (value.impl->hashAndFlags & 4); } + +std::u16string NameUnits(const String& value) { + Check(value.impl != nullptr); + std::u16string units; + for (unsigned i = 0; i < value.impl->length; ++i) { + units.push_back(NameIs8Bit(value) ? static_cast(value.impl->data)[i] : static_cast(value.impl->data)[i]); + } + return units; +} + +String Wide(const std::u16string& units) { return Create16(units.data(), static_cast(units.size())); } + +String NameString(const char* text) { return Create8(U8(text), static_cast(std::strlen(text))); } + +void TestFileNameHelpers() { + for (unsigned c = 0; c < 128; ++c) { + char16_t unit = static_cast(c); + auto encoded = EncodeName(&(const String&)Wide(std::u16string(1, unit))); + bool escaped = c < 0x20 || c == 0x7F || std::strchr(" \"%*/:<>?\\|", static_cast(c)) != nullptr; + if (escaped) { + static const char Hex[] = "0123456789ABCDEF"; + std::u16string expected{u'%', static_cast(Hex[c >> 4]), static_cast(Hex[c & 15])}; + Check(NameUnits(encoded) == expected); + } else { + Check(NameUnits(encoded) == std::u16string(1, unit)); + } + Check(NameIs8Bit(encoded)); + } + + Text(EncodeName(&(const String&)NameString("a b/c%")), "a%20b%2Fc%25"); + Text(EncodeName(&(const String&)NameString("#+.~ -_")), "#+.~%20-_"); + Text(EncodeName(&(const String&)NameString("a\\b|c:d*e?fh\"i")), "a%5Cb%7Cc%3Ad%2Ae%3Ff%3Cg%3Eh%22i"); + + auto accented = Wide(u"café"); + Check(!NameIs8Bit(accented)); + auto accentedEncoded = EncodeName(&accented); + Check(NameUnits(accentedEncoded) == u"café" && NameIs8Bit(accentedEncoded)); + Check(accentedEncoded.impl != accented.impl); + + const unsigned char latin1Source[] = {'c', 'a', 'f', 0xE9, '/'}; + auto latin1Bytes = Create8(latin1Source, 5); + auto latin1Encoded = EncodeName(&latin1Bytes); + Check(NameUnits(latin1Encoded) == u"café%2F" && NameIs8Bit(latin1Encoded)); + auto latin1Decoded = DecodeName(&latin1Encoded); + Check(NameUnits(latin1Decoded) == u"café/" && NameIs8Bit(latin1Decoded)); + + auto wide = Wide(u"a中 b"); + auto wideEncoded = EncodeName(&wide); + Check(NameUnits(wideEncoded) == u"a中%20b" && !NameIs8Bit(wideEncoded)); + + auto paired = Wide(u"\xD83D\xDE00"); + auto pairedEncoded = EncodeName(&paired); + Check(NameUnits(pairedEncoded) == u"\xD83D\xDE00" && !NameIs8Bit(pairedEncoded)); + + auto loneLeadEnd = Wide(u"a\xD83D"); + Check(NameUnits(EncodeName(&loneLeadEnd)) == u"a%+D83D"); + auto loneLeadMiddle = Wide(u"\xD83D" u"a"); + Check(NameUnits(EncodeName(&loneLeadMiddle)) == u"%+D83Da"); + auto loneTrailStart = Wide(u"\xDE00" u"a"); + Check(NameUnits(EncodeName(&loneTrailStart)) == u"%+DE00a"); + auto loneTrailMiddle = Wide(u"a\xDE00"); + Check(NameUnits(EncodeName(&loneTrailMiddle)) == u"a%+DE00"); + auto doubleLead = Wide(u"\xD83D\xD83D\xDE00"); + Check(NameUnits(EncodeName(&doubleLead)) == u"%+D83D\xD83D\xDE00"); + auto doubleTrail = Wide(u"\xD83D\xDE00\xDE00"); + Check(NameUnits(EncodeName(&doubleTrail)) == u"\xD83D\xDE00%+DE00"); + auto reversed = Wide(u"\xDE00\xD83D"); + Check(NameUnits(EncodeName(&reversed)) == u"%+DE00%+D83D"); + + auto latinWide = Wide(u"a/é"); + Check(!NameIs8Bit(latinWide) && !NameIs8Bit(wide) && !NameIs8Bit(paired)); + auto latinWideEncoded = EncodeName(&latinWide); + Check(NameUnits(latinWideEncoded) == u"a%2Fé" && NameIs8Bit(latinWideEncoded)); + auto highByte = Wide(u"\x00ff\x0100"); + Check(NameUnits(EncodeName(&highByte)) == u"\x00ff\x0100"); + + String null; + Check(EncodeName(&null).impl == nullptr); + auto empty = NameString(""); + auto emptyEncoded = EncodeName(&empty); + Check(emptyEncoded.impl == empty.impl && empty.impl->length == 0); + Throws([&] { EncodeName(nullptr); }); + + StringImpl privateEmpty{4, 0, nullptr, 0}; + String privateHolder(&privateEmpty); + Check(EncodeName(&privateHolder).impl == &privateEmpty); + Check(DecodeName(&privateHolder).impl == &privateEmpty); + Check(LastComponent(&privateHolder).impl == &privateEmpty); + Check(privateEmpty.refCount == 4); + + auto plain = NameString("name.txt"); + auto plainEncoded = EncodeName(&plain); + Text(plainEncoded, "name.txt"); + Check(plainEncoded.impl != plain.impl); + + auto roundTrip = Wide(u"a b/\x01\x7Fé中\xD83D\xDE00\xD83D%\xDE00"); + auto roundTripEncoded = EncodeName(&roundTrip); + Check(NameUnits(DecodeName(&roundTripEncoded)) == NameUnits(roundTrip)); + + Text(DecodeName(&(const String&)NameString("a%20b%2fc%2Fd")), "a b/c/d"); + Text(DecodeName(&(const String&)NameString("%41")), "A"); + Text(DecodeName(&(const String&)NameString("%+0041")), "A"); + Text(DecodeName(&(const String&)NameString("plain+text")), "plain+text"); + Text(DecodeName(&(const String&)NameString("%41%42")), "AB"); + auto e9 = DecodeName(&(const String&)NameString("%+00e9")); + Check(NameUnits(e9) == u"é" && NameIs8Bit(e9)); + auto latin1 = DecodeName(&(const String&)NameString("%E9")); + Check(NameUnits(latin1) == u"é" && NameIs8Bit(latin1)); + auto cjk = DecodeName(&(const String&)NameString("x%+4E2Dy")); + Check(NameUnits(cjk) == u"x中y" && !NameIs8Bit(cjk)); + auto lone = DecodeName(&(const String&)NameString("%+d83D")); + Check(NameUnits(lone) == u"\xD83D"); + auto nul = DecodeName(&(const String&)NameString("a%00b")); + Check(NameUnits(nul) == std::u16string(u"a\0b", 3)); + auto widePlain = Wide(u"中%41"); + Check(NameUnits(DecodeName(&widePlain)) == u"中A"); + + for (const char* invalid : {"%", "%4", "a%4", "%G1", "%1G", "%+", "%+4", "%+41", "%+004", "%+00G1", "%+G041", "%+0G41", "%+00", "x%", "x%4"}) { + auto result = DecodeName(&(const String&)NameString(invalid)); + Check(result.impl == nullptr); + } + auto truncatedInSharedParent = [&](const char* parentText, unsigned length) { + auto parent = NameString(parentText); + auto part = SharedSubstring(&parent, 0, length); + Check((part.impl->hashAndFlags & 3) == 2 && part.impl->length == length); + Check(DecodeName(&part).impl == nullptr); + }; + truncatedInSharedParent("aaaaaaaaaaaaaaaa%41", 18); + truncatedInSharedParent("aaaaaaaaaaaaaaaa%+0041", 17); + truncatedInSharedParent("aaaaaaaaaaaaaaaa%+0041", 19); + truncatedInSharedParent("aaaaaaaaaaaaaaaa%+4141", 20); + + auto invalidWide = Wide(u"中%4"); + Check(DecodeName(&invalidWide).impl == nullptr); + auto nonHexWide = Wide(u"%١٢"); + Check(DecodeName(&nonHexWide).impl == nullptr); + + Check(DecodeName(&null).impl == nullptr); + auto emptyDecoded = DecodeName(&empty); + Check(emptyDecoded.impl == empty.impl); + Throws([&] { DecodeName(nullptr); }); + auto unchanged = NameString("abc"); + Check(DecodeName(&unchanged).impl != unchanged.impl); + + auto sameImpl = [&](const char* path) { + auto input = NameString(path); + auto result = LastComponent(&input); + Check(result.impl == input.impl); + }; + sameImpl("abc"); + sameImpl(""); + Check(LastComponent(&null).impl == nullptr); + + Text(LastComponent(&(const String&)NameString("/abc")), "abc"); + Text(LastComponent(&(const String&)NameString("a/b/c")), "c"); + Text(LastComponent(&(const String&)NameString("a/b/")), "b"); + Text(LastComponent(&(const String&)NameString("abc/")), "abc"); + Text(LastComponent(&(const String&)NameString("a/")), "a"); + Text(LastComponent(&(const String&)NameString("x/abc/")), "abc"); + Text(LastComponent(&(const String&)NameString("/a/")), "a"); + Text(LastComponent(&(const String&)NameString("/")), ""); + Text(LastComponent(&(const String&)NameString("//")), ""); + Text(LastComponent(&(const String&)NameString("a//")), ""); + Text(LastComponent(&(const String&)NameString("///")), ""); + Text(LastComponent(&(const String&)NameString("a/b//")), ""); + Text(LastComponent(&(const String&)NameString("dir/file.txt")), "file.txt"); + sameImpl("a\\b\\c"); + sameImpl("a\\b\\c\\"); + + auto widePath = Wide(u"中/文/"); + auto wideLast = LastComponent(&widePath); + Check(NameUnits(wideLast) == u"文" && !NameIs8Bit(wideLast)); + auto wideNoSlash = Wide(u"中文"); + Check(LastComponent(&wideNoSlash).impl == wideNoSlash.impl); + auto wideTail = Wide(u"a/文"); + Check(NameUnits(LastComponent(&wideTail)) == u"文"); + Throws([&] { LastComponent(nullptr); }); +} + void TestURLsAndReallocation() { struct PinnedBits { unsigned valid : 1; diff --git a/docs/dev/debt/libSceNKWebKit.md b/docs/dev/debt/libSceNKWebKit.md index 537fa34590..1327243d33 100644 --- a/docs/dev/debt/libSceNKWebKit.md +++ b/docs/dev/debt/libSceNKWebKit.md @@ -33,7 +33,7 @@ Inventory prose retains historical counts; use the [generated progress report](h - The [revision-pinned identity inventory](https://gist.github.com/kyon-eth/d44995f181e1818977bd38f0e6fc0f34/8afcb14b0707a09b6274ff7f7bcf33ef46cfe2e7) records the module, library, symbol type and NIDs for the declarations in [Export.cpp](../../../core/libs/prx/libSceNKWebKit/Export.cpp); it is a metadata summary rather than independent verification. For the 255 absent from the public catalogue, [per-image ELF records](https://gist.github.com/kyon-eth/147be8dd556e9cced2577ccbed548972/1a659ecfe819e724675991a574a1aebb5fbb48e5) include the raw qualified names, module and library identities, symbol type, values and sizes; the record file SHA-256 is `44bdc4d6073192a8f41b7be359b75364b3d41dbbd11ce649924b8cebdd4ffe51`. Image labels are not authenticated. -- [Additional callable exports](../../../core/libs/prx/libSceNKWebKit/Export.cpp) - 7429 of the 8703 inventory declarations listed below throw through `NotImplemented_nid_no_patch`; signatures and behavior are unverified. The later 13.60-labelled image contains all 8703; 8448 match the public 3.20 catalogue, while 255 are absent and their presence in retail 3.20 is unverified. Per-image records show the latter in a separate 3.20-labelled image, but neither image label is authenticated. Identified names match NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). Data exports need separate analysis. +- [Additional callable exports](../../../core/libs/prx/libSceNKWebKit/Export.cpp) - 7426 of the 8703 inventory declarations listed below throw through NotImplemented_nid_no_patch; signatures and behavior are unverified. The later 13.60-labelled image contains all 8703; 8448 match the public 3.20 catalogue, while 255 are absent and their presence in retail 3.20 is unverified. Per-image records show the latter in a separate 3.20-labelled image, but neither image label is authenticated. Identified names match NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). Data exports need separate analysis. | Name | NID | | --- | --- | @@ -1666,9 +1666,7 @@ Inventory prose retains historical counts; use the [generated progress report](h | `_ZN3WTF14FileSystemImpl14MappedFileDataD1Ev` | `GhSIARptbG8` | | `_ZN3WTF14FileSystemImpl14MappedFileDataD2Ev` | `xONraGfpcUI` | | `_ZN3WTF14FileSystemImpl14setMetadataURLERKNS_6StringES3_S3_` | `Cubm05Z7E7I` | -| `_ZN3WTF14FileSystemImpl17encodeForFileNameERKNS_6StringE` | `DM39YN-X56M` | | `_ZN3WTF14FileSystemImpl18createSymbolicLinkERKNS_6StringES3_` | `dbycEreQEcg` | -| `_ZN3WTF14FileSystemImpl18decodeFromFilenameERKNS_6StringE` | `dlR6Z857IFs` | | `_ZN3WTF14FileSystemImpl18hardLinkOrCopyFileERKNS_6StringES3_` | `8R3APcMguxY` | | `_ZN3WTF14FileSystemImpl18makeAllDirectoriesERKNS_6StringE` | `yUU8KDyU5Xw` | | `_ZN3WTF14FileSystemImpl18unlockAndCloseFileEi` | `QpQN5xlV+7w` | @@ -1680,7 +1678,6 @@ Inventory prose retains historical counts; use the [generated progress report](h | `_ZN3WTF14FileSystemImpl29makeSafeToUseMemoryMapForPathERKNS_6StringE` | `GzaGT-nX+vk` | | `_ZN3WTF14FileSystemImpl30appendFileContentsToFileHandleERKNS_6StringERi` | `bFLwUoQqNHM` | | `_ZN3WTF14FileSystemImpl34stringFromFileSystemRepresentationEPKc` | `Q5IWykchiD8` | -| `_ZN3WTF14FileSystemImpl40lastComponentOfPathIgnoringTrailingSlashERKNS_6StringE` | `3TsXk8jA39E` | | `_ZN3WTF14FileSystemImpl8hardLinkERKNS_6StringES3_` | `PduV0fym+xE` | | `_ZN3WTF14FileSystemImpl8moveFileERKNS_6StringES3_` | `Pr98fHFIZlQ` | | `_ZN3WTF14FileSystemImpl8openFileERKNS_6StringENS0_12FileOpenModeENS0_20FileAccessPermissionEb` | `SQRcDBsMDZU` | @@ -7464,5 +7461,6 @@ Inventory prose retains historical counts; use the [generated progress report](h - [parseDateFromNullTerminatedCharacters(const char*)](../../../core/libs/prx/libSceNKWebKit/Export.cpp) (libSceNKWebKit) - still throws: it subtracts [calculateLocalTimeOffset](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/DateMath.cpp#L318-L361), whose code path follows `HAVE_LOCALTIME_R`, `HAVE_TIMEGM` and `HAVE_TM_GMTOFF` from [configure checks](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/cmake/OptionsCommon.cmake#L131-L148) against the port's sysroot, and the device time zone. - [parseDateFromNullTerminatedCharacters(const char*, bool&)](../../../core/libs/prx/libSceNKWebKit/DateParsing.cpp) (libSceNKWebKit) - is not measured on a device, and throws where pinned [DateMath.cpp](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/DateMath.cpp#L684-L988) overflows a signed integer, which is undefined there (the `YYYY/MM/DD` day count, the `GMT+hh:mm` minute total and comment nesting), and for a null argument, where WebKit faults. +- [WTF::FileSystemImpl::encodeForFileName, decodeFromFilename and lastComponentOfPathIgnoringTrailingSlash](../../../core/libs/prx/libSceNKWebKit/src/WtfString.cpp) - are source-guided by pinned [FileSystem.cpp](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/FileSystem.cpp#L66-L210), not console-measured; the device's WebKit revision is unverified. A null `String` reference, or an encoded result above 2^31-1 units, throws where WebKit has undefined behaviour or a release assertion. - [WTF decimal parsing](../../../core/libs/prx/libSceNKWebKit/CharactersToNumber.cpp) - the device's WebKit revision is unverified; `charactersToDouble`, `charactersToFloat` and `StringToDoubleConverter` follow pinned WebKit [toDoubleType](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/text/WTFString.cpp#L1040-L1098) and [StringToIeee](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/dtoa/double-conversion.cc#L459-L664). Under a rounding mode other than round-to-nearest, WebKit's [exact-power fast path](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/dtoa/strtod.cc#L219-L254) and the double-to-float cast in [Strtof](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/dtoa/strtod.cc#L485-L588) round in the current mode and the host conversion may round differently. - [WTF decimal parsing limits](../../../core/libs/prx/libSceNKWebKit/CharactersToNumber.cpp) - inputs of about 1 GiB or more whose decimal exponent leaves the range of WebKit's [`int` exponent](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/dtoa/double-conversion.cc#L488-L490) throw, where WebKit overflows. A UTF-16 `charactersTo*` length above `UINT32_MAX` after leading spaces throws where WebKit's [conversion Vector](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/Vector.h#L291-L293) crashes. From 9ba6807346077f2c5693e42ff7ce673250be274f Mon Sep 17 00:00:00 2001 From: Panagiotis Karaliolios Date: Mon, 2 Nov 2026 03:19:35 +0200 Subject: [PATCH 337/612] feat(libSceLibcInternal): implement std::_MP_Add, _MP_Get, _MP_Mul and _MP_Rem NIDs QfPuSqhub7o, lrQSsTRMMr4, Gav1Xt1Ce+c and Ozk+Z6QnlTY follow Dinkumware V6.00 multprec.cpp, the multiprecision helpers of linear_congruential_engine, instead of throwing: five 64-bit words of base 2^32 digits, the product by Knuth's Algorithm M and the remainder by Algorithm D. _MP_Rem throws for the inputs where V6.00 reads outside its arrays, and every export throws for a null array. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceLibcInternal/CMakeLists.txt | 14 ++ .../libSceLibcInternal/InventoryExports.cpp | 20 --- .../prx/libSceLibcInternal/MultiPrecision.cpp | 144 +++++++++++++++++ .../include/InventoryExports.hpp | 4 - .../include/MultiPrecision.hpp | 12 ++ .../libSceLibcInternal/libSceLibcInternal.hpp | 1 + .../tests/GuestLibcInternalMultiPrecision.cpp | 146 ++++++++++++++++++ docs/dev/debt/libSceLibcInternal.md | 7 +- 8 files changed, 319 insertions(+), 29 deletions(-) create mode 100644 core/libs/prx/libSceLibcInternal/MultiPrecision.cpp create mode 100644 core/libs/prx/libSceLibcInternal/include/MultiPrecision.hpp create mode 100644 core/libs/tests/GuestLibcInternalMultiPrecision.cpp diff --git a/core/libs/prx/libSceLibcInternal/CMakeLists.txt b/core/libs/prx/libSceLibcInternal/CMakeLists.txt index e222b977f1..71abdfb4d8 100644 --- a/core/libs/prx/libSceLibcInternal/CMakeLists.txt +++ b/core/libs/prx/libSceLibcInternal/CMakeLists.txt @@ -327,3 +327,17 @@ if(BUILD_TESTING AND (WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux")) -- "$") set_tests_properties(guest_sigvec_modules PROPERTIES TIMEOUT 20) endif() + +target_sources(libSceLibcInternal PRIVATE MultiPrecision.cpp) +if(BUILD_TESTING) + add_test_executable(guest_libc_internal_multi_precision_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcInternalMultiPrecision.cpp) + target_include_directories(guest_libc_internal_multi_precision_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libc_internal_multi_precision_tests PRIVATE libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_libc_internal_multi_precision_tests) + add_test(NAME guest_libc_internal_multi_precision COMMAND guest_libc_internal_multi_precision_tests) + set_tests_properties(guest_libc_internal_multi_precision PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_libc_internal_multi_precision PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibcInternal/InventoryExports.cpp b/core/libs/prx/libSceLibcInternal/InventoryExports.cpp index 3047b1c7a3..cb576ff3d5 100644 --- a/core/libs/prx/libSceLibcInternal/InventoryExports.cpp +++ b/core/libs/prx/libSceLibcInternal/InventoryExports.cpp @@ -1224,26 +1224,6 @@ int APS5_VABI libSceLibcInternalInventory_246_nid_no_patch() { } APS5_EXPORT("aqyjhIx7jaY", libSceLibcInternalInventory_246_nid_no_patch); -int APS5_VABI libSceLibcInternalInventory_247_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("QfPuSqhub7o", libSceLibcInternalInventory_247_nid_no_patch); - -int APS5_VABI libSceLibcInternalInventory_248_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("lrQSsTRMMr4", libSceLibcInternalInventory_248_nid_no_patch); - -int APS5_VABI libSceLibcInternalInventory_249_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("Gav1Xt1Ce+c", libSceLibcInternalInventory_249_nid_no_patch); - -int APS5_VABI libSceLibcInternalInventory_250_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("Ozk+Z6QnlTY", libSceLibcInternalInventory_250_nid_no_patch); - int APS5_VABI libSceLibcInternalInventory_251_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceLibcInternal/MultiPrecision.cpp b/core/libs/prx/libSceLibcInternal/MultiPrecision.cpp new file mode 100644 index 0000000000..2607914fa4 --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/MultiPrecision.cpp @@ -0,0 +1,144 @@ +#include "prx/libSceLibcInternal/include/MultiPrecision.hpp" +#include "prx/libc/include/general/ExportMacros.hpp" +#include +#include +#include + +namespace { + +constexpr int ValueWords = 5; +constexpr int DigitBits = 32; +constexpr unsigned long long DigitMask = 0xFFFFFFFFull; +constexpr unsigned long long DigitBase = DigitMask + 1; + +void RequireValue(const unsigned long long* value, const char* function) { + if (!value) throw std::invalid_argument(std::string(function) + ": null value"); +} + +void AddDigits(unsigned long long* value, int valueWords, const unsigned long long* addend, int addendWords) { + unsigned long long carry = 0; + int index = 0; + for (; index < addendWords; ++index) { + value[index] += addend[index] + carry; + carry = value[index] >> DigitBits; + value[index] &= DigitMask; + } + for (; carry != 0 && index < valueWords; ++index) { + value[index] += carry; + carry = value[index] >> DigitBits; + value[index] &= DigitMask; + } +} + +void ScaleDigits(unsigned long long* value, int words, unsigned long long factor) { + unsigned long long carry = 0; + for (int index = 0; index < words; ++index) { + value[index] = value[index] * factor + carry; + carry = value[index] >> DigitBits; + value[index] &= DigitMask; + } +} + +void UnscaleDigits(unsigned long long* value, unsigned long long factor) { + unsigned long long remainder = 0; + for (int index = ValueWords - 1; index >= 0; --index) { + const unsigned long long current = (remainder << DigitBits) + value[index]; + value[index] = current / factor; + remainder = current % factor; + } +} + +unsigned long long TakeBorrow(unsigned long long& word) { + const unsigned long long high = word >> DigitBits; + word &= DigitMask; + return high == 0 ? 0 : DigitBase - high; +} + +void Reduce(unsigned long long* value, unsigned long long divisor) { + if (divisor == 0) throw std::invalid_argument("_MP_Rem: zero divisor"); + if (value[ValueWords - 1] != 0) throw std::invalid_argument("_MP_Rem: nonzero fifth word"); + int words = ValueWords - 1; + while (words > 0 && value[words - 1] == 0) --words; + if (words == 0) throw std::invalid_argument("_MP_Rem: zero value"); + unsigned long long digits[2] = {divisor & DigitMask, divisor >> DigitBits}; + const int divisorWords = digits[1] == 0 ? 1 : 2; + unsigned long long work[ValueWords]; + std::copy_n(value, ValueWords, work); + const unsigned long long scale = DigitBase / (digits[divisorWords - 1] + 1); + if (scale != 1) { + ScaleDigits(work, ValueWords, scale); + ScaleDigits(digits, divisorWords, scale); + } + const unsigned long long leading = digits[divisorWords - 1]; + for (int position = words - divisorWords; position >= 0; --position) { + const unsigned long long top = (work[position + divisorWords] << DigitBits) + work[position + divisorWords - 1]; + unsigned long long quotient = top / leading; + if (quotient == 0) continue; + unsigned long long remainder = top % leading; + for (;;) { + if (quotient < DigitBase) { + if (divisorWords == 1) throw std::invalid_argument("_MP_Rem: nonzero quotient digit for divisor " + std::to_string(divisor)); + if (quotient * digits[0] <= (remainder << DigitBits) + work[position]) break; + } + --quotient; + remainder += leading; + if (remainder >= DigitBase) break; + } + unsigned long long borrow = 0; + int index = position; + for (; index < position + divisorWords; ++index) { + work[index] -= quotient * digits[index - position] + borrow; + borrow = TakeBorrow(work[index]); + } + for (; borrow != 0 && index < ValueWords; ++index) { + work[index] -= borrow; + borrow = TakeBorrow(work[index]); + } + if (borrow != 0) AddDigits(work + position, divisorWords + 1, digits, divisorWords); + } + if (scale != 1) UnscaleDigits(work, scale); + std::copy_n(work, ValueWords, value); +} + +} + +extern "C" { + +unsigned long long APS5_VABI sceLibcInternalMultiPrecisionGet_nid_no_patch(unsigned long long* value) { + RequireValue(value, "_MP_Get"); + return (value[1] << DigitBits) + value[0]; +} +APS5_EXPORT("lrQSsTRMMr4", sceLibcInternalMultiPrecisionGet_nid_no_patch); + +void APS5_VABI sceLibcInternalMultiPrecisionAdd_nid_no_patch(unsigned long long* value, unsigned long long addend) { + RequireValue(value, "_MP_Add"); + const unsigned long long digits[2] = {addend & DigitMask, addend >> DigitBits}; + AddDigits(value, ValueWords, digits, 2); +} +APS5_EXPORT("QfPuSqhub7o", sceLibcInternalMultiPrecisionAdd_nid_no_patch); + +void APS5_VABI sceLibcInternalMultiPrecisionMultiply_nid_no_patch(unsigned long long* product, unsigned long long left, unsigned long long right) { + RequireValue(product, "_MP_Mul"); + const unsigned long long leftDigits[2] = {left & DigitMask, left >> DigitBits}; + const unsigned long long rightDigits[2] = {right & DigitMask, right >> DigitBits}; + for (int index = 0; index < ValueWords; ++index) product[index] = 0; + for (int column = 0; column < 2; ++column) { + unsigned long long carry = 0; + for (int row = 0; row < 2; ++row) { + unsigned long long& word = product[row + column]; + word = leftDigits[row] * rightDigits[column] + word + carry; + carry = word >> DigitBits; + word &= DigitMask; + } + product[column + 2] = carry; + } +} +APS5_EXPORT("Gav1Xt1Ce+c", sceLibcInternalMultiPrecisionMultiply_nid_no_patch); + +void APS5_VABI sceLibcInternalMultiPrecisionRemainder_nid_no_patch(unsigned long long* value, unsigned long long divisor) { + RequireValue(value, "_MP_Rem"); + Reduce(value, divisor); +} +APS5_EXPORT("Ozk+Z6QnlTY", sceLibcInternalMultiPrecisionRemainder_nid_no_patch); + +} diff --git a/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp b/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp index 83ea0a2f25..c71d1adb38 100644 --- a/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp +++ b/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp @@ -248,10 +248,6 @@ int APS5_VABI libSceLibcInternalInventory_243_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_244_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_245_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_246_nid_no_patch(); -int APS5_VABI libSceLibcInternalInventory_247_nid_no_patch(); -int APS5_VABI libSceLibcInternalInventory_248_nid_no_patch(); -int APS5_VABI libSceLibcInternalInventory_249_nid_no_patch(); -int APS5_VABI libSceLibcInternalInventory_250_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_251_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_252_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_253_nid_no_patch(); diff --git a/core/libs/prx/libSceLibcInternal/include/MultiPrecision.hpp b/core/libs/prx/libSceLibcInternal/include/MultiPrecision.hpp new file mode 100644 index 0000000000..5a68bd7e2d --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/include/MultiPrecision.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +unsigned long long APS5_VABI sceLibcInternalMultiPrecisionGet_nid_no_patch(unsigned long long* value); +void APS5_VABI sceLibcInternalMultiPrecisionAdd_nid_no_patch(unsigned long long* value, unsigned long long addend); +void APS5_VABI sceLibcInternalMultiPrecisionMultiply_nid_no_patch(unsigned long long* product, unsigned long long left, unsigned long long right); +void APS5_VABI sceLibcInternalMultiPrecisionRemainder_nid_no_patch(unsigned long long* value, unsigned long long divisor); + +} diff --git a/core/libs/prx/libSceLibcInternal/libSceLibcInternal.hpp b/core/libs/prx/libSceLibcInternal/libSceLibcInternal.hpp index 8fb6498e98..9dd1e44ff4 100644 --- a/core/libs/prx/libSceLibcInternal/libSceLibcInternal.hpp +++ b/core/libs/prx/libSceLibcInternal/libSceLibcInternal.hpp @@ -4,4 +4,5 @@ #include "prx/libSceLibcInternal/include/RuntimeInternals.hpp" #include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libSceLibcInternal/include/MathInternals.hpp" +#include "prx/libSceLibcInternal/include/MultiPrecision.hpp" #include "prx/libSceLibcInternal/include/InventoryExports.hpp" diff --git a/core/libs/tests/GuestLibcInternalMultiPrecision.cpp b/core/libs/tests/GuestLibcInternalMultiPrecision.cpp new file mode 100644 index 0000000000..f9b2f65b66 --- /dev/null +++ b/core/libs/tests/GuestLibcInternalMultiPrecision.cpp @@ -0,0 +1,146 @@ +#include "prx/libSceLibcInternal/include/MultiPrecision.hpp" +#include +#include +#include +#include + +namespace { + +using Words = std::array; + +constexpr unsigned long long DigitMask = 0xFFFFFFFFull; + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "libc internal multiprecision: %s\n", message); + std::abort(); +} + +template +bool Rejects(const TCall& call) { + try { + call(); + } catch (const std::invalid_argument&) { + return true; + } + return false; +} + +Words Multiply(unsigned long long left, unsigned long long right) { + Words product; + product.fill(0xAAAAAAAAAAAAAAAAull); + sceLibcInternalMultiPrecisionMultiply_nid_no_patch(product.data(), left, right); + return product; +} + +Words Add(Words value, unsigned long long addend) { + sceLibcInternalMultiPrecisionAdd_nid_no_patch(value.data(), addend); + return value; +} + +Words Remainder(Words value, unsigned long long divisor) { + sceLibcInternalMultiPrecisionRemainder_nid_no_patch(value.data(), divisor); + return value; +} + +Words SchoolbookProduct(unsigned long long left, unsigned long long right) { + const unsigned long long low = (left & DigitMask) * (right & DigitMask); + const unsigned long long crossLeft = (left >> 32) * (right & DigitMask); + const unsigned long long crossRight = (left & DigitMask) * (right >> 32); + const unsigned long long middle = (low >> 32) + (crossLeft & DigitMask) + (crossRight & DigitMask); + const unsigned long long upper = (left >> 32) * (right >> 32) + (crossLeft >> 32) + (crossRight >> 32) + (middle >> 32); + return {low & DigitMask, middle & DigitMask, upper & DigitMask, upper >> 32, 0}; +} + +unsigned long long AddModulo(unsigned long long left, unsigned long long right, unsigned long long modulus) { + return left >= modulus - right ? left - (modulus - right) : left + right; +} + +unsigned long long MultiplyModulo(unsigned long long left, unsigned long long right, unsigned long long modulus) { + unsigned long long result = 0; + left %= modulus; + for (; right != 0; right >>= 1) { + if (right & 1) result = AddModulo(result, left, modulus); + left = AddModulo(left, left, modulus); + } + return result; +} + +void CheckMultiply() { + const unsigned long long values[] = {0, 1, DigitMask, DigitMask + 1, 1ull << 63, ~0ull, 0x0123456789ABCDEFull}; + for (const auto left : values) { + for (const auto right : values) { + Require(Multiply(left, right) == SchoolbookProduct(left, right), "_MP_Mul writes the 128-bit product as four digits and clears the fifth word"); + } + } +} + +void CheckGet() { + Words value{5, 7, 9, 11, 13}; + Require(sceLibcInternalMultiPrecisionGet_nid_no_patch(value.data()) == (7ull << 32) + 5, "_MP_Get joins the two low digits"); + value = {1, 0x100000002ull, 0, 0, 0}; + Require(sceLibcInternalMultiPrecisionGet_nid_no_patch(value.data()) == 0x200000001ull, "_MP_Get wraps a second word wider than a digit"); +} + +void CheckAdd() { + Require(Add({DigitMask, DigitMask, 0, 0, 0}, 1) == Words{0, 0, 1, 0, 0}, "_MP_Add carries into the third digit"); + Require(Add({DigitMask, DigitMask, DigitMask, DigitMask, 0}, 1) == Words{0, 0, 0, 0, 1}, "_MP_Add carries past 2^128 into the fifth word"); + Require(Add({DigitMask, DigitMask, DigitMask, DigitMask, DigitMask}, 1) == Words{}, "_MP_Add drops the carry out of the fifth word"); + Require(Add({0x1FFFFFFFFull, 0, 0, 0, 7}, 0) == Words{DigitMask, 1, 0, 0, 7}, "_MP_Add carries a first word wider than a digit"); + Require(Add({3, 0x100000000ull, 0, 0, 0}, 0xFFFFFFFF00000001ull) == Words{4, DigitMask, 1, 0, 0}, "_MP_Add adds both digits of the addend"); +} + +void CheckRemainder() { + for (const auto modulus : {(1ull << 32) + 15, (1ull << 61) - 1, ~0ull - 58}) { + const unsigned long long multiplier = 6364136223846793005ull; + const unsigned long long increment = 1442695040888963407ull % modulus; + unsigned long long state = 12345; + for (int step = 0; step < 10000; ++step) { + const unsigned long long expected = AddModulo(MultiplyModulo(multiplier, state, modulus), increment, modulus); + Words value{}; + sceLibcInternalMultiPrecisionMultiply_nid_no_patch(value.data(), multiplier, state); + sceLibcInternalMultiPrecisionAdd_nid_no_patch(value.data(), increment); + sceLibcInternalMultiPrecisionRemainder_nid_no_patch(value.data(), modulus); + Require(value[2] == 0 && value[3] == 0 && value[4] == 0, "_MP_Rem leaves the remainder in the two low digits"); + state = sceLibcInternalMultiPrecisionGet_nid_no_patch(value.data()); + Require(state == expected, "_MP_Rem gives the remainder of a linear congruential step"); + } + } + const Words wide{0x89ABCDEF, 0x01234567, 0xFEDCBA98, 0x76543210, 0}; + Require(Remainder(wide, ~0ull - 58) == Words{0x468AD530, 0x468ACF52, 0, 0, 0}, "_MP_Rem reduces a four-digit value"); + Require(Remainder(wide, (1ull << 61) - 1) == Words{0x8091A2CC, 0x13C4D5EF, 0, 0, 0}, "_MP_Rem normalizes a divisor with a small high digit"); + Require(Remainder(wide, 1ull << 32) == Words{0x89ABCDEF, 0, 0, 0, 0}, "_MP_Rem by 2^32 keeps the lowest digit"); + Require(Remainder({5, 0, 0, 0, 0}, 1ull << 32) == Words{5, 0, 0, 0, 0}, "_MP_Rem keeps a value below the divisor"); + Require(Remainder({0, 0, 1, 0, 0}, 1ull << 32) == Words{}, "_MP_Rem of a multiple is zero"); + Require(Remainder({0, 0, 0x7FFFFFFF, 0, 0}, 0x80000000FFFFFFFEull) == Words{0xFFFFFFF8, 5, 0, 0, 0}, "_MP_Rem corrects the trial quotient twice"); + Require(Remainder({7, 0, 0, 0, 0}, DigitMask) == Words{7, 0, 0, 0, 0}, "_MP_Rem keeps a digit below a one-digit divisor"); + Require(Remainder({5, 0, 0, 0, 0}, 7) == Words{5, 0, 0, 0, 0}, "_MP_Rem keeps a digit below a scaled one-digit divisor"); + Require(Remainder({0x123456789ull, 0xFFFFFFFFFull, 3, 0, 0}, 0x1FFFFFFFFull) == Words{0xA3456789, 1, 0, 0, 0}, "_MP_Rem scales words wider than a digit"); + Require(Remainder({~0ull, ~0ull, 0, 0, 0}, 0xFFFFFFFFFFFFFFC5ull) == Words{0xFFFFFF89, 0x39, 2, DigitMask, DigitMask}, "_MP_Rem propagates a borrow past the top digit"); +} + +void CheckRejections() { + Words zero{}; + Require(Rejects([&] { sceLibcInternalMultiPrecisionRemainder_nid_no_patch(zero.data(), 1ull << 32); }) && zero == Words{}, "_MP_Rem rejects a zero value"); + Words fifth{1, 0, 0, 0, 1}; + Require(Rejects([&] { sceLibcInternalMultiPrecisionRemainder_nid_no_patch(fifth.data(), 1ull << 32); }) && fifth == Words{1, 0, 0, 0, 1}, "_MP_Rem rejects a nonzero fifth word"); + Words small{8, 0, 0, 0, 0}; + Require(Rejects([&] { sceLibcInternalMultiPrecisionRemainder_nid_no_patch(small.data(), 0); }), "_MP_Rem rejects a zero divisor"); + Require(Rejects([&] { sceLibcInternalMultiPrecisionRemainder_nid_no_patch(small.data(), 7); }) && small == Words{8, 0, 0, 0, 0}, "_MP_Rem rejects a nonzero quotient digit for a one-digit divisor"); + Require(Rejects([] { sceLibcInternalMultiPrecisionGet_nid_no_patch(nullptr); }), "_MP_Get rejects a null value"); + Require(Rejects([] { sceLibcInternalMultiPrecisionAdd_nid_no_patch(nullptr, 1); }), "_MP_Add rejects a null value"); + Require(Rejects([] { sceLibcInternalMultiPrecisionMultiply_nid_no_patch(nullptr, 1, 1); }), "_MP_Mul rejects a null product"); + Require(Rejects([] { sceLibcInternalMultiPrecisionRemainder_nid_no_patch(nullptr, 1ull << 32); }), "_MP_Rem rejects a null value"); +} + +} + +int main() { + CheckMultiply(); + CheckGet(); + CheckAdd(); + CheckRemainder(); + CheckRejections(); + std::puts("libc internal multiprecision checks passed"); + return 0; +} diff --git a/docs/dev/debt/libSceLibcInternal.md b/docs/dev/debt/libSceLibcInternal.md index 6da84d4fe0..28383b9277 100644 --- a/docs/dev/debt/libSceLibcInternal.md +++ b/docs/dev/debt/libSceLibcInternal.md @@ -6,7 +6,7 @@ The named entries below use provider-specific NID aliases to avoid interposing h - The [revision-pinned identity inventory](https://gist.github.com/kyon-eth/6bd58c3d7a0fa933dd4b524754c43265/e4e5dbc43890a24e35dc1c656135000210e28390) records module, library, symbol type and NIDs for [InventoryExports.cpp](../../../core/libs/prx/libSceLibcInternal/InventoryExports.cpp); it is a metadata summary rather than independent verification. One entry belongs to the logical library `libSceLibcInternalExt` within this module. -- [Additional callable exports](../../../core/libs/prx/libSceLibcInternal/InventoryExports.cpp) - 295 entries observed in a 3.20-labelled image and a later 13.60-labelled image; 289 match the public 3.20 catalogue and 6 do not. These are declarations only; neither image label authenticates a retail firmware version. Signatures and behavior are unverified; parameterless `int` declarations are placeholders. Every entry throws through `NotImplemented_nid_no_patch`. All declarations use literal NID aliases; the table records candidate names where known. No runtime functionality is implemented; data exports need separate analysis. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). +- [Additional callable exports](../../../core/libs/prx/libSceLibcInternal/InventoryExports.cpp) - 288 entries observed in a 3.20-labelled image and a later 13.60-labelled image; 282 match the public 3.20 catalogue and 6 do not. These are declarations only; neither image label authenticates a retail firmware version. Signatures and behavior are unverified; parameterless `int` declarations are placeholders. Every entry throws through `NotImplemented_nid_no_patch`. All declarations use literal NID aliases; the table records candidate names where known. No runtime functionality is implemented; data exports need separate analysis. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). | Name | NID | | --- | --- | @@ -254,10 +254,6 @@ The named entries below use provider-specific NID aliases to avoid interposing h | `_ZSt22_Random_device_entropyv` | `mDIHE-aaRaI` | | `_ZSt6_ThrowRKSt9exception` | `pDFe-IgbTPg` | | `_ZSt7_FiopenPKwNSt5_IosbIiE9_OpenmodeEi` | `aqyjhIx7jaY` | -| `_ZSt7_MP_AddPyy` | `QfPuSqhub7o` | -| `_ZSt7_MP_GetPy` | `lrQSsTRMMr4` | -| `_ZSt7_MP_MulPyyy` | `Gav1Xt1Ce+c` | -| `_ZSt7_MP_RemPyy` | `Ozk+Z6QnlTY` | | `_ZSt7setbasei` | `4rrOHCHAV1w` | | `_ZSt9_LStrxfrmIcEmPT_S1_PKS0_S3_PKSt8_Collvec` | `V62E2Q8bJVY` | | `_ZSt9_LStrxfrmIwEmPT_S1_PKS0_S3_PKSt8_Collvec` | `BloPUt1HCH0` | @@ -346,6 +342,7 @@ The named entries below use provider-specific NID aliases to avoid interposing h - [inet_aton, inet_addr, inet_ntoa and inet_ntoa_r](../../../core/libs/prx/libSceLibcInternal/Inet.cpp) (libSceLibcInternal) follow the FreeBSD code in the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x8ADE0 to 0x8B3D0) and matched it on a PS5 (firmware 13.60) for 20 `inet_aton` inputs; a null text, which the console dereferences, throws. `inet_ntoa_r` formats through libkernel's `__inet_ntop`, so a null buffer with room for the text throws and a null buffer that is too small sets `ENOSPC`, as `__inet_ntop` does for a null destination; the console's `inet_ntoa_r` was not measured with a null buffer - [__inet_ntoa_r](../../../core/libs/prx/libSceLibcInternal/Inet.cpp) (libSceLibcInternal) was not measured on the console: it runs `inet_ntoa_r`, as FreeBSD 11.4 names the function `__inet_ntoa_r` ([arpa/inet.h](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/include/arpa/inet.h#L129)) and makes `inet_ntoa_r` a weak alias of it ([inet_ntoa.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_ntoa.c#L62-L76)) - [inet_netof, inet_lnaof, inet_makeaddr and inet_network](../../../core/libs/prx/libSceLibcInternal/Inet.cpp) (libSceLibcInternal, NIDs `UXEIr+tUsM4`, `4y7OKpbXUDs`, `5YaIoLorY8A`, `-Qdf3rmtqRA`, and aliases `3R15U5X00nY`, `bD39ou3sn7w`, `9cf7QE7cmto`, `xDCABmswhcM`) follow FreeBSD 11.4 [inet_netof.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_netof.c), [inet_lnaof.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_lnaof.c), [inet_makeaddr.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_makeaddr.c) and [inet_network.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_network.c) (`8c639945a4`). `inet_network` components are bounded to 0xff with a 32-bit accumulator. A null text throws std::invalid_argument. Console unverified. +- [std::_MP_Get, _MP_Add, _MP_Mul and _MP_Rem](../../../core/libs/prx/libSceLibcInternal/MultiPrecision.cpp) (libSceLibcInternal, NIDs `lrQSsTRMMr4`, `QfPuSqhub7o`, `Gav1Xt1Ce+c` and `Ozk+Z6QnlTY`) are unverified on the console: its `libSceLibcInternal.sprx` was not read and no title was seen calling them. Parameters come from the mangled names; return types and behaviour follow Dinkumware V6.00 [multprec.cpp](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/multprec.cpp), the algorithm of the [first MSVC STL release](https://github.com/microsoft/STL/blob/219514876ea86491de191ceaa88632616bbc0f19/stl/src/multprec.cpp): five 64-bit words of base 2^32 digits, with V6.00's results for words wider than a digit, a carry into the fifth word and a borrow past the top digit. A null array throws, and `_MP_Rem` throws where V6.00 reads outside its arrays: a zero divisor or value, a nonzero fifth word, or a nonzero quotient digit for a divisor below 2^32 ### Additional PS5 3.20 imports (xdCloudy #9511) From 761dc38931d89c470db0accccff3d70982a3f599 Mon Sep 17 00:00:00 2001 From: Panagiotis Karaliolios Date: Mon, 2 Nov 2026 06:52:22 +0200 Subject: [PATCH 338/612] docs(libSceLibcInternal): credit Microsoft STL multprec.cpp in the multiprecision helpers MultiPrecision.cpp is derived from stl/src/multprec.cpp at the first microsoft/STL release (219514876ea8): add its notice and make that revision the source of record in the debt entry, which also records that firmware 13.42 exports all four functions. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceLibcInternal/MultiPrecision.cpp | 3 +++ docs/dev/debt/libSceLibcInternal.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceLibcInternal/MultiPrecision.cpp b/core/libs/prx/libSceLibcInternal/MultiPrecision.cpp index 2607914fa4..9c01797599 100644 --- a/core/libs/prx/libSceLibcInternal/MultiPrecision.cpp +++ b/core/libs/prx/libSceLibcInternal/MultiPrecision.cpp @@ -1,3 +1,6 @@ +/* Derived from Microsoft STL stl/src/multprec.cpp (219514876ea86491de191ceaa88632616bbc0f19) */ +/* Copyright (c) Microsoft Corporation. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ #include "prx/libSceLibcInternal/include/MultiPrecision.hpp" #include "prx/libc/include/general/ExportMacros.hpp" #include diff --git a/docs/dev/debt/libSceLibcInternal.md b/docs/dev/debt/libSceLibcInternal.md index 28383b9277..e98ed6997a 100644 --- a/docs/dev/debt/libSceLibcInternal.md +++ b/docs/dev/debt/libSceLibcInternal.md @@ -342,7 +342,7 @@ The named entries below use provider-specific NID aliases to avoid interposing h - [inet_aton, inet_addr, inet_ntoa and inet_ntoa_r](../../../core/libs/prx/libSceLibcInternal/Inet.cpp) (libSceLibcInternal) follow the FreeBSD code in the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x8ADE0 to 0x8B3D0) and matched it on a PS5 (firmware 13.60) for 20 `inet_aton` inputs; a null text, which the console dereferences, throws. `inet_ntoa_r` formats through libkernel's `__inet_ntop`, so a null buffer with room for the text throws and a null buffer that is too small sets `ENOSPC`, as `__inet_ntop` does for a null destination; the console's `inet_ntoa_r` was not measured with a null buffer - [__inet_ntoa_r](../../../core/libs/prx/libSceLibcInternal/Inet.cpp) (libSceLibcInternal) was not measured on the console: it runs `inet_ntoa_r`, as FreeBSD 11.4 names the function `__inet_ntoa_r` ([arpa/inet.h](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/include/arpa/inet.h#L129)) and makes `inet_ntoa_r` a weak alias of it ([inet_ntoa.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_ntoa.c#L62-L76)) - [inet_netof, inet_lnaof, inet_makeaddr and inet_network](../../../core/libs/prx/libSceLibcInternal/Inet.cpp) (libSceLibcInternal, NIDs `UXEIr+tUsM4`, `4y7OKpbXUDs`, `5YaIoLorY8A`, `-Qdf3rmtqRA`, and aliases `3R15U5X00nY`, `bD39ou3sn7w`, `9cf7QE7cmto`, `xDCABmswhcM`) follow FreeBSD 11.4 [inet_netof.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_netof.c), [inet_lnaof.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_lnaof.c), [inet_makeaddr.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_makeaddr.c) and [inet_network.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_network.c) (`8c639945a4`). `inet_network` components are bounded to 0xff with a 32-bit accumulator. A null text throws std::invalid_argument. Console unverified. -- [std::_MP_Get, _MP_Add, _MP_Mul and _MP_Rem](../../../core/libs/prx/libSceLibcInternal/MultiPrecision.cpp) (libSceLibcInternal, NIDs `lrQSsTRMMr4`, `QfPuSqhub7o`, `Gav1Xt1Ce+c` and `Ozk+Z6QnlTY`) are unverified on the console: its `libSceLibcInternal.sprx` was not read and no title was seen calling them. Parameters come from the mangled names; return types and behaviour follow Dinkumware V6.00 [multprec.cpp](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/multprec.cpp), the algorithm of the [first MSVC STL release](https://github.com/microsoft/STL/blob/219514876ea86491de191ceaa88632616bbc0f19/stl/src/multprec.cpp): five 64-bit words of base 2^32 digits, with V6.00's results for words wider than a digit, a carry into the fifth word and a borrow past the top digit. A null array throws, and `_MP_Rem` throws where V6.00 reads outside its arrays: a zero divisor or value, a nonzero fifth word, or a nonzero quotient digit for a divisor below 2^32 +- [std::_MP_Get, _MP_Add, _MP_Mul and _MP_Rem](../../../core/libs/prx/libSceLibcInternal/MultiPrecision.cpp) (libSceLibcInternal, NIDs `lrQSsTRMMr4`, `QfPuSqhub7o`, `Gav1Xt1Ce+c` and `Ozk+Z6QnlTY`) are unverified on the console: firmware 13.42's `libSceLibcInternal.sprx` exports all four ([#14936](https://github.com/boykopovar/AnyPS5/pull/14936#pullrequestreview-5731406143)), but their code was not run and no title was seen calling them. Parameters come from the mangled names, the rest from the first MSVC STL release's [multprec.cpp](https://github.com/microsoft/STL/blob/219514876ea86491de191ceaa88632616bbc0f19/stl/src/multprec.cpp), Dinkumware's code: five 64-bit words of base 2^32 digits, with its results for words wider than a digit, a carry into the fifth word and a borrow past the top digit. A null array throws, and `_MP_Rem` throws where that code reads outside its arrays: a zero divisor or value, a nonzero fifth word, or a nonzero quotient digit for a divisor below 2^32 ### Additional PS5 3.20 imports (xdCloudy #9511) From 282f660d61f1d09e40e34e40e41124b9b298b833 Mon Sep 17 00:00:00 2001 From: hyvvs Date: Wed, 4 Nov 2026 22:22:34 +0100 Subject: [PATCH 339/612] fix(libcairo): preserve image type on rectangle subsurfaces (cherry picked from commit f6ca44f6ce33cdcd4f86057c866640f8d86327ae) --- core/libs/prx/libcairo/Surface.cpp | 6 ++---- core/libs/tests/GuestCairoSurface.cpp | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libcairo/Surface.cpp b/core/libs/prx/libcairo/Surface.cpp index 2e3dd3e2be..5700249c1f 100644 --- a/core/libs/prx/libcairo/Surface.cpp +++ b/core/libs/prx/libcairo/Surface.cpp @@ -19,7 +19,6 @@ constexpr int kColor = 0x1000; constexpr int kAlpha = 0x2000; constexpr int kColorAlpha = 0x3000; constexpr int kImageSurfaceType = 0; -constexpr int kSubsurfaceType = 23; constexpr int kInvalidFormatValue = -1; int BitsPerPixel(int format) { @@ -232,9 +231,8 @@ int APS5_VABI cairo_surface_status(CairoSurface* surface) { return surface ? State(surface)->status : kNullPointer; } -int APS5_VABI cairo_surface_get_type(CairoSurface* surface) { - return surface && State(surface)->status == kSuccess && !State(surface)->isImage - ? kSubsurfaceType : kImageSurfaceType; +int APS5_VABI cairo_surface_get_type(CairoSurface*) { + return kImageSurfaceType; } int APS5_VABI cairo_surface_get_content(CairoSurface* surface) { diff --git a/core/libs/tests/GuestCairoSurface.cpp b/core/libs/tests/GuestCairoSurface.cpp index ec9d1082c7..cae240d087 100644 --- a/core/libs/tests/GuestCairoSurface.cpp +++ b/core/libs/tests/GuestCairoSurface.cpp @@ -185,7 +185,7 @@ void CheckSimilarAndSubsurfaces() { } CairoSurface* subsection = cairo_surface_create_for_rectangle(parent, 1.5, 2.5, 3.5, 4.5); assert(subsection && cairo_surface_status(subsection) == 0); - assert(cairo_surface_get_type(subsection) == 23); + assert(cairo_surface_get_type(subsection) == 0); assert(cairo_surface_get_content(subsection) == 0x3000); assert(cairo_image_surface_get_format(subsection) == -1); assert(cairo_image_surface_get_data(subsection) == nullptr); From 69470e70759ac1ae814e90c5121a0b11727444c6 Mon Sep 17 00:00:00 2001 From: Retr0 Date: Thu, 5 Nov 2026 00:11:22 +0200 Subject: [PATCH 340/612] fix(libkernel): pass ioctl on a sceNetSocket socket to libSceNet --- core/libs/prx/libSceNet/Export.cpp | 11 +++++++++++ .../libkernel/Socket/include/SocketPoll.hpp | 3 +++ core/libs/prx/libkernel/Socket/src/Socket.cpp | 10 ++++++++++ core/libs/prx/libkernel/Socket/src/Udp.cpp | 14 +++++++++++++- core/libs/tests/GuestSceNet.cpp | 19 +++++++++++++++++++ docs/dev/debt/libkernel.md | 2 +- 6 files changed, 57 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 9e14d46d5e..0dba2fc62f 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -1036,6 +1036,17 @@ bool ControlSocketStatusForKernel(int descriptor, int command, int flags, int* r const bool g_socketStatusControlRegistered = (KernelSetSocketStatusControl_nid_no_patch(&ControlSocketStatusForKernel), true); +bool ControlSocketIoctlForKernel(int descriptor, std::uint32_t request, void* argument, int* error) { + { + std::lock_guard lk(g_mutex); + if (g_socks.find(descriptor) == g_socks.end()) return false; + } + *error = sceNetIoctl(descriptor, request, argument) == 0 ? 0 : *errno_slot(); + return true; +} + +const bool g_socketIoctlControlRegistered = (KernelSetSocketIoctlControl_nid_no_patch(&ControlSocketIoctlForKernel), true); + bool FindSocketNativeForKernel(int descriptor, std::shared_ptr* owner, std::uintptr_t* native) { std::lock_guard lk(g_mutex); const auto it = g_socks.find(descriptor); diff --git a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp index 6d816ac6f5..f5e50cf55c 100644 --- a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp +++ b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp @@ -22,6 +22,7 @@ using Closer = bool (*)(int descriptor, int* error); using OptionSetter = bool (*)(int descriptor, int level, int option, const void* value, std::uint32_t length, int* error); using OptionGetter = bool (*)(int descriptor, int level, int option, void* value, std::uint32_t* length, int* error); using StatusControl = bool (*)(int descriptor, int command, int flags, int* result, int* error); +using IoctlControl = bool (*)(int descriptor, std::uint32_t request, void* argument, int* error); struct Forwards { int (*family)(int descriptor); int (*bind)(int descriptor, const void* address, std::uint32_t length); @@ -54,6 +55,8 @@ extern "C" void KernelSetSocketOptionGetter_nid_no_patch(KernelSocketPoll::Optio extern "C" bool KernelGetSocketOption_nid_no_patch(int descriptor, int level, int option, void* value, std::uint32_t* length, int* error); extern "C" void KernelSetSocketStatusControl_nid_no_patch(KernelSocketPoll::StatusControl control); extern "C" bool KernelControlSocketStatus_nid_no_patch(int descriptor, int command, int flags, int* result, int* error); +extern "C" void KernelSetSocketIoctlControl_nid_no_patch(KernelSocketPoll::IoctlControl control); +extern "C" bool KernelControlSocketIoctl_nid_no_patch(int descriptor, std::uint32_t request, void* argument, int* error); extern "C" void KernelSetSocketNativeLookup_nid_no_patch(KernelSocketPoll::NativeLookup lookup); extern "C" bool KernelFindSocketNative_nid_no_patch(int descriptor, std::shared_ptr* owner, std::uintptr_t* native); extern "C" bool KernelLookupSocketNative_nid_no_patch(int descriptor, std::shared_ptr* owner, std::uintptr_t* native); diff --git a/core/libs/prx/libkernel/Socket/src/Socket.cpp b/core/libs/prx/libkernel/Socket/src/Socket.cpp index b8f8ab3a47..30c0f421a1 100644 --- a/core/libs/prx/libkernel/Socket/src/Socket.cpp +++ b/core/libs/prx/libkernel/Socket/src/Socket.cpp @@ -42,6 +42,7 @@ std::atomic g_socketCloser{nullptr}; std::atomic g_socketOptionSetter{nullptr}; std::atomic g_socketOptionGetter{nullptr}; std::atomic g_socketStatusControl{nullptr}; +std::atomic g_socketIoctlControl{nullptr}; std::atomic g_socketNativeLookup{nullptr}; bool DescriptorSet(const void* set, int descriptor) { @@ -190,6 +191,15 @@ bool KernelControlSocketStatus_nid_no_patch(int descriptor, int command, int fla return control != nullptr && control(descriptor, command, flags, result, error); } +void KernelSetSocketIoctlControl_nid_no_patch(KernelSocketPoll::IoctlControl control) { + g_socketIoctlControl.store(control); +} + +bool KernelControlSocketIoctl_nid_no_patch(int descriptor, std::uint32_t request, void* argument, int* error) { + const auto control = g_socketIoctlControl.load(); + return control != nullptr && control(descriptor, request, argument, error); +} + void KernelSetSocketNativeLookup_nid_no_patch(KernelSocketPoll::NativeLookup lookup) { g_socketNativeLookup.store(lookup); } diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index 0a883ef1f4..c0979a20de 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -1688,7 +1688,19 @@ int APS5_VABI getpeername_nid_postfix(int descriptor, void* address, std::uint32 return CopyAddressOut(native, false, address, length, capacity); } int APS5_VABI ioctl_nid_postfix(int descriptor, std::uint64_t request, void* argument) { - if (!GuestSockets::IsOpen(descriptor)) return File::Ioctl(descriptor, request, argument); + if (!GuestSockets::IsOpen(descriptor)) { + const auto command = static_cast(request); + std::shared_ptr owner; + std::uintptr_t native = 0; + if (command == 0x20006601 || command == 0x20006602 || !KernelFindSocketNative_nid_no_patch(descriptor, &owner, &native)) { + return File::Ioctl(descriptor, request, argument); + } + if (File::IoctlRequestMalformed(request)) return Fail(25); + if (!argument && (request & 0x1fff0000 || !(request & 0x20000000))) return Fail(14); + int error = 0; + if (!KernelControlSocketIoctl_nid_no_patch(descriptor, command, argument, &error)) return Fail(9); + return error == 0 ? 0 : Fail(error); + } if (File::IoctlRequestMalformed(request)) return Fail(25); const auto socket = Lookup(descriptor); if (!socket) return -1; diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 47a78c252d..f6cc4b5ee7 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -1183,6 +1183,25 @@ static void CheckIoctl() { Require(Failed(sceNetIoctl(socket, 0x4004667f, reinterpret_cast(std::uintptr_t{0x10})), 14)); Require(Failed(sceNetIoctl(socket, 123, &readable), 25)); Require(Failed(sceNetIoctl(socket, 123, nullptr), 25)); + for (const int value : {0, 3}) { + int request = value; + Require(ioctl_nid_postfix(socket, 0x8004667e, &request) == 0); + int nonblocking = -1; + std::uint32_t length = sizeof(nonblocking); + Require(sceNetGetsockopt(socket, 0xffff, 0x1200, &nonblocking, &length) == 0 && nonblocking == (value != 0 ? 1 : 0)); + Require(fcntl_nid_postfix(socket, 3) == (value != 0 ? 2 | 4 : 2)); + } + Require(sceNetSendto(socket, payload, sizeof(payload), 0, address.data(), address.size()) == sizeof(payload)); + entry.revents = 0; + Require(poll_nid_postfix(&entry, 1, 1000) == 1 && entry.revents == 1); + readable = -1; + Require(ioctl_nid_postfix(socket, 0x4004667f, &readable) == 0 && readable == sizeof(payload)); + Require(sceNetRecv(socket, buffer, sizeof(buffer), 0) == sizeof(payload)); + Require(ioctl_nid_postfix(socket, 0x4004667f, &readable) == 0 && readable == 0); + *__error_nid_postfix() = 0; + Require(ioctl_nid_postfix(socket, 0x4004667f, nullptr) == -1 && *__error_nid_postfix() == 14); + *__error_nid_postfix() = 0; + Require(ioctl_nid_postfix(socket, 0x8004667e, nullptr) == -1 && *__error_nid_postfix() == 14); Require(sceNetSocketClose(socket) == 0); Require(Failed(sceNetIoctl(socket, 0x8004667e, &enabled), 9)); Require(Failed(sceNetIoctl(-1, 0x4004667f, &readable), 9)); diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 9ce8b92b72..115692dad0 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -337,7 +337,7 @@ - [fcntl on a guest socket](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) answers `F_DUPFD`, `F_DUP2FD`, `F_DUP2FD_CLOEXEC`, `F_GETFD`, `F_SETFD`, `F_GETFL` and `F_SETFL`, fails `F_DUPFD_CLOEXEC` with `EOPNOTSUPP` as a PS5 does, and fails every other command with `EINVAL`; FreeBSD 11.4's `kern_fcntl` also handles `F_GETOWN` and `F_SETOWN` on a socket. `FD_CLOEXEC` is only recorded (there is no `exec`): `dup`, `dup2`, `F_DUPFD`, `F_DUP2FD` onto another descriptor and an accepted socket start without it, `F_DUP2FD` onto the same descriptor keeps it, and `SOCK_CLOEXEC` and `F_SETFD` set it. None of this was measured on a console. - [F_DUPFD_CLOEXEC and F_DUP2FD_CLOEXEC](../../../core/libs/prx/libkernel/File/src/DescriptorControl.cpp) (libkernel) - `F_DUPFD_CLOEXEC` on a shared memory descriptor fails with `EOPNOTSUPP`, where PS5 firmware 13.60 returns 0 for any start without setting `errno`; whether it duplicates anything is not known, since descriptor 0 was already open. `F_DUP2FD_CLOEXEC` returns the lowest free descriptor, where the console picks scattered free numbers below 1024 (17 to 1012 measured). Otherwise both follow the console: `F_DUPFD_CLOEXEC` fails with `EOPNOTSUPP` for any start on files, sockets, pipes and kqueues, after `EBADF` for a bad descriptor; `F_DUP2FD_CLOEXEC` ignores its target, which it neither opens nor closes (-1, -2, `INT_MAX`, the source and an open descriptor alike), and returns a new descriptor for the same open file without `FD_CLOEXEC`, or `EBADF` for a bad source - [ioctl, _ioctl](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) handle only `FIONBIO`, `FIONREAD`, `FIOCLEX` and `FIONCLEX` on a socket, and any other request there fails with `ENOTTY`. On the other descriptors they follow FreeBSD's [`kern_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_generic.c#L758), [`vn_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_vnops.c#L1488) and [`pipe_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_pipe.c#L1346): `FIONBIO` sets `O_NONBLOCK` as `fcntl` does, `FIOCLEX` and `FIONCLEX` set and clear the close-on-exec flag `F_GETFD` reports, and `FIONREAD` reports the size past the offset of a file, the queued bytes of a pipe's read end, 0 on its write end and `ENOTTY` on the random device, while it throws on a directory or another character device. Every termios request (group `t`, `TIOCGWINSZ` included) fails with `ENOTTY`, since those descriptors are taken as not a terminal, as in `tcgetattr`, and any other well-formed request throws. An input request with length 0 is not rejected as malformed: [`sys_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_generic.c#L703-L718) rejects it only in a kernel built without `COMPAT_FREEBSD4`, `COMPAT_FREEBSD5` and `COMPAT_43`, and which of these the PS5 kernel has is unknown. Stremio Plus (PPSA74126) imports `_ioctl` for its statically linked `tc*` functions, which send `TIOCGETA`, `TIOCSETA`, `TIOCSPGRP` and the other termios requests -- [ioctl, _ioctl](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) do not pass a `sceNetSocket` socket to libSceNet as `fcntl` does. On Linux its number duplicates the host socket, so `FIONBIO` switches the host `O_NONBLOCK` but not the library's `SO_NBIO` state; on Windows it is a `NUL` placeholder, so `FIONREAD` and enabling `FIONBIO` throw as on a character device. `sceNetIoctl` handles both requests on such a socket. +- [ioctl, _ioctl](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) pass `FIONBIO`, `FIONREAD` and any other request on a `sceNetSocket` socket to `sceNetIoctl`, except `FIOCLEX` and `FIONCLEX`, which still go to the descriptor beneath it (a duplicate of the host socket on Linux, a `NUL` placeholder on Windows), while `fcntl`'s `F_GETFD` and `F_SETFD` on such a socket fail with `EINVAL`. FreeBSD 11.4's [`kern_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_generic.c#L814-L820) and `kern_fcntl` keep the close-on-exec flag on every descriptor. - [_accept, _bind, _connect, _getpeername, _getsockname, _getsockopt, _listen, _recvfrom, _recvmsg, _sendmsg, _sendto, _setsockopt](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp), [_getdirentries](../../../core/libs/prx/libkernel/File/src/Stdio.cpp), [_sigaction, _sigsuspend](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) are not measured on the console. They forward to the calls without the underscore, which have no libthr layer here, as FreeBSD 11.4 makes the underscore names the plain system calls. `_sigaction` and `sigaction` share one handler table, where FreeBSD's `__thr_sigaction` hands its own wrapper to the system call `_sigaction` reads back, and rejects `SIGCANCEL` (32). A null set throws in `_sigsuspend` as in `sigsuspend`, while the system call FreeBSD 11.4 exports as `_sigsuspend` ([`Symbol.map`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/sys/Symbol.map#L961)) returns `EFAULT` - [Regular file mappings](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) - on Linux `mmap` maps a regular file's descriptor shared or private like a shared memory object, with the same access checks; SQLite's WAL index (`-shm`) in Dusklight PPSA99305 needs it. FreeBSD 11 `mmap(2)` and shadPS4's `MemoryManager::MapFile` map regular files; the console's behaviour is not measured. Other descriptors (pipes, sockets, directories, devices) still fail with `EOPNOTSUPP`. On Windows a shared mapping is a view of a file section (`CreateFileMappingW` on the descriptor's handle, read-write when the descriptor is, otherwise read-only), so writes through the descriptor and the mapping see each other. This also maps anonymous `shm_open` objects and JIT shared memory descriptors, which are temporary files on Windows. A shared mapping may end inside the 16 KiB page that holds the end of the file, as POSIX [`mmap`](https://pubs.opengroup.org/onlinepubs/9799919799/functions/mmap.html) allows (llama.cpp in PS5LM Plus PPSA99581 maps a whole model file this way): the section keeps the file's size, so the file never grows, the view ends with the 4 KiB host page that holds the end of the file, which Windows fills with zeros past it, and the rest of the guest page is private zero pages. As POSIX requires, the bytes past the end read zero and are never written to the file; bytes written to those private pages are seen only through that mapping, not through other mappings of the file, and a file that grows later does not show its new bytes there. A shared mapping that reaches a whole page past the end of the file fails with `EOPNOTSUPP`, where POSIX maps it and delivers `SIGBUS` on access, as does a write-only descriptor; FreeBSD 11.4's `vn_mmap` maps a write-only descriptor without `PROT_READ`, and Linux refuses it with `EACCES`. A `PROT_EXEC` mapping is a view of a section created on a second handle that `ReOpenFile` opens with execute access, so a file whose ACL does not grant the user execute fails with `EACCES`, where FreeBSD maps it unless the file system is mounted `noexec`. A read-write alias (`GuestArenaMapAlias`) of a mapped page that holds the end of the file throws. `mprotect` above the view's protection (writing through a read-only descriptor, or adding `PROT_EXEC`) fails with `EACCES` before anything changes; FreeBSD allows `PROT_EXEC` on a readable descriptor. Shrinking the file with `ftruncate` fails with `EINVAL` while any shared mapping of it exists, even one that does not cover the removed part, because the section keeps the file's size at `mmap`; FreeBSD truncates. A read-write alias of a view (`GuestArenaMapAlias`, used when the GPU imports guest memory) of a read-only file section fails, as the alias is mapped `PAGE_READWRITE`. Views of the same file made by separate `mmap` calls are separate sections, so write tracking does not treat them as aliases of each other. A private mapping on Windows is a copy of the file read at `mmap`, which POSIX allows (later changes to the file need not show); pages wholly past the end of the file fail with `EOPNOTSUPP` instead of faulting on access, and a write-only descriptor fails with `EOPNOTSUPP`. - [File mappings on macOS](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) - `mmap` of a regular file, of an anonymous `shm_open` object (an unlinked temporary file on macOS) or of a JIT shared memory descriptor maps the descriptor shared or private as on Linux, with the same access checks. macOS refuses to create an executable file mapping (`EPERM`), so an executable mapping is made without execute and then given it with `mprotect`. Under Rosetta 2 (Apple M2, macOS 26), code rewritten through one view of a file after it ran from another view keeps its old translation until the executable view's protection changes. Named `shm_open` objects still throw on macOS. From 9f7896f2ba5e78dcf803460c88ffc3a70a3e628c Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:40:47 +0200 Subject: [PATCH 341/612] feat(libSceLibreSsl): implement X509_NAME_get_entry --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 ++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 44 +++++++++++ core/libs/tests/GuestLibreSslX509Name.cpp | 79 +++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 3 +- 5 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/X509Name.cpp create mode 100644 core/libs/tests/GuestLibreSslX509Name.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 9f9df4076c..5ee86a9d98 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Bignum.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipherCtx.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Evp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipher.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Bignum.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipherCtx.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Evp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509Name.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -203,6 +203,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_evp_cipher PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_x509_name_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslX509Name.cpp) + target_include_directories(guest_libressl_x509_name_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_x509_name_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_x509_name_tests) + add_test(NAME guest_libressl_x509_name COMMAND guest_libressl_x509_name_tests) + set_tests_properties(guest_libressl_x509_name PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_x509_name PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 50a98d55c0..80d66b498a 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1079,11 +1079,6 @@ int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void) { return 0; } -int APS5_VABI X509_NAME_get_entry_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp new file mode 100644 index 0000000000..85fb8215a6 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct LibreSslX509NameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct LibreSslX509Name { + LibreSslStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + +static_assert(sizeof(LibreSslStack) == 32); +static_assert(offsetof(LibreSslStack, data) == 8); +static_assert(sizeof(LibreSslX509NameEntry) == 24); +static_assert(offsetof(LibreSslX509NameEntry, value) == 8); +static_assert(sizeof(LibreSslX509Name) == 40); +static_assert(offsetof(LibreSslX509Name, entries) == 0); + +extern "C" { + +LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc) { + if (!name || loc < 0 || !name->entries || name->entries->num <= loc) return nullptr; + if (!name->entries->data) throw std::invalid_argument(std::string(__func__) + ": null entry array"); + return reinterpret_cast(name->entries->data[loc]); +} + +} diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp new file mode 100644 index 0000000000..ac574ce133 --- /dev/null +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -0,0 +1,79 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct TestStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct TestNameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct TestName { + TestStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + +static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); +static_assert(sizeof(TestNameEntry) == 24 && offsetof(TestNameEntry, value) == 8); +static_assert(sizeof(TestName) == 40); + +extern "C" { +TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL X509_NAME check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetEntry() { + TestNameEntry first {}; + TestNameEntry second {}; + char* slots[] {reinterpret_cast(&first), reinterpret_cast(&second), nullptr}; + TestStack entries {2, slots, 0, 3, nullptr}; + TestName name {}; + Require(X509_NAME_get_entry_nid_postfix(nullptr, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + name.entries = &entries; + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == &first); + Require(X509_NAME_get_entry_nid_postfix(&name, 1) == &second); + Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, INT_MIN) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(&name, INT_MAX) == nullptr); + entries.num = 0; + Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + entries.num = 3; + Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + entries.data = nullptr; + RequireThrows([&] { X509_NAME_get_entry_nid_postfix(&name, 0); }); + Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); +} + +int main() { + TestGetEntry(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index a0df7f31b8..4ab76d0885 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 220 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 219 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -31,6 +31,7 @@ - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [EVP_CIPHER_CTX_init, EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_set_padding](../../../core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The 168-byte `EVP_CIPHER_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_enc.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214), which also measured `EVP_CIPHER_CTX_init` clearing 168 bytes. `EVP_CIPHER_CTX_new` allocates through the guest `calloc` as the module would through libc. A null context, which LibreSSL dereferences, throws - [EVP_CIPHER_block_size](../../../core/libs/prx/libSceLibreSsl/src/EvpCipher.cpp) (libSceLibreSsl) - nothing is checked on the console. The 88-byte `EVP_CIPHER` layout and the result follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): `block_size` is returned as stored. A null cipher, which LibreSSL dereferences, throws +- [X509_NAME_get_entry](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names the caller builds reach it. A positive entry count with a null entry array, which LibreSSL dereferences, throws ### Functional From 1b7914cf30bcf01e2cf79c5669a38969f0f08983 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:42:33 +0200 Subject: [PATCH 342/612] feat(libSceLibreSsl): implement X509_NAME_ENTRY_get_data --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 5 +++++ core/libs/tests/GuestLibreSslX509Name.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 80d66b498a..a1dc3439ec 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1074,11 +1074,6 @@ int APS5_VABI X509_INFO_free_nid_postfix(void) { return 0; } -int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp index 85fb8215a6..57efc6993b 100644 --- a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -41,4 +41,9 @@ LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX return reinterpret_cast(name->entries->data[loc]); } +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry* entry) { + if (!entry) return nullptr; + return entry->value; +} + } diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp index ac574ce133..1dea64955e 100644 --- a/core/libs/tests/GuestLibreSslX509Name.cpp +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -35,6 +35,7 @@ static_assert(sizeof(TestName) == 40); extern "C" { TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const TestNameEntry*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -73,7 +74,20 @@ static void TestGetEntry() { Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); } +static void TestEntryGetData() { + int value = 0; + TestNameEntry entry {}; + Require(X509_NAME_ENTRY_get_data_nid_postfix(nullptr) == nullptr); + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == nullptr); + entry.value = &value; + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + entry.object = &entry; + entry.set = 7; + Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); +} + int main() { TestGetEntry(); + TestEntryGetData(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 4ab76d0885..a6fde5966b 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 219 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 218 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -31,7 +31,7 @@ - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [EVP_CIPHER_CTX_init, EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_set_padding](../../../core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The 168-byte `EVP_CIPHER_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_enc.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214), which also measured `EVP_CIPHER_CTX_init` clearing 168 bytes. `EVP_CIPHER_CTX_new` allocates through the guest `calloc` as the module would through libc. A null context, which LibreSSL dereferences, throws - [EVP_CIPHER_block_size](../../../core/libs/prx/libSceLibreSsl/src/EvpCipher.cpp) (libSceLibreSsl) - nothing is checked on the console. The 88-byte `EVP_CIPHER` layout and the result follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): `block_size` is returned as stored. A null cipher, which LibreSSL dereferences, throws -- [X509_NAME_get_entry](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names the caller builds reach it. A positive entry count with a null entry array, which LibreSSL dereferences, throws +- [X509_NAME_get_entry, X509_NAME_ENTRY_get_data](../../../core/libs/prx/libSceLibreSsl/src/X509Name.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `x509.h`, `x509name.c` and `stack.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a name yet, so only names and entries the caller builds reach them. A positive entry count with a null entry array, which LibreSSL dereferences, throws ### Functional From 4da8e01907747977c581175b61491d6dd84182fe Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 06:00:22 +0100 Subject: [PATCH 343/612] refactor(libSceLibreSsl): declare the X509_NAME exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 19 ++++++++- core/libs/prx/libSceLibreSsl/src/X509Name.cpp | 26 +----------- core/libs/tests/GuestLibreSslX509Name.cpp | 40 ++++++++++--------- 3 files changed, 40 insertions(+), 45 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 58d3f2dc88..ff346aefdf 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -342,6 +342,21 @@ struct LibreSslEvpCipher { void* applicationData; }; +struct LibreSslX509NameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct LibreSslX509Name { + LibreSslStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -655,8 +670,8 @@ void* APS5_VABI X509_EXTENSION_get_data_nid_postfix(const void* extension); void* APS5_VABI X509_EXTENSION_get_object_nid_postfix(const void* extension); int APS5_VABI X509_INFO_free_nid_postfix(void); LibreSslX509LookupMethod* APS5_VABI X509_LOOKUP_file_nid_postfix(void); -int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void); -int APS5_VABI X509_NAME_get_entry_nid_postfix(void); +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry* entry); +LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc); int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void); int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(void); int APS5_VABI X509_NAME_oneline_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp index 57efc6993b..c220b821a0 100644 --- a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -1,33 +1,9 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslStack { - int num; - char** data; - int sorted; - int numAlloc; - void* comp; -}; - -struct LibreSslX509NameEntry { - void* object; - void* value; - int set; - int size; -}; - -struct LibreSslX509Name { - LibreSslStack* entries; - int modified; - void* bytes; - unsigned char* canonEnc; - int canonEnclen; -}; - -static_assert(sizeof(LibreSslStack) == 32); -static_assert(offsetof(LibreSslStack, data) == 8); static_assert(sizeof(LibreSslX509NameEntry) == 24); static_assert(offsetof(LibreSslX509NameEntry, value) == 8); static_assert(sizeof(LibreSslX509Name) == 40); diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp index 1dea64955e..365b16002e 100644 --- a/core/libs/tests/GuestLibreSslX509Name.cpp +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -33,9 +34,12 @@ static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); static_assert(sizeof(TestNameEntry) == 24 && offsetof(TestNameEntry, value) == 8); static_assert(sizeof(TestName) == 40); -extern "C" { -TestNameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const TestName*, int); -void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const TestNameEntry*); +static const LibreSslX509Name* Name(const TestName* name) { + return reinterpret_cast(name); +} + +static const LibreSslX509NameEntry* Entry(const TestNameEntry* entry) { + return reinterpret_cast(entry); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -56,34 +60,34 @@ static void TestGetEntry() { TestStack entries {2, slots, 0, 3, nullptr}; TestName name {}; Require(X509_NAME_get_entry_nid_postfix(nullptr, 0) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), -1) == nullptr); name.entries = &entries; - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == &first); - Require(X509_NAME_get_entry_nid_postfix(&name, 1) == &second); - Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, -1) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, INT_MIN) == nullptr); - Require(X509_NAME_get_entry_nid_postfix(&name, INT_MAX) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == Entry(&first)); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 1) == Entry(&second)); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), INT_MIN) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), INT_MAX) == nullptr); entries.num = 0; - Require(X509_NAME_get_entry_nid_postfix(&name, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == nullptr); entries.num = 3; - Require(X509_NAME_get_entry_nid_postfix(&name, 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 2) == nullptr); entries.data = nullptr; - RequireThrows([&] { X509_NAME_get_entry_nid_postfix(&name, 0); }); - Require(X509_NAME_get_entry_nid_postfix(&name, 3) == nullptr); + RequireThrows([&] { X509_NAME_get_entry_nid_postfix(Name(&name), 0); }); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 3) == nullptr); } static void TestEntryGetData() { int value = 0; TestNameEntry entry {}; Require(X509_NAME_ENTRY_get_data_nid_postfix(nullptr) == nullptr); - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == nullptr); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == nullptr); entry.value = &value; - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == &value); entry.object = &entry; entry.set = 7; - Require(X509_NAME_ENTRY_get_data_nid_postfix(&entry) == &value); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == &value); } int main() { From a8ea8d0ffce53885e987c0229be631a035a4a557 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:53:15 +0200 Subject: [PATCH 344/612] feat(libSceLibreSsl): implement SSL_get_SSL_CTX --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 +++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 78 +++++++++++++++++++++ core/libs/tests/GuestLibreSslSsl.cpp | 65 +++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 3 +- 5 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/Ssl.cpp create mode 100644 core/libs/tests/GuestLibreSslSsl.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 9f9df4076c..7d2a7f7b41 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Bignum.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipherCtx.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Evp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipher.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Bignum.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipherCtx.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Evp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ssl.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -203,6 +203,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_evp_cipher PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_ssl_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSsl.cpp) + target_include_directories(guest_libressl_ssl_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_tests) + add_test(NAME guest_libressl_ssl COMMAND guest_libressl_ssl_tests) + set_tests_properties(guest_libressl_ssl PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 50a98d55c0..4d6596aa8e 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -838,11 +838,6 @@ int APS5_VABI SSL_get1_session_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_SSL_CTX_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_get_certificate_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp new file mode 100644 index 0000000000..2bec555f3b --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct LibreSslSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + int server; + void* s3; + void* d1; + void* param; + void* cipherList; + void* cert; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* session; + int verifyMode; + int error; + int errorCode; + void* ctx; + std::int64_t verifyResult; + int references; + int clientVersion; + unsigned int maxSendFragment; + char* tlsextHostname; + int tlsextStatusType; + void* initialCtx; + void* encReadCtx; + void* readHash; + LibreSslSslInternal* internal; +}; + +static_assert(sizeof(LibreSslSslInternal) == 432); +static_assert(offsetof(LibreSslSslInternal, rwstate) == 156); +static_assert(offsetof(LibreSslSslInternal, shutdown) == 168); +static_assert(sizeof(LibreSslSsl) == 232); +static_assert(offsetof(LibreSslSsl, rbio) == 16); +static_assert(offsetof(LibreSslSsl, wbio) == 24); +static_assert(offsetof(LibreSslSsl, session) == 128); +static_assert(offsetof(LibreSslSsl, ctx) == 152); +static_assert(offsetof(LibreSslSsl, verifyResult) == 160); +static_assert(offsetof(LibreSslSsl, internal) == 224); + +namespace { + +const LibreSslSsl& Connection(const LibreSslSsl* ssl, const char* function) { + if (!ssl) throw std::invalid_argument(std::string(function) + ": null connection"); + return *ssl; +} + +const LibreSslSslInternal& Internal(const LibreSslSsl* ssl, const char* function) { + const LibreSslSsl& connection = Connection(ssl, function); + if (!connection.internal) throw std::invalid_argument(std::string(function) + ": null internal state"); + return *connection.internal; +} + +} + +extern "C" { + +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSslSsl* ssl) { + return Connection(ssl, __func__).ctx; +} + +} diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp new file mode 100644 index 0000000000..db130edf56 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -0,0 +1,65 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct TestSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct TestSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + unsigned char beforeSession[88]; + void* session; + unsigned char beforeCtx[16]; + void* ctx; + std::int64_t verifyResult; + unsigned char beforeInternal[56]; + TestSslInternal* internal; +}; + +static_assert(sizeof(TestSslInternal) == 432 && offsetof(TestSslInternal, rwstate) == 156 && offsetof(TestSslInternal, shutdown) == 168); +static_assert(sizeof(TestSsl) == 232 && offsetof(TestSsl, session) == 128 && offsetof(TestSsl, ctx) == 152); +static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, internal) == 224); + +extern "C" { +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetSslCtx() { + int context = 0; + int session = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_SSL_CTX_nid_postfix(nullptr); }); + Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == nullptr); + ssl.ctx = &context; + ssl.session = &session; + Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); +} + +int main() { + TestGetSslCtx(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index a0df7f31b8..f0c57a7d7f 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 220 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 219 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -31,6 +31,7 @@ - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [EVP_CIPHER_CTX_init, EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_set_padding](../../../core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The 168-byte `EVP_CIPHER_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_enc.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214), which also measured `EVP_CIPHER_CTX_init` clearing 168 bytes. `EVP_CIPHER_CTX_new` allocates through the guest `calloc` as the module would through libc. A null context, which LibreSSL dereferences, throws - [EVP_CIPHER_block_size](../../../core/libs/prx/libSceLibreSsl/src/EvpCipher.cpp) (libSceLibreSsl) - nothing is checked on the console. The 88-byte `EVP_CIPHER` layout and the result follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): `block_size` is returned as stored. A null cipher, which LibreSSL dereferences, throws +- [SSL_get_SSL_CTX](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach it. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 97ef438714f81e2982f2b15e311e88d0f17ffafa Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:17 +0200 Subject: [PATCH 345/612] feat(libSceLibreSsl): implement SSL_get_rbio --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 13 +++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 4d6596aa8e..fef002e5b7 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -878,11 +878,6 @@ int APS5_VABI SSL_get_privatekey_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_rbio_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_get_servername_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 2bec555f3b..7bcd3d1bd9 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -75,4 +75,8 @@ void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSslSsl* ssl) { return Connection(ssl, __func__).ctx; } +void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSslSsl* ssl) { + return Connection(ssl, __func__).rbio; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index db130edf56..95fa53ff94 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -35,6 +35,7 @@ static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, intern extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); +void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -59,7 +60,19 @@ static void TestGetSslCtx() { Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); } +static void TestGetRbio() { + int read = 0; + int write = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_rbio_nid_postfix(nullptr); }); + Require(SSL_get_rbio_nid_postfix(&ssl) == nullptr); + ssl.rbio = &read; + ssl.wbio = &write; + Require(SSL_get_rbio_nid_postfix(&ssl) == &read); +} + int main() { TestGetSslCtx(); + TestGetRbio(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index f0c57a7d7f..bff7b65fcc 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 219 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 218 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -31,7 +31,7 @@ - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [EVP_CIPHER_CTX_init, EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_set_padding](../../../core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The 168-byte `EVP_CIPHER_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_enc.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214), which also measured `EVP_CIPHER_CTX_init` clearing 168 bytes. `EVP_CIPHER_CTX_new` allocates through the guest `calloc` as the module would through libc. A null context, which LibreSSL dereferences, throws - [EVP_CIPHER_block_size](../../../core/libs/prx/libSceLibreSsl/src/EvpCipher.cpp) (libSceLibreSsl) - nothing is checked on the console. The 88-byte `EVP_CIPHER` layout and the result follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): `block_size` is returned as stored. A null cipher, which LibreSSL dereferences, throws -- [SSL_get_SSL_CTX](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach it. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 3d8e708cd3b1353ba7b27555e99797a8e89d55d4 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:27 +0200 Subject: [PATCH 346/612] feat(libSceLibreSsl): implement SSL_get_wbio --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 15 +++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index fef002e5b7..3512ad0fc5 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -898,11 +898,6 @@ int APS5_VABI SSL_get_verify_result_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_wbio_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { return 1; } diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 7bcd3d1bd9..9b00abaff3 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -79,4 +79,8 @@ void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSslSsl* ssl) { return Connection(ssl, __func__).rbio; } +void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSslSsl* ssl) { + return Connection(ssl, __func__).wbio; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 95fa53ff94..6547b2aac1 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -36,6 +36,7 @@ static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, intern extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); +void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -71,8 +72,22 @@ static void TestGetRbio() { Require(SSL_get_rbio_nid_postfix(&ssl) == &read); } +static void TestGetWbio() { + int read = 0; + int write = 0; + int buffer = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_wbio_nid_postfix(nullptr); }); + Require(SSL_get_wbio_nid_postfix(&ssl) == nullptr); + ssl.rbio = &read; + ssl.wbio = &write; + ssl.bbio = &buffer; + Require(SSL_get_wbio_nid_postfix(&ssl) == &write); +} + int main() { TestGetSslCtx(); TestGetRbio(); + TestGetWbio(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index bff7b65fcc..ae9e803d32 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 218 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 217 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -31,7 +31,7 @@ - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [EVP_CIPHER_CTX_init, EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_set_padding](../../../core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The 168-byte `EVP_CIPHER_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_enc.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214), which also measured `EVP_CIPHER_CTX_init` clearing 168 bytes. `EVP_CIPHER_CTX_new` allocates through the guest `calloc` as the module would through libc. A null context, which LibreSSL dereferences, throws - [EVP_CIPHER_block_size](../../../core/libs/prx/libSceLibreSsl/src/EvpCipher.cpp) (libSceLibreSsl) - nothing is checked on the console. The 88-byte `EVP_CIPHER` layout and the result follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): `block_size` is returned as stored. A null cipher, which LibreSSL dereferences, throws -- [SSL_get_SSL_CTX, SSL_get_rbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 90a6773de6e9f229e3e91d776c82ff1bf3b0e254 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:37 +0200 Subject: [PATCH 347/612] feat(libSceLibreSsl): implement SSL_version --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 3512ad0fc5..b8f3237dc5 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -974,11 +974,6 @@ int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void) { return 0; } -int APS5_VABI SSL_version_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_want_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 9b00abaff3..4f67f7311f 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -83,4 +83,8 @@ void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSslSsl* ssl) { return Connection(ssl, __func__).wbio; } +int APS5_VABI SSL_version_nid_postfix(const LibreSslSsl* ssl) { + return Connection(ssl, __func__).version; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 6547b2aac1..10945d54ba 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -37,6 +37,7 @@ extern "C" { void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); +int APS5_VABI SSL_version_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -85,9 +86,20 @@ static void TestGetWbio() { Require(SSL_get_wbio_nid_postfix(&ssl) == &write); } +static void TestVersion() { + TestSsl ssl {}; + RequireThrows([] { SSL_version_nid_postfix(nullptr); }); + Require(SSL_version_nid_postfix(&ssl) == 0); + ssl.version = 0x0303; + Require(SSL_version_nid_postfix(&ssl) == 0x0303); + ssl.version = 0xFEFF; + Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); + TestVersion(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index ae9e803d32..d597800da7 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 217 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 216 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -31,7 +31,7 @@ - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [EVP_CIPHER_CTX_init, EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_set_padding](../../../core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The 168-byte `EVP_CIPHER_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_enc.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214), which also measured `EVP_CIPHER_CTX_init` clearing 168 bytes. `EVP_CIPHER_CTX_new` allocates through the guest `calloc` as the module would through libc. A null context, which LibreSSL dereferences, throws - [EVP_CIPHER_block_size](../../../core/libs/prx/libSceLibreSsl/src/EvpCipher.cpp) (libSceLibreSsl) - nothing is checked on the console. The 88-byte `EVP_CIPHER` layout and the result follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): `block_size` is returned as stored. A null cipher, which LibreSSL dereferences, throws -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 677ba43c1030b127073d004c75c2a99ae21c0809 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:47 +0200 Subject: [PATCH 348/612] feat(libSceLibreSsl): implement SSL_get_verify_result --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index b8f3237dc5..30721a5587 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -893,11 +893,6 @@ int APS5_VABI SSL_get_shutdown_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_verify_result_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { return 1; } diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 4f67f7311f..86f3343d34 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -87,4 +87,8 @@ int APS5_VABI SSL_version_nid_postfix(const LibreSslSsl* ssl) { return Connection(ssl, __func__).version; } +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSslSsl* ssl) { + return Connection(ssl, __func__).verifyResult; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 10945d54ba..2d1a43528c 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -38,6 +38,7 @@ void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -96,10 +97,23 @@ static void TestVersion() { Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); } +static void TestVerifyResult() { + TestSsl ssl {}; + RequireThrows([] { SSL_get_verify_result_nid_postfix(nullptr); }); + Require(SSL_get_verify_result_nid_postfix(&ssl) == 0); + ssl.verifyResult = 20; + Require(SSL_get_verify_result_nid_postfix(&ssl) == 20); + ssl.verifyResult = INT64_C(0x100000002); + Require(SSL_get_verify_result_nid_postfix(&ssl) == INT64_C(0x100000002)); + ssl.verifyResult = -1; + Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); TestVersion(); + TestVerifyResult(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index d597800da7..431a9061ab 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 216 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 215 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -31,7 +31,7 @@ - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [EVP_CIPHER_CTX_init, EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_set_padding](../../../core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The 168-byte `EVP_CIPHER_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_enc.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214), which also measured `EVP_CIPHER_CTX_init` clearing 168 bytes. `EVP_CIPHER_CTX_new` allocates through the guest `calloc` as the module would through libc. A null context, which LibreSSL dereferences, throws - [EVP_CIPHER_block_size](../../../core/libs/prx/libSceLibreSsl/src/EvpCipher.cpp) (libSceLibreSsl) - nothing is checked on the console. The 88-byte `EVP_CIPHER` layout and the result follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): `block_size` is returned as stored. A null cipher, which LibreSSL dereferences, throws -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 4574b58171c9c50c8f1a64e608b8678ed4703a47 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:54:57 +0200 Subject: [PATCH 349/612] feat(libSceLibreSsl): implement SSL_get_shutdown --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 16 ++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 30721a5587..13f1e92da2 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -888,11 +888,6 @@ int APS5_VABI SSL_get_session_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_shutdown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_library_init_nid_postfix(void) { return 1; } diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 86f3343d34..5730456c65 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -91,4 +91,8 @@ std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSslSsl* ssl) return Connection(ssl, __func__).verifyResult; } +int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSslSsl* ssl) { + return Internal(ssl, __func__).shutdown; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 2d1a43528c..2bca9bdbdb 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -39,6 +39,7 @@ void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); +int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -109,11 +110,26 @@ static void TestVerifyResult() { Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); } +static void TestGetShutdown() { + TestSslInternal internal {}; + TestSsl ssl {}; + RequireThrows([] { SSL_get_shutdown_nid_postfix(nullptr); }); + RequireThrows([&] { SSL_get_shutdown_nid_postfix(&ssl); }); + ssl.internal = &internal; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 0); + internal.rwstate = 3; + internal.shutdown = 1; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 1); + internal.shutdown = 3; + Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); +} + int main() { TestGetSslCtx(); TestGetRbio(); TestGetWbio(); TestVersion(); TestVerifyResult(); + TestGetShutdown(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 431a9061ab..26efe3c5fc 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 215 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 214 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -31,7 +31,7 @@ - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [EVP_CIPHER_CTX_init, EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_set_padding](../../../core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The 168-byte `EVP_CIPHER_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_enc.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214), which also measured `EVP_CIPHER_CTX_init` clearing 168 bytes. `EVP_CIPHER_CTX_new` allocates through the guest `calloc` as the module would through libc. A null context, which LibreSSL dereferences, throws - [EVP_CIPHER_block_size](../../../core/libs/prx/libSceLibreSsl/src/EvpCipher.cpp) (libSceLibreSsl) - nothing is checked on the console. The 88-byte `EVP_CIPHER` layout and the result follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): `block_size` is returned as stored. A null cipher, which LibreSSL dereferences, throws -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From d5f0ea7f5ffa5fbb52b41b221f11debcfccd7378 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 04:55:07 +0200 Subject: [PATCH 350/612] feat(libSceLibreSsl): implement SSL_want --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 4 ++++ core/libs/tests/GuestLibreSslSsl.cpp | 16 ++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 13f1e92da2..8f991e8eb1 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -964,11 +964,6 @@ int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void) { return 0; } -int APS5_VABI SSL_want_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_write_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 5730456c65..9fcf922b19 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -95,4 +95,8 @@ int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSslSsl* ssl) { return Internal(ssl, __func__).shutdown; } +int APS5_VABI SSL_want_nid_postfix(const LibreSslSsl* ssl) { + return Internal(ssl, __func__).rwstate; +} + } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 2bca9bdbdb..04abd2ad93 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -40,6 +40,7 @@ void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); int APS5_VABI SSL_version_nid_postfix(const TestSsl*); std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); +int APS5_VABI SSL_want_nid_postfix(const TestSsl*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -124,6 +125,20 @@ static void TestGetShutdown() { Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); } +static void TestWant() { + TestSslInternal internal {}; + TestSsl ssl {}; + RequireThrows([] { SSL_want_nid_postfix(nullptr); }); + RequireThrows([&] { SSL_want_nid_postfix(&ssl); }); + ssl.internal = &internal; + Require(SSL_want_nid_postfix(&ssl) == 0); + internal.rwstate = 1; + Require(SSL_want_nid_postfix(&ssl) == 1); + internal.shutdown = 2; + internal.rwstate = 3; + Require(SSL_want_nid_postfix(&ssl) == 3); +} + int main() { TestGetSslCtx(); TestGetRbio(); @@ -131,5 +146,6 @@ int main() { TestVersion(); TestVerifyResult(); TestGetShutdown(); + TestWant(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 26efe3c5fc..c26bc522c9 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 214 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 213 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -31,7 +31,7 @@ - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [EVP_CIPHER_CTX_init, EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_set_padding](../../../core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The 168-byte `EVP_CIPHER_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_enc.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214), which also measured `EVP_CIPHER_CTX_init` clearing 168 bytes. `EVP_CIPHER_CTX_new` allocates through the guest `calloc` as the module would through libc. A null context, which LibreSSL dereferences, throws - [EVP_CIPHER_block_size](../../../core/libs/prx/libSceLibreSsl/src/EvpCipher.cpp) (libSceLibreSsl) - nothing is checked on the console. The 88-byte `EVP_CIPHER` layout and the result follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): `block_size` is returned as stored. A null cipher, which LibreSSL dereferences, throws -- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw +- [SSL_get_SSL_CTX, SSL_get_rbio, SSL_get_wbio, SSL_version, SSL_get_verify_result, SSL_get_shutdown, SSL_want](../../../core/libs/prx/libSceLibreSsl/src/Ssl.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a connection yet, so only connections the caller builds reach them. A null connection, and for the fields kept in `ssl_internal_st` a null internal state, which LibreSSL dereferences, throw ### Functional From 30e4550f160487fd9fd7641636dd070aea990ff3 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 06:01:21 +0100 Subject: [PATCH 351/612] refactor(libSceLibreSsl): declare the SSL accessor exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 53 +++++++++++++++++--- core/libs/prx/libSceLibreSsl/src/Ssl.cpp | 40 +-------------- core/libs/tests/GuestLibreSslSsl.cpp | 53 +++++++++----------- 3 files changed, 71 insertions(+), 75 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 58d3f2dc88..4ba8146921 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -342,6 +342,45 @@ struct LibreSslEvpCipher { void* applicationData; }; +struct LibreSslSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct LibreSslSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + int server; + void* s3; + void* d1; + void* param; + void* cipherList; + void* cert; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* session; + int verifyMode; + int error; + int errorCode; + void* ctx; + std::int64_t verifyResult; + int references; + int clientVersion; + unsigned int maxSendFragment; + char* tlsextHostname; + int tlsextStatusType; + void* initialCtx; + void* encReadCtx; + void* readHash; + LibreSslSslInternal* internal; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -593,7 +632,7 @@ int APS5_VABI SSL_free_nid_postfix(void); int APS5_VABI SSL_get0_alpn_selected_nid_postfix(void); int APS5_VABI SSL_get0_param_nid_postfix(void); int APS5_VABI SSL_get1_session_nid_postfix(void); -int APS5_VABI SSL_get_SSL_CTX_nid_postfix(void); +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSslSsl* ssl); int APS5_VABI SSL_get_certificate_nid_postfix(void); int APS5_VABI SSL_get_current_cipher_nid_postfix(void); int APS5_VABI SSL_get_error_nid_postfix(void); @@ -604,12 +643,12 @@ int APS5_VABI SSL_get_fd_nid_postfix(void); int APS5_VABI SSL_get_peer_cert_chain_nid_postfix(void); int APS5_VABI SSL_get_peer_certificate_nid_postfix(void); int APS5_VABI SSL_get_privatekey_nid_postfix(void); -int APS5_VABI SSL_get_rbio_nid_postfix(void); +void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSslSsl* ssl); int APS5_VABI SSL_get_servername_nid_postfix(void); int APS5_VABI SSL_get_session_nid_postfix(void); -int APS5_VABI SSL_get_shutdown_nid_postfix(void); -int APS5_VABI SSL_get_verify_result_nid_postfix(void); -int APS5_VABI SSL_get_wbio_nid_postfix(void); +int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSslSsl* ssl); +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSslSsl* ssl); +void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSslSsl* ssl); int APS5_VABI SSL_library_init_nid_postfix(void); void APS5_VABI SSL_load_error_strings_nid_postfix(void); int APS5_VABI SSL_new_nid_postfix(void); @@ -627,8 +666,8 @@ int APS5_VABI SSL_set_verify_nid_postfix(void); int APS5_VABI SSL_shutdown_nid_postfix(void); int APS5_VABI SSL_use_PrivateKey_ASN1_nid_postfix(void); int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void); -int APS5_VABI SSL_version_nid_postfix(void); -int APS5_VABI SSL_want_nid_postfix(void); +int APS5_VABI SSL_version_nid_postfix(const LibreSslSsl* ssl); +int APS5_VABI SSL_want_nid_postfix(const LibreSslSsl* ssl); int APS5_VABI SSL_write_nid_postfix(void); std::uint64_t APS5_VABI SSLeay_nid_postfix(void); int APS5_VABI SSLv23_client_method_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp index 9fcf922b19..29ae0fadf6 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -1,48 +1,10 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslSslInternal { - unsigned char beforeRwstate[156]; - int rwstate; - unsigned char beforeShutdown[8]; - int shutdown; - unsigned char rest[260]; -}; - -struct LibreSslSsl { - int version; - const void* method; - void* rbio; - void* wbio; - void* bbio; - int server; - void* s3; - void* d1; - void* param; - void* cipherList; - void* cert; - unsigned int sidCtxLength; - unsigned char sidCtx[32]; - void* session; - int verifyMode; - int error; - int errorCode; - void* ctx; - std::int64_t verifyResult; - int references; - int clientVersion; - unsigned int maxSendFragment; - char* tlsextHostname; - int tlsextStatusType; - void* initialCtx; - void* encReadCtx; - void* readHash; - LibreSslSslInternal* internal; -}; - static_assert(sizeof(LibreSslSslInternal) == 432); static_assert(offsetof(LibreSslSslInternal, rwstate) == 156); static_assert(offsetof(LibreSslSslInternal, shutdown) == 168); diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp index 04abd2ad93..2c2863a664 100644 --- a/core/libs/tests/GuestLibreSslSsl.cpp +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -33,14 +34,8 @@ static_assert(sizeof(TestSslInternal) == 432 && offsetof(TestSslInternal, rwstat static_assert(sizeof(TestSsl) == 232 && offsetof(TestSsl, session) == 128 && offsetof(TestSsl, ctx) == 152); static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, internal) == 224); -extern "C" { -void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const TestSsl*); -void* APS5_VABI SSL_get_rbio_nid_postfix(const TestSsl*); -void* APS5_VABI SSL_get_wbio_nid_postfix(const TestSsl*); -int APS5_VABI SSL_version_nid_postfix(const TestSsl*); -std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const TestSsl*); -int APS5_VABI SSL_get_shutdown_nid_postfix(const TestSsl*); -int APS5_VABI SSL_want_nid_postfix(const TestSsl*); +static const LibreSslSsl* Ssl(const TestSsl* ssl) { + return reinterpret_cast(ssl); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -59,10 +54,10 @@ static void TestGetSslCtx() { int session = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_SSL_CTX_nid_postfix(nullptr); }); - Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == nullptr); + Require(SSL_get_SSL_CTX_nid_postfix(Ssl(&ssl)) == nullptr); ssl.ctx = &context; ssl.session = &session; - Require(SSL_get_SSL_CTX_nid_postfix(&ssl) == &context); + Require(SSL_get_SSL_CTX_nid_postfix(Ssl(&ssl)) == &context); } static void TestGetRbio() { @@ -70,10 +65,10 @@ static void TestGetRbio() { int write = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_rbio_nid_postfix(nullptr); }); - Require(SSL_get_rbio_nid_postfix(&ssl) == nullptr); + Require(SSL_get_rbio_nid_postfix(Ssl(&ssl)) == nullptr); ssl.rbio = &read; ssl.wbio = &write; - Require(SSL_get_rbio_nid_postfix(&ssl) == &read); + Require(SSL_get_rbio_nid_postfix(Ssl(&ssl)) == &read); } static void TestGetWbio() { @@ -82,61 +77,61 @@ static void TestGetWbio() { int buffer = 0; TestSsl ssl {}; RequireThrows([] { SSL_get_wbio_nid_postfix(nullptr); }); - Require(SSL_get_wbio_nid_postfix(&ssl) == nullptr); + Require(SSL_get_wbio_nid_postfix(Ssl(&ssl)) == nullptr); ssl.rbio = &read; ssl.wbio = &write; ssl.bbio = &buffer; - Require(SSL_get_wbio_nid_postfix(&ssl) == &write); + Require(SSL_get_wbio_nid_postfix(Ssl(&ssl)) == &write); } static void TestVersion() { TestSsl ssl {}; RequireThrows([] { SSL_version_nid_postfix(nullptr); }); - Require(SSL_version_nid_postfix(&ssl) == 0); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0); ssl.version = 0x0303; - Require(SSL_version_nid_postfix(&ssl) == 0x0303); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0x0303); ssl.version = 0xFEFF; - Require(SSL_version_nid_postfix(&ssl) == 0xFEFF); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0xFEFF); } static void TestVerifyResult() { TestSsl ssl {}; RequireThrows([] { SSL_get_verify_result_nid_postfix(nullptr); }); - Require(SSL_get_verify_result_nid_postfix(&ssl) == 0); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == 0); ssl.verifyResult = 20; - Require(SSL_get_verify_result_nid_postfix(&ssl) == 20); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == 20); ssl.verifyResult = INT64_C(0x100000002); - Require(SSL_get_verify_result_nid_postfix(&ssl) == INT64_C(0x100000002)); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == INT64_C(0x100000002)); ssl.verifyResult = -1; - Require(SSL_get_verify_result_nid_postfix(&ssl) == -1); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == -1); } static void TestGetShutdown() { TestSslInternal internal {}; TestSsl ssl {}; RequireThrows([] { SSL_get_shutdown_nid_postfix(nullptr); }); - RequireThrows([&] { SSL_get_shutdown_nid_postfix(&ssl); }); + RequireThrows([&] { SSL_get_shutdown_nid_postfix(Ssl(&ssl)); }); ssl.internal = &internal; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 0); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 0); internal.rwstate = 3; internal.shutdown = 1; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 1); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 1); internal.shutdown = 3; - Require(SSL_get_shutdown_nid_postfix(&ssl) == 3); + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 3); } static void TestWant() { TestSslInternal internal {}; TestSsl ssl {}; RequireThrows([] { SSL_want_nid_postfix(nullptr); }); - RequireThrows([&] { SSL_want_nid_postfix(&ssl); }); + RequireThrows([&] { SSL_want_nid_postfix(Ssl(&ssl)); }); ssl.internal = &internal; - Require(SSL_want_nid_postfix(&ssl) == 0); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 0); internal.rwstate = 1; - Require(SSL_want_nid_postfix(&ssl) == 1); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 1); internal.shutdown = 2; internal.rwstate = 3; - Require(SSL_want_nid_postfix(&ssl) == 3); + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 3); } int main() { From 546ecf161782203cea710a85881dab412cd8e5e7 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:00:27 +0200 Subject: [PATCH 352/612] feat(libSceLibreSsl): implement SSL_CTX_get_verify_mode --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 ++++- core/libs/prx/libSceLibreSsl/Export.cpp | 5 -- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 53 ++++++++++++++++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 60 +++++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 3 +- 5 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/SslCtx.cpp create mode 100644 core/libs/tests/GuestLibreSslSslCtx.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 9f9df4076c..d42290b4ea 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Bignum.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipherCtx.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Evp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipher.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Bignum.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipherCtx.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Evp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SslCtx.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -203,6 +203,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_evp_cipher PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_ssl_ctx_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSslCtx.cpp) + target_include_directories(guest_libressl_ssl_ctx_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_ctx_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_ctx_tests) + add_test(NAME guest_libressl_ssl_ctx COMMAND guest_libressl_ssl_ctx_tests) + set_tests_properties(guest_libressl_ssl_ctx PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl_ctx PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 50a98d55c0..22b40066fc 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -653,11 +653,6 @@ int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_load_verify_locations_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp new file mode 100644 index 0000000000..f4566a99a9 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct LibreSslCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + int references; + void* extraCerts; + int verifyMode; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* param; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + LibreSslCtxInternal* internal; +}; + +static_assert(sizeof(LibreSslCtxInternal) == 464); +static_assert(offsetof(LibreSslCtxInternal, quietShutdown) == 292); +static_assert(sizeof(LibreSslCtx) == 120); +static_assert(offsetof(LibreSslCtx, certStore) == 16); +static_assert(offsetof(LibreSslCtx, verifyMode) == 48); +static_assert(offsetof(LibreSslCtx, defaultPasswdCallback) == 96); +static_assert(offsetof(LibreSslCtx, defaultPasswdCallbackUserdata) == 104); +static_assert(offsetof(LibreSslCtx, internal) == 112); + +namespace { + +LibreSslCtx& Context(LibreSslCtx* ctx, const char* function) { + if (!ctx) throw std::invalid_argument(std::string(function) + ": null context"); + return *ctx; +} + +} + +extern "C" { + +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx) { + return Context(ctx, __func__).verifyMode; +} + +} diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp new file mode 100644 index 0000000000..acbcf82c94 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -0,0 +1,60 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +struct TestCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct TestCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + unsigned char beforeVerifyMode[16]; + int verifyMode; + unsigned char beforeCallback[44]; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + TestCtxInternal* internal; +}; + +static_assert(sizeof(TestCtxInternal) == 464 && offsetof(TestCtxInternal, quietShutdown) == 292); +static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && offsetof(TestCtx, defaultPasswdCallback) == 96); + +extern "C" { +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL_CTX check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetVerifyMode() { + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_get_verify_mode_nid_postfix(nullptr); }); + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0); + ctx.verifyMode = 0x03; + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0x03); + ctx.verifyMode = -1; + Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); +} + +int main() { + TestGetVerifyMode(); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index a0df7f31b8..a4ef7267ee 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 220 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 219 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -31,6 +31,7 @@ - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [EVP_CIPHER_CTX_init, EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_set_padding](../../../core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The 168-byte `EVP_CIPHER_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_enc.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214), which also measured `EVP_CIPHER_CTX_init` clearing 168 bytes. `EVP_CIPHER_CTX_new` allocates through the guest `calloc` as the module would through libc. A null context, which LibreSSL dereferences, throws - [EVP_CIPHER_block_size](../../../core/libs/prx/libSceLibreSsl/src/EvpCipher.cpp) (libSceLibreSsl) - nothing is checked on the console. The 88-byte `EVP_CIPHER` layout and the result follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): `block_size` is returned as stored. A null cipher, which LibreSSL dereferences, throws +- [SSL_CTX_get_verify_mode](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach it, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 05ab42a339b431dce4105ae70621c1a552337a72 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:22 +0200 Subject: [PATCH 353/612] feat(libSceLibreSsl): implement SSL_CTX_get_cert_store --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 14 ++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 22b40066fc..f455e02db7 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -643,11 +643,6 @@ int APS5_VABI SSL_CTX_free_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_get_cert_store_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index f4566a99a9..446eff72de 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -50,4 +50,8 @@ int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx) { return Context(ctx, __func__).verifyMode; } +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx) { + return Context(ctx, __func__).certStore; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index acbcf82c94..df6c8d1c3d 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -31,6 +31,7 @@ static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && o extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -54,7 +55,20 @@ static void TestGetVerifyMode() { Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); } +static void TestGetCertStore() { + int store = 0; + int ciphers = 0; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_get_cert_store_nid_postfix(nullptr); }); + Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == nullptr); + ctx.cipherList = &ciphers; + ctx.certStore = &store; + ctx.sessionTimeout = -1; + Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); +} + int main() { TestGetVerifyMode(); + TestGetCertStore(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index a4ef7267ee..f453330e38 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 219 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 218 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -31,7 +31,7 @@ - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [EVP_CIPHER_CTX_init, EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_set_padding](../../../core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The 168-byte `EVP_CIPHER_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_enc.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214), which also measured `EVP_CIPHER_CTX_init` clearing 168 bytes. `EVP_CIPHER_CTX_new` allocates through the guest `calloc` as the module would through libc. A null context, which LibreSSL dereferences, throws - [EVP_CIPHER_block_size](../../../core/libs/prx/libSceLibreSsl/src/EvpCipher.cpp) (libSceLibreSsl) - nothing is checked on the console. The 88-byte `EVP_CIPHER` layout and the result follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): `block_size` is returned as stored. A null cipher, which LibreSSL dereferences, throws -- [SSL_CTX_get_verify_mode](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach it, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 6fe4b307fa4b7bdca9128ad3bb389b47ad22a30e Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:31 +0200 Subject: [PATCH 354/612] feat(libSceLibreSsl): implement SSL_CTX_set_quiet_shutdown --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 6 ++++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 18 ++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index f455e02db7..8487d326f4 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -733,11 +733,6 @@ int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 446eff72de..2a86c75d25 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -54,4 +54,10 @@ void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx) { return Context(ctx, __func__).certStore; } +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode) { + LibreSslCtx& context = Context(ctx, __func__); + if (!context.internal) throw std::invalid_argument(std::string(__func__) + ": null internal state"); + context.internal->quietShutdown = mode; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index df6c8d1c3d..38f3bb5fad 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -32,6 +32,7 @@ static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && o extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -67,8 +68,25 @@ static void TestGetCertStore() { Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); } +static void TestSetQuietShutdown() { + TestCtxInternal internal {}; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_set_quiet_shutdown_nid_postfix(nullptr, 1); }); + RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); }); + ctx.internal = &internal; + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); + Require(internal.quietShutdown == 1); + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, INT_MIN); + Require(internal.quietShutdown == INT_MIN); + SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 0); + Require(internal.quietShutdown == 0); + for (unsigned char byte : internal.beforeQuietShutdown) Require(byte == 0); + for (unsigned char byte : internal.rest) Require(byte == 0); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); + TestSetQuietShutdown(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index f453330e38..63f4a201b1 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 218 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 217 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -31,7 +31,7 @@ - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [EVP_CIPHER_CTX_init, EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_set_padding](../../../core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The 168-byte `EVP_CIPHER_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_enc.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214), which also measured `EVP_CIPHER_CTX_init` clearing 168 bytes. `EVP_CIPHER_CTX_new` allocates through the guest `calloc` as the module would through libc. A null context, which LibreSSL dereferences, throws - [EVP_CIPHER_block_size](../../../core/libs/prx/libSceLibreSsl/src/EvpCipher.cpp) (libSceLibreSsl) - nothing is checked on the console. The 88-byte `EVP_CIPHER` layout and the result follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): `block_size` is returned as stored. A null cipher, which LibreSSL dereferences, throws -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 51b17e5994a61a67f2a19fcc67ea99aed94b96fc Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:41 +0200 Subject: [PATCH 355/612] feat(libSceLibreSsl): implement SSL_CTX_set_default_passwd_cb --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 8487d326f4..3d87a6ad55 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -703,11 +703,6 @@ int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 2a86c75d25..573dbf8978 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -60,4 +60,8 @@ void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode context.internal->quietShutdown = mode; } +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* callback) { + Context(ctx, __func__).defaultPasswdCallback = callback; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index 38f3bb5fad..613aed964a 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -33,6 +33,7 @@ extern "C" { int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -84,9 +85,20 @@ static void TestSetQuietShutdown() { for (unsigned char byte : internal.rest) Require(byte == 0); } +static void TestSetPasswordCallback() { + int callback = 0; + TestCtx ctx {}; + RequireThrows([&] { SSL_CTX_set_default_passwd_cb_nid_postfix(nullptr, &callback); }); + SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, &callback); + Require(ctx.defaultPasswdCallback == &callback && ctx.defaultPasswdCallbackUserdata == nullptr); + SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, nullptr); + Require(ctx.defaultPasswdCallback == nullptr); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); TestSetQuietShutdown(); + TestSetPasswordCallback(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 63f4a201b1..7386315e1e 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 217 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 216 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -31,7 +31,7 @@ - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [EVP_CIPHER_CTX_init, EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_set_padding](../../../core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The 168-byte `EVP_CIPHER_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_enc.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214), which also measured `EVP_CIPHER_CTX_init` clearing 168 bytes. `EVP_CIPHER_CTX_new` allocates through the guest `calloc` as the module would through libc. A null context, which LibreSSL dereferences, throws - [EVP_CIPHER_block_size](../../../core/libs/prx/libSceLibreSsl/src/EvpCipher.cpp) (libSceLibreSsl) - nothing is checked on the console. The 88-byte `EVP_CIPHER` layout and the result follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): `block_size` is returned as stored. A null cipher, which LibreSSL dereferences, throws -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 4311e68cf189d02fbcf27696c63ced05d5640d5b Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sat, 24 Oct 2026 05:01:51 +0200 Subject: [PATCH 356/612] feat(libSceLibreSsl): implement SSL_CTX_set_default_passwd_cb_userdata --- core/libs/prx/libSceLibreSsl/Export.cpp | 5 ----- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 4 ++++ core/libs/tests/GuestLibreSslSslCtx.cpp | 12 ++++++++++++ docs/dev/debt/libSceLibreSsl.md | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 3d87a6ad55..51a734709a 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -703,11 +703,6 @@ int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index 573dbf8978..b70a0d9f12 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -64,4 +64,8 @@ void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* Context(ctx, __func__).defaultPasswdCallback = callback; } +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* ctx, void* userdata) { + Context(ctx, __func__).defaultPasswdCallbackUserdata = userdata; +} + } diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index 613aed964a..fe76a282de 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -34,6 +34,7 @@ int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(TestCtx*, void*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -95,10 +96,21 @@ static void TestSetPasswordCallback() { Require(ctx.defaultPasswdCallback == nullptr); } +static void TestSetPasswordUserdata() { + int userdata = 0; + TestCtx ctx {}; + RequireThrows([&] { SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(nullptr, &userdata); }); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, &userdata); + Require(ctx.defaultPasswdCallbackUserdata == &userdata && ctx.defaultPasswdCallback == nullptr && ctx.internal == nullptr); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, nullptr); + Require(ctx.defaultPasswdCallbackUserdata == nullptr); +} + int main() { TestGetVerifyMode(); TestGetCertStore(); TestSetQuietShutdown(); TestSetPasswordCallback(); + TestSetPasswordUserdata(); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 7386315e1e..00729e4caf 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,7 +7,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 216 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 215 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c9cd71c1f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -31,7 +31,7 @@ - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console - [EVP_CIPHER_CTX_init, EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_set_padding](../../../core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The 168-byte `EVP_CIPHER_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_enc.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214), which also measured `EVP_CIPHER_CTX_init` clearing 168 bytes. `EVP_CIPHER_CTX_new` allocates through the guest `calloc` as the module would through libc. A null context, which LibreSSL dereferences, throws - [EVP_CIPHER_block_size](../../../core/libs/prx/libSceLibreSsl/src/EvpCipher.cpp) (libSceLibreSsl) - nothing is checked on the console. The 88-byte `EVP_CIPHER` layout and the result follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): `block_size` is returned as stored. A null cipher, which LibreSSL dereferences, throws -- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw +- [SSL_CTX_get_verify_mode, SSL_CTX_get_cert_store, SSL_CTX_set_quiet_shutdown, SSL_CTX_set_default_passwd_cb, SSL_CTX_set_default_passwd_cb_userdata](../../../core/libs/prx/libSceLibreSsl/src/SslCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl) `ssl.h`, `ssl_locl.h` and `ssl_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a context yet, so only contexts the caller builds reach them, and no export calls the stored password callback. A null context, and a null internal state in `SSL_CTX_set_quiet_shutdown`, which LibreSSL dereferences, throw ### Functional From 2c6493028f9f76ba30ef34db52d8441d07f77536 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 05:59:27 +0100 Subject: [PATCH 357/612] refactor(libSceLibreSsl): declare the SSL_CTX accessor exports in the public header --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 32 +++++++++++++++--- core/libs/prx/libSceLibreSsl/src/SslCtx.cpp | 23 +------------ core/libs/tests/GuestLibreSslSslCtx.cpp | 35 +++++++++----------- 3 files changed, 44 insertions(+), 46 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 58d3f2dc88..dd3f2458ba 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -342,6 +342,28 @@ struct LibreSslEvpCipher { void* applicationData; }; +struct LibreSslCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct LibreSslCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + int references; + void* extraCerts; + int verifyMode; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* param; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + LibreSslCtxInternal* internal; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -552,10 +574,10 @@ int APS5_VABI SSL_CTX_callback_ctrl_nid_postfix(void); int APS5_VABI SSL_CTX_check_private_key_nid_postfix(void); int APS5_VABI SSL_CTX_ctrl_nid_postfix(void); int APS5_VABI SSL_CTX_free_nid_postfix(void); -int APS5_VABI SSL_CTX_get_cert_store_nid_postfix(void); +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx); int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void); int APS5_VABI SSL_CTX_get_ex_new_index_nid_postfix(std::int64_t argl, void* argp, LibreSslExNew newFunction, LibreSslExDup dupFunction, LibreSslExFree freeFunction); -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void); +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx); int APS5_VABI SSL_CTX_load_verify_locations_nid_postfix(void); int APS5_VABI SSL_CTX_load_verify_mem_nid_postfix(void); int APS5_VABI SSL_CTX_new_nid_postfix(void); @@ -567,13 +589,13 @@ int APS5_VABI SSL_CTX_set_cert_store_nid_postfix(void); int APS5_VABI SSL_CTX_set_cert_verify_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_cipher_list_nid_postfix(void); int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void); +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* callback); +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* ctx, void* userdata); int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void); int APS5_VABI SSL_CTX_set_info_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_msg_callback_nid_postfix(void); int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(void); +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode); int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void); int APS5_VABI SSL_CTX_set_verify_nid_postfix(void); int APS5_VABI SSL_CTX_use_PrivateKey_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp index b70a0d9f12..99eb4fa5a1 100644 --- a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -1,31 +1,10 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include #include #include #include #include "prx/libc/include/General.hpp" -struct LibreSslCtxInternal { - unsigned char beforeQuietShutdown[292]; - int quietShutdown; - unsigned char rest[168]; -}; - -struct LibreSslCtx { - const void* method; - void* cipherList; - void* certStore; - std::int64_t sessionTimeout; - int references; - void* extraCerts; - int verifyMode; - unsigned int sidCtxLength; - unsigned char sidCtx[32]; - void* param; - void* defaultPasswdCallback; - void* defaultPasswdCallbackUserdata; - LibreSslCtxInternal* internal; -}; - static_assert(sizeof(LibreSslCtxInternal) == 464); static_assert(offsetof(LibreSslCtxInternal, quietShutdown) == 292); static_assert(sizeof(LibreSslCtx) == 120); diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp index fe76a282de..3e77a25e02 100644 --- a/core/libs/tests/GuestLibreSslSslCtx.cpp +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -29,12 +30,8 @@ struct TestCtx { static_assert(sizeof(TestCtxInternal) == 464 && offsetof(TestCtxInternal, quietShutdown) == 292); static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && offsetof(TestCtx, defaultPasswdCallback) == 96); -extern "C" { -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(TestCtx*); -void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(TestCtx*); -void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(TestCtx*, int); -void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(TestCtx*, void*); -void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(TestCtx*, void*); +static LibreSslCtx* Context(TestCtx* ctx) { + return reinterpret_cast(ctx); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -51,11 +48,11 @@ template static void RequireThrows(TAction action, std::source static void TestGetVerifyMode() { TestCtx ctx {}; RequireThrows([] { SSL_CTX_get_verify_mode_nid_postfix(nullptr); }); - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == 0); ctx.verifyMode = 0x03; - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == 0x03); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == 0x03); ctx.verifyMode = -1; - Require(SSL_CTX_get_verify_mode_nid_postfix(&ctx) == -1); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == -1); } static void TestGetCertStore() { @@ -63,24 +60,24 @@ static void TestGetCertStore() { int ciphers = 0; TestCtx ctx {}; RequireThrows([] { SSL_CTX_get_cert_store_nid_postfix(nullptr); }); - Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == nullptr); + Require(SSL_CTX_get_cert_store_nid_postfix(Context(&ctx)) == nullptr); ctx.cipherList = &ciphers; ctx.certStore = &store; ctx.sessionTimeout = -1; - Require(SSL_CTX_get_cert_store_nid_postfix(&ctx) == &store); + Require(SSL_CTX_get_cert_store_nid_postfix(Context(&ctx)) == &store); } static void TestSetQuietShutdown() { TestCtxInternal internal {}; TestCtx ctx {}; RequireThrows([] { SSL_CTX_set_quiet_shutdown_nid_postfix(nullptr, 1); }); - RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); }); + RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 1); }); ctx.internal = &internal; - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 1); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 1); Require(internal.quietShutdown == 1); - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, INT_MIN); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), INT_MIN); Require(internal.quietShutdown == INT_MIN); - SSL_CTX_set_quiet_shutdown_nid_postfix(&ctx, 0); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 0); Require(internal.quietShutdown == 0); for (unsigned char byte : internal.beforeQuietShutdown) Require(byte == 0); for (unsigned char byte : internal.rest) Require(byte == 0); @@ -90,9 +87,9 @@ static void TestSetPasswordCallback() { int callback = 0; TestCtx ctx {}; RequireThrows([&] { SSL_CTX_set_default_passwd_cb_nid_postfix(nullptr, &callback); }); - SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, &callback); + SSL_CTX_set_default_passwd_cb_nid_postfix(Context(&ctx), &callback); Require(ctx.defaultPasswdCallback == &callback && ctx.defaultPasswdCallbackUserdata == nullptr); - SSL_CTX_set_default_passwd_cb_nid_postfix(&ctx, nullptr); + SSL_CTX_set_default_passwd_cb_nid_postfix(Context(&ctx), nullptr); Require(ctx.defaultPasswdCallback == nullptr); } @@ -100,9 +97,9 @@ static void TestSetPasswordUserdata() { int userdata = 0; TestCtx ctx {}; RequireThrows([&] { SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(nullptr, &userdata); }); - SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, &userdata); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(Context(&ctx), &userdata); Require(ctx.defaultPasswdCallbackUserdata == &userdata && ctx.defaultPasswdCallback == nullptr && ctx.internal == nullptr); - SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(&ctx, nullptr); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(Context(&ctx), nullptr); Require(ctx.defaultPasswdCallbackUserdata == nullptr); } From 877a7567ca2b567e42a774c1a2c6496a5197c9c4 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Mon, 2 Nov 2026 15:56:36 +0100 Subject: [PATCH 358/612] feat(libwebrtc): implement webrtc::rtcp::Pli::Parse --- core/libs/prx/libwebrtc/CMakeLists.txt | 1 + core/libs/prx/libwebrtc/Export.cpp | 5 -- core/libs/prx/libwebrtc/include/Api.hpp | 3 +- core/libs/prx/libwebrtc/include/RtcpPli.hpp | 16 +++++ core/libs/prx/libwebrtc/src/RtcpPli.cpp | 72 +++++++++++++++++++++ 5 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/libwebrtc/include/RtcpPli.hpp create mode 100644 core/libs/prx/libwebrtc/src/RtcpPli.cpp diff --git a/core/libs/prx/libwebrtc/CMakeLists.txt b/core/libs/prx/libwebrtc/CMakeLists.txt index a0809c2959..b66133cac5 100644 --- a/core/libs/prx/libwebrtc/CMakeLists.txt +++ b/core/libs/prx/libwebrtc/CMakeLists.txt @@ -12,6 +12,7 @@ target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/IpAddress.cpp) target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/RtcpCommonHeader.cpp) target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/CpuFeatures.cpp) target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/RmsLevel.cpp) +target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/RtcpPli.cpp) target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Opus.cpp) target_link_libraries(libwebrtc PRIVATE opus) target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/TaskQueueBase.cpp) diff --git a/core/libs/prx/libwebrtc/Export.cpp b/core/libs/prx/libwebrtc/Export.cpp index 6eb865f27a..ca560ab5dc 100644 --- a/core/libs/prx/libwebrtc/Export.cpp +++ b/core/libs/prx/libwebrtc/Export.cpp @@ -1111,11 +1111,6 @@ int APS5_VABI _ZN6webrtc4rtcp4NackD1Ev_nid_postfix(void) { return 0; } -int APS5_VABI _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN6webrtc4rtcp3PliC1Ev_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libwebrtc/include/Api.hpp b/core/libs/prx/libwebrtc/include/Api.hpp index c7f068170a..a7a6fd792e 100644 --- a/core/libs/prx/libwebrtc/include/Api.hpp +++ b/core/libs/prx/libwebrtc/include/Api.hpp @@ -9,6 +9,7 @@ #include "prx/libwebrtc/include/IpAddress.hpp" #include "prx/libwebrtc/include/RmsLevel.hpp" #include "prx/libwebrtc/include/RtcpCommonHeader.hpp" +#include "prx/libwebrtc/include/RtcpPli.hpp" #include "prx/libwebrtc/include/Opus.hpp" #include "prx/libwebrtc/include/CpuFeatures.hpp" #include @@ -243,7 +244,7 @@ int APS5_VABI _ZN6webrtc4rtcp14CompoundPacketD1Ev_nid_postfix(void); int APS5_VABI _ZN6webrtc4rtcp4Nack5ParseERKNS0_12CommonHeaderE_nid_postfix(void); int APS5_VABI _ZN6webrtc4rtcp4NackC1Ev_nid_postfix(void); int APS5_VABI _ZN6webrtc4rtcp4NackD1Ev_nid_postfix(void); -int APS5_VABI _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(void); +bool APS5_VABI _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(WebRtcRtcpPli* pli, const WebRtcRtcpCommonHeader* packet); int APS5_VABI _ZN6webrtc4rtcp3PliC1Ev_nid_postfix(void); int APS5_VABI _ZN6webrtc4rtcp3PliD1Ev_nid_postfix(void); int APS5_VABI _ZN6webrtc4rtcp14ReceiverReportC1Ev_nid_postfix(void); diff --git a/core/libs/prx/libwebrtc/include/RtcpPli.hpp b/core/libs/prx/libwebrtc/include/RtcpPli.hpp new file mode 100644 index 0000000000..405880cf8a --- /dev/null +++ b/core/libs/prx/libwebrtc/include/RtcpPli.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +constexpr std::size_t WebRtcRtcpCommonFeedbackSize = 8; + +struct WebRtcRtcpPli { + const void* vtable; + std::uint32_t senderSsrc; + std::uint32_t mediaSsrc; +}; + +static_assert(sizeof(WebRtcRtcpPli) == 16); +static_assert(offsetof(WebRtcRtcpPli, senderSsrc) == 8); +static_assert(offsetof(WebRtcRtcpPli, mediaSsrc) == 12); diff --git a/core/libs/prx/libwebrtc/src/RtcpPli.cpp b/core/libs/prx/libwebrtc/src/RtcpPli.cpp new file mode 100644 index 0000000000..7ca3445464 --- /dev/null +++ b/core/libs/prx/libwebrtc/src/RtcpPli.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include + +namespace { + +std::uint32_t ReadBigEndian(const std::uint8_t* bytes) { + return (static_cast(bytes[0]) << 24) | (static_cast(bytes[1]) << 16) | (static_cast(bytes[2]) << 8) | bytes[3]; +} + +} + +extern "C" { + +bool APS5_VABI _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(WebRtcRtcpPli* pli, const WebRtcRtcpCommonHeader* packet) { + if (!pli) + throw std::invalid_argument("Pli::Parse requires a packet object"); + if (!packet) + throw std::invalid_argument("Pli::Parse requires a common header"); + if (packet->payloadSize < WebRtcRtcpCommonFeedbackSize) + return false; + if (!packet->payload) + throw std::invalid_argument("Pli::Parse requires a payload"); + pli->senderSsrc = ReadBigEndian(packet->payload); + pli->mediaSsrc = ReadBigEndian(packet->payload + 4); + return true; +} + +} From e3aae49e2e672172f009b62de0bbc47b106c86a2 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Mon, 2 Nov 2026 15:57:49 +0100 Subject: [PATCH 359/612] test(libwebrtc): check rtcp::Pli::Parse against pli.cc --- core/libs/prx/libwebrtc/CMakeLists.txt | 10 +++ core/libs/prx/libwebrtc/tests/RtcpPli.cpp | 97 +++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 core/libs/prx/libwebrtc/tests/RtcpPli.cpp diff --git a/core/libs/prx/libwebrtc/CMakeLists.txt b/core/libs/prx/libwebrtc/CMakeLists.txt index b66133cac5..76e65dfa34 100644 --- a/core/libs/prx/libwebrtc/CMakeLists.txt +++ b/core/libs/prx/libwebrtc/CMakeLists.txt @@ -68,6 +68,16 @@ if(BUILD_TESTING) set_tests_properties(guest_webrtc_cpu_features PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_webrtc_rtcp_pli_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/RtcpPli.cpp) + target_include_directories(guest_webrtc_rtcp_pli_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_webrtc_rtcp_pli_tests PRIVATE libwebrtc libc) + configure_windows_unwind(guest_webrtc_rtcp_pli_tests) + add_test(NAME guest_webrtc_rtcp_pli COMMAND guest_webrtc_rtcp_pli_tests) + set_tests_properties(guest_webrtc_rtcp_pli PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_webrtc_rtcp_pli PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_webrtc_opus_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Opus.cpp) target_include_directories(guest_webrtc_opus_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_webrtc_opus_tests PRIVATE libwebrtc libc opus) diff --git a/core/libs/prx/libwebrtc/tests/RtcpPli.cpp b/core/libs/prx/libwebrtc/tests/RtcpPli.cpp new file mode 100644 index 0000000000..56b935ce79 --- /dev/null +++ b/core/libs/prx/libwebrtc/tests/RtcpPli.cpp @@ -0,0 +1,97 @@ +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "libwebrtc rtcp::Pli check failed at line %u\n", location.line()); + std::abort(); +} + +template void RequireInvalidArgument(TAction action, std::source_location location = std::source_location::current()) { + try { + action(); + } catch (const std::invalid_argument&) { + return; + } + Require(false, location); +} + +WebRtcRtcpPli Filled() { + WebRtcRtcpPli pli; + std::memset(&pli, 0xA5, sizeof(pli)); + return pli; +} + +bool Unchanged(const WebRtcRtcpPli& pli) { + WebRtcRtcpPli filled = Filled(); + return std::memcmp(&pli, &filled, sizeof(pli)) == 0; +} + +bool ParsePacket(WebRtcRtcpPli& pli, const std::vector& packet) { + WebRtcRtcpCommonHeader header; + Require(_ZN6webrtc4rtcp12CommonHeader5ParseEPKhm_nid_postfix(&header, packet.data(), packet.size())); + return _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(&pli, &header); +} + +void TestPli() { + std::vector packet {0x81, 206, 0x00, 0x02, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + WebRtcRtcpPli pli = Filled(); + WebRtcRtcpPli filled = Filled(); + Require(ParsePacket(pli, packet)); + Require(pli.senderSsrc == 0x12345678u); + Require(pli.mediaSsrc == 0x9ABCDEF0u); + Require(pli.vtable == filled.vtable); +} + +void TestLongerPayloadReadsTheCommonFeedbackOnly() { + std::vector packet {0x81, 206, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0x55, 0x55, 0x55, 0x55}; + WebRtcRtcpPli pli = Filled(); + Require(ParsePacket(pli, packet)); + Require(pli.senderSsrc == 1); + Require(pli.mediaSsrc == 0xFFFFFFFEu); +} + +void TestShortPayloadLeavesThePacketUntouched() { + std::vector empty {0x81, 206, 0x00, 0x00}; + WebRtcRtcpPli pli = Filled(); + Require(!ParsePacket(pli, empty)); + Require(Unchanged(pli)); + + std::vector padded {0xA1, 206, 0x00, 0x02, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0x01}; + Require(!ParsePacket(pli, padded)); + Require(Unchanged(pli)); + + WebRtcRtcpCommonHeader header {206, 1, 0, 7, nullptr}; + Require(!_ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(&pli, &header)); + Require(Unchanged(pli)); +} + +void TestNullArgumentsThrow() { + std::vector payload(8, 0x11); + WebRtcRtcpCommonHeader header {206, 1, 0, 8, payload.data()}; + WebRtcRtcpPli pli = Filled(); + RequireInvalidArgument([&] { _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(nullptr, &header); }); + RequireInvalidArgument([&] { _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(&pli, nullptr); }); + header.payload = nullptr; + RequireInvalidArgument([&] { _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(&pli, &header); }); + Require(Unchanged(pli)); +} + +} + +int main() { + TestPli(); + TestLongerPayloadReadsTheCommonFeedbackOnly(); + TestShortPayloadLeavesThePacketUntouched(); + TestNullArgumentsThrow(); + return 0; +} From 5812f894488f52d6c3637c57c4167616f166ec8c Mon Sep 17 00:00:00 2001 From: Alvaro Date: Mon, 2 Nov 2026 15:58:06 +0100 Subject: [PATCH 360/612] docs(libwebrtc): record rtcp::Pli::Parse --- docs/dev/debt/libwebrtc.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/dev/debt/libwebrtc.md b/docs/dev/debt/libwebrtc.md index 7d6493f7f9..4dd3ba8813 100644 --- a/docs/dev/debt/libwebrtc.md +++ b/docs/dev/debt/libwebrtc.md @@ -2,7 +2,7 @@ ### Unknown function info -- [347 exports (60 without a known name, bound by NID)](../../../core/libs/prx/libwebrtc/Export.cpp) (libwebrtc) - every function export of `libwebrtc.sprx` from the firmware 13.60 export table that the next section does not implement, names from aerolib.csv only where `tools/nid_names.py` `compute_nid` reproduces the NID. Signatures, parameter layouts and behaviour are unknown; every export throws through `NotImplemented_nid_no_patch` +- [346 exports (60 without a known name, bound by NID)](../../../core/libs/prx/libwebrtc/Export.cpp) (libwebrtc) - every function export of `libwebrtc.sprx` from the firmware 13.60 export table that the next section does not implement, names from aerolib.csv only where `tools/nid_names.py` `compute_nid` reproduces the NID. Signatures, parameter layouts and behaviour are unknown; every export throws through `NotImplemented_nid_no_patch` - [31 data exports](../../../core/libs/prx/libwebrtc/Export.cpp) (libwebrtc) - the module also exports 31 data objects (+qcl2IzxTzg, -ix-knPYqWA, 00mefEErs+Y, 5FM0YOa3NWc, 67nsSkXmJ7E, 7I2xDnqfDV4, 8Dzy4JayJSU, CVCDyAzGrhI, HpbtuVdvO-w, PGIpx6NnFLE, Q+xnDp4KI-s, QXDv9OpZNvM, aOOjLmup+u4, g5USRJNh3bU, hvpHGXjlpbk, iU6+qODNSjU, jpkYoOv34AM, lHLJlq3NEqE, lQJvoyxHov4, nUaORqH85DQ, o6LS+08kOUs, oh8srl3AcNE, oslHzys1vV0, sp5NqvqDdHw, t66Gw3OIpSo, tr8vseK8-ro, uSis0q+bv6k, uTz5pEKL+hM, uxkudCJIYJA, whHm70jlRXw, yW0sV3sCdPA); they are not defined, so imports of them stay unresolved until their layout is known ### Public source implementations @@ -13,6 +13,7 @@ - [rtc::ExpFilter::Reset and Apply](../../../core/libs/prx/libwebrtc/src/ExpFilter.cpp) (libwebrtc) - adapted from [exp_filter.cc](https://webrtc.googlesource.com/src/+/8d78f5de6c27b2c793039989ea381f1428fb0100/rtc_base/numerics/exp_filter.cc), with its WebRTC copyright notice and BSD licence at the top of the file, on the 12-byte `alpha_`, `filtered_` and `max_` layout of `exp_filter.h`. -1 marks both an unset output and no maximum, so an output that lands on exactly -1 restarts the filter on the next sample, as upstream does. The last bit of a result can differ if the console build fuses the multiply-adds. A null filter throws. Closes when: compared with the console. - [webrtc::rtcp::CommonHeader::Parse](../../../core/libs/prx/libwebrtc/src/RtcpCommonHeader.cpp) (libwebrtc) - adapted from [common_header.cc](https://webrtc.googlesource.com/src/+/8d78f5de6c27b2c793039989ea381f1428fb0100/modules/rtp_rtcp/source/rtcp_packet/common_header.cc), with its WebRTC copyright notice and BSD licence at the top of the file, on the 16-byte layout of `common_header.h`: packet type, count or format and padding size at 0 to 2, payload size at 4 and payload pointer at 8. Its checks run in the same order, so a short buffer or a wrong version leaves the header untouched and the later failures leave the fields already parsed. The `RTC_LOG(LS_WARNING)` message each failure logs upstream is not emitted. A null header, or a null buffer of 4 bytes or more, which upstream reads, throws. Closes when: the warnings are logged through libwebrtc's logging and the parse is compared with the console. - [webrtc::RmsLevel::RmsLevel, ~RmsLevel, Analyze and Average](../../../core/libs/prx/libwebrtc/src/RmsLevel.cpp) (libwebrtc) - adapted from [rms_level.cc](https://webrtc.googlesource.com/src/+/8d78f5de6c27b2c793039989ea381f1428fb0100/modules/audio_processing/rms_level.cc), with its copyright notice and BSD licence on top, on the 40-byte layout of `rms_level.h`: `sum_square_` at 0, `sample_count_` at 8, `max_sum_square_` at 16, and `block_size_`'s value and flag at 24 and 32. M127's `absl::optional` there is `std::optional`, as the pinned abseil sets [`ABSL_OPTION_USE_STD_OPTIONAL`](https://chromium.googlesource.com/chromium/src/third_party/+/65ef38d36813d1ad9d4cbf8a18b32ac3691f418b/abseil-cpp/absl/base/options.h#124) to 2, so both pins share the layout. `Analyze` gets the view's pointer and size after `this`. The destructor does nothing, as upstream's is defaulted; a null object throws. The console's `log10` may round a level differently. Not compared with the console. +- [webrtc::rtcp::Pli::Parse](../../../core/libs/prx/libwebrtc/src/RtcpPli.cpp) (libwebrtc) - the `RTC_LOG(LS_WARNING)` upstream writes for a payload shorter than 8 bytes is not emitted, and, as in a release build, the packet type and format that upstream only `RTC_DCHECK`s are not checked. Adapted from [pli.cc](https://webrtc.googlesource.com/src/+/8d78f5de6c27b2c793039989ea381f1428fb0100/modules/rtp_rtcp/source/rtcp_packet/pli.cc) and `ParseCommonFeedback` in [psfb.cc](https://webrtc.googlesource.com/src/+/8d78f5de6c27b2c793039989ea381f1428fb0100/modules/rtp_rtcp/source/rtcp_packet/psfb.cc), with their WebRTC notice and BSD licence at the top of the file, on the 16-byte layout of `psfb.h`: vtable pointer, `sender_ssrc_` at 8, `media_ssrc_` at 12. A short payload leaves the packet untouched. A null packet, header, or payload of 8 bytes or more throws. Closes when: the warning is logged and the parse is compared with the console. - [WebRtcOpus encoder functions](../../../core/libs/prx/libwebrtc/src/Opus.cpp) (libwebrtc) - the 16 `WebRtcOpus_*` encoder exports are adapted from [opus_interface.cc](https://webrtc.googlesource.com/src/+/8d78f5de6c27b2c793039989ea381f1428fb0100/modules/audio_coding/codecs/opus/opus_interface.cc), with its WebRTC copyright notice and BSD licence at the top of the file. They encode through the bundled libopus 1.6.1, not WebRTC's pinned copy, so packet bytes can differ. Signatures match at both revisions; M127 only adds a DTX change behind the `WebRTC-Audio-OpusAvoidNoisePumpingDuringDtx` field trial, which stays off because `InitFieldTrialsFromString` throws. Frames over 60 ms, up to 120 ms, throw: they depend on `WEBRTC_OPUS_SUPPORT_120MS_PTIME`. A null encoder or buffer in `Encode`, which upstream reads, throws. Closes when: the build flag is known and packets are compared with the console. - [WebRtcVad_Create, WebRtcVad_Free, WebRtcVad_Init, WebRtcVad_set_mode and WebRtcVad_Process](../../../core/libs/prx/libwebrtc/Vad.cpp) use the opaque-handle C API and validation rules from [upstream WebRTC](https://github.com/webrtc-mirror/webrtc/blob/3710345e71ad3a7c1f4ed4efef152fd4fc24869a/common_audio/vad/webrtc_vad.c), with the signal-processing core from [libfvad](https://github.com/dpirch/libfvad/tree/532ab666c20d3cfda38bca63abbb0f152706c369). PS5 signatures and classification parity are not measured; differences between the console core and this WebRTC-derived revision remain unverified. Handles are host allocations and expose no guest-visible object layout. - [webrtc::TaskQueueBase::Current and CurrentTaskQueueSetter](../../../core/libs/prx/libwebrtc/src/TaskQueueBase.cpp) (libwebrtc) - adapted from [task_queue_base.cc](https://webrtc.googlesource.com/src/+/8d78f5de6c27b2c793039989ea381f1428fb0100/api/task_queue/task_queue_base.cc), with its WebRTC copyright notice and BSD licence at the top of the file. A thread-local pointer starts null on every thread; the 8-byte setter stores the current queue in `previous_`, makes its queue current, and its destructor makes `previous_` current again. The file's `thread_local` and pthread-key variants behave the same. A null setter, which upstream writes through, throws. Closes when: compared with the console. From 6587e40840b6db42956afbaeefa9871dab7b5377 Mon Sep 17 00:00:00 2001 From: Kikoarl <7354010+Kikoarl@users.noreply.github.com> Date: Wed, 4 Nov 2026 23:59:12 +0100 Subject: [PATCH 361/612] feat(shader): support SDWA destination selectors on v_sin_f32 and v_cos_f32 --- .../agc_driver_sdwa_sin_cos_f32_tests.cmake | 9 + .../tests/execution/SdwaSinCosF32.cpp | 279 ++++++++++++++++++ .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 2 + docs/dev/debt/shader.md | 1 + 4 files changed, 291 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_sdwa_sin_cos_f32_tests.cmake create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/SdwaSinCosF32.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_sdwa_sin_cos_f32_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_sdwa_sin_cos_f32_tests.cmake new file mode 100644 index 0000000000..e2ab08832f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_sdwa_sin_cos_f32_tests.cmake @@ -0,0 +1,9 @@ +if(BUILD_TESTING) + add_test(NAME agc_driver_sdwa_sin_cos_f32_wave64 COMMAND agc_driver_sdwa_sin_cos_f32_tests wave64) + set_tests_properties(agc_driver_sdwa_sin_cos_f32_wave64 PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_sdwa_sin_cos_f32_wave64 PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaSinCosF32.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaSinCosF32.cpp new file mode 100644 index 0000000000..d751313eed --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaSinCosF32.cpp @@ -0,0 +1,279 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "RdnaDecoder/RdnaInstructionDecoder.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 16; +constexpr std::uint32_t ResultsPerCase = 19; +constexpr std::uint32_t Results = Inputs * ResultsPerCase; +constexpr std::uint32_t Garbage = 0xabcd1234u; + +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +struct TestCase { + std::uint32_t opcode; + std::uint32_t input; + std::uint32_t expectedFull; + bool checkPinned; + const char* name; +}; + +constexpr TestCase TestCases[Inputs] = { + {0x35u, 0x00000000u, 0x00000000u, true, "v_sin_f32 0.0"}, + {0x35u, 0x3e800000u, 0x3f800000u, true, "v_sin_f32 0.25"}, + {0x35u, 0xbe800000u, 0xbf800000u, true, "v_sin_f32 -0.25"}, + {0x35u, 0x3f400000u, 0xbf800000u, true, "v_sin_f32 0.75"}, + {0x35u, 0x3fa00000u, 0x3f800000u, true, "v_sin_f32 1.25"}, + {0x35u, 0xbf400000u, 0x3f800000u, true, "v_sin_f32 -0.75"}, + {0x35u, 0xbe000000u, 0u, false, "v_sin_f32 oracle -0.125"}, + {0x35u, 0x3d000000u, 0u, false, "v_sin_f32 oracle 0.03125"}, + {0x36u, 0x00000000u, 0x3f800000u, true, "v_cos_f32 0.0"}, + {0x36u, 0x3f000000u, 0xbf800000u, true, "v_cos_f32 0.5"}, + {0x36u, 0xbf000000u, 0xbf800000u, true, "v_cos_f32 -0.5"}, + {0x36u, 0x3f800000u, 0x3f800000u, true, "v_cos_f32 1.0"}, + {0x36u, 0x3e800000u, 0x00000000u, true, "v_cos_f32 0.25"}, + {0x36u, 0xbe800000u, 0x00000000u, true, "v_cos_f32 -0.25"}, + {0x36u, 0xbe000000u, 0u, false, "v_cos_f32 oracle -0.125"}, + {0x36u, 0x3e600000u, 0u, false, "v_cos_f32 oracle 0.21875"}, +}; + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::uint32_t FieldWidth(std::uint32_t selector) { + return selector < 4u ? 8u : selector < 6u ? 16u : 32u; +} + +std::uint32_t FieldOffset(std::uint32_t selector) { + return selector < 4u ? selector * 8u : selector == 5u ? 16u : 0u; +} + +std::uint32_t InsertResult(std::uint32_t bits, std::uint32_t destination, std::uint32_t unused) { + const auto width = FieldWidth(destination); + const auto offset = FieldOffset(destination); + const auto mask = (1u << width) - 1u; + auto field = bits & mask; + if (unused == 1u && (field & (1u << (width - 1u)))) field |= ~mask; + return (field << offset) | (unused == 2u ? Garbage & ~(mask << offset) : 0u); +} + +std::array BufferDescriptor(const void* data, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (4u << 16u), count, 0x11016facu}; +} + +void TestInvalidSdwaSelectors() { + constexpr std::uint32_t SinSdwa = 0x7e0000f9u | (5u << 17u) | (0x35u << 9u); + constexpr std::uint32_t CosSdwa = 0x7e0000f9u | (5u << 17u) | (0x36u << 9u); + + for (const auto opWord : {SinSdwa, CosSdwa}) { + { + const std::array words{opWord, 4u | (0u << 8u) | (2u << 11u) | (6u << 16u)}; + const auto instruction = ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u); + Require(instruction.family == ShaderRecompiler::RdnaInstructionFamily::VOP1, "instruction must be VOP1"); + Require(instruction.destination.sdwaSel == 0u, "destination sdwaSel must be 0"); + Require(instruction.destination.sdwaDstUnused == 2u, "destination sdwaDstUnused must be 2"); + Require(instruction.destination.explicitSdwaDst, "destination explicitSdwaDst must be true"); + Require(instruction.source0.sdwaSel == 6u, "source0 sdwaSel must be 6"); + Require(!instruction.source0.negate, "source0 negate must be false"); + Require(!instruction.source0.absolute, "source0 absolute must be false"); + Require(!instruction.destination.clamp, "destination clamp must be false"); + Require(instruction.destination.omod == 0u, "destination omod must be 0"); + } + { + const std::array words{opWord, 4u | (4u << 8u) | (1u << 11u) | (1u << 13u) | (1u << 14u) | (6u << 16u) | (1u << 20u) | (1u << 21u)}; + const auto instruction = ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u); + Require(instruction.destination.sdwaSel == 4u, "destination sdwaSel must be 4"); + Require(instruction.destination.sdwaDstUnused == 1u, "destination sdwaDstUnused must be 1"); + Require(instruction.destination.clamp, "destination clamp must be true"); + Require(instruction.destination.omod == 1u, "destination omod must be 1"); + Require(instruction.source0.negate, "source0 negate must be true"); + Require(instruction.source0.absolute, "source0 absolute must be true"); + } + { + const std::array words{opWord, 4u | (7u << 8u) | (6u << 16u)}; + bool threw = false; + try { + ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw, "SDWA destination selector 7 must throw std::invalid_argument"); + } + { + const std::array words{opWord, 4u | (0u << 8u) | (7u << 16u)}; + bool threw = false; + try { + ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw, "SDWA source selector 7 must throw std::invalid_argument"); + } + { + const std::array words{opWord, 4u | (0u << 8u) | (3u << 11u) | (6u << 16u)}; + bool threw = false; + try { + ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw, "SDWA destination unused 3 on byte destination must throw std::invalid_argument"); + } + { + const std::array words{opWord, 4u | (4u << 8u) | (3u << 11u) | (6u << 16u)}; + bool threw = false; + try { + ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw, "SDWA destination unused 3 on word destination must throw std::invalid_argument"); + } + { + const std::array words{opWord, 4u | (6u << 8u) | (3u << 11u) | (6u << 16u)}; + bool threw = false; + try { + ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw, "SDWA destination unused 3 on dword destination must throw std::invalid_argument"); + } + } +} + +void RunExecutionTests(AgcDriver::VulkanDevice& device, std::uint32_t waveSize) { + for (std::uint32_t lane = 0u; lane < Threads; ++lane) { + for (std::uint32_t c = 0u; c < Inputs; ++c) { + Input[lane * Inputs + c] = TestCases[c].input; + } + } + Output.fill(0xdeadbeefu); + + std::vector words{ + 0x160200ffu, Inputs, 0x160400ffu, Results, + }; + + std::uint32_t resultIndex = 0u; + for (std::uint32_t c = 0u; c < Inputs; ++c) { + words.insert(words.end(), {0xe0302000u | (c * 4u), 0x80000401u, 0xbf8c3f70u}); + words.insert(words.end(), {0x7e0002ffu | (5u << 17u), Garbage}); + words.push_back(0x7e000000u | (5u << 17u) | (TestCases[c].opcode << 9u) | 260u); + words.insert(words.end(), {0xe0702000u | (resultIndex * 4u), 0x80010502u}); + ++resultIndex; + + for (std::uint32_t destination = 0u; destination < 6u; ++destination) { + for (std::uint32_t unused = 0u; unused < 3u; ++unused, ++resultIndex) { + words.insert(words.end(), {0x7e0002ffu | (5u << 17u), Garbage}); + const auto word0 = 0x7e0000f9u | (5u << 17u) | (TestCases[c].opcode << 9u); + const auto word1 = 4u | (destination << 8u) | (unused << 11u) | (6u << 16u); + words.insert(words.end(), {word0, word1}); + words.insert(words.end(), {0xe0702000u | (resultIndex * 4u), 0x80010502u}); + } + } + } + words.push_back(0xbf810000u); + + std::vector userData(8u); + const auto inputDesc = BufferDescriptor(Input.data(), static_cast(Input.size())); + const auto outputDesc = BufferDescriptor(Output.data(), static_cast(Output.size())); + std::copy(inputDesc.begin(), inputDesc.end(), userData.begin()); + std::copy(outputDesc.begin(), outputDesc.end(), userData.begin() + 4); + + const std::span code(words); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + + std::uint32_t checkIndex = 0u; + for (std::uint32_t c = 0u; c < Inputs; ++c) { + const auto fullResultIndex = c * ResultsPerCase; + for (std::uint32_t lane = 0u; lane < Threads; ++lane) { + const auto actualFull = Output[lane * Results + checkIndex]; + if (TestCases[c].checkPinned) { + Require(actualFull == TestCases[c].expectedFull, + std::string(TestCases[c].name) + " full result lane " + std::to_string(lane) + + " is " + Hex(actualFull) + ", expected " + Hex(TestCases[c].expectedFull)); + } else if (TestCases[c].opcode == 0x35u) { + Require((actualFull >> 16u) == 0xbf35u || (actualFull >> 16u) == 0x3f35u || (actualFull >> 16u) == 0x3e47u, + std::string(TestCases[c].name) + " full result lane " + std::to_string(lane) + + " is " + Hex(actualFull) + ", expected high word 0xbf35, 0x3f35 or 0x3e47"); + Require((actualFull & 0xffu) != 0u, std::string(TestCases[c].name) + " must produce non-zero low byte"); + Require((actualFull & 0x80u) != 0u, std::string(TestCases[c].name) + " must have bit 7 set for SEXT"); + if ((actualFull >> 16u) == 0x3e47u) { + Require((actualFull & 0x8000u) != 0u, std::string(TestCases[c].name) + " must have bit 15 set for word SEXT"); + } + } else { + Require((actualFull >> 16u) == 0x3f35u || (actualFull >> 16u) == 0x3e47u, + std::string(TestCases[c].name) + " full result lane " + std::to_string(lane) + + " is " + Hex(actualFull) + ", expected high word 0x3f35 or 0x3e47"); + Require((actualFull & 0xffu) != 0u, std::string(TestCases[c].name) + " must produce non-zero low byte"); + Require((actualFull & 0x80u) != 0u, std::string(TestCases[c].name) + " must have bit 7 set for SEXT"); + if ((actualFull >> 16u) == 0x3e47u) { + Require((actualFull & 0x8000u) != 0u, std::string(TestCases[c].name) + " must have bit 15 set for word SEXT"); + } + } + } + ++checkIndex; + for (std::uint32_t destination = 0u; destination < 6u; ++destination) { + for (std::uint32_t unused = 0u; unused < 3u; ++unused, ++checkIndex) { + for (std::uint32_t lane = 0u; lane < Threads; ++lane) { + const auto base = TestCases[c].checkPinned ? TestCases[c].expectedFull : Output[lane * Results + fullResultIndex]; + const auto expected = InsertResult(base, destination, unused); + const auto actual = Output[lane * Results + checkIndex]; + Require(actual == expected, + std::string(TestCases[c].name) + " lane " + std::to_string(lane) + + " wave" + std::to_string(waveSize) + " destination " + + std::to_string(destination) + " unused " + std::to_string(unused) + + " is " + Hex(actual) + ", expected " + Hex(expected)); + } + } + } + } +} + +} + +int main(int argc, char** argv) { + try { + TestInvalidSdwaSelectors(); + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Require(argc == 1 || (argc == 2 && std::string(argv[1]) == "wave64"), "expected no argument or wave64"); + const auto waveSize = argc == 2 ? 64u : 32u; + RunExecutionTests(*device, waveSize); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 02281d8d79..c1ef571320 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -1155,6 +1155,8 @@ constexpr Vop1SdwaRule vop1SdwaRules[] = { {RdnaOpcode::VCvtI32F32, sdwaSelFull(), sdwaSelBytes() | sdwaSelWords(), sdwaSelFull(), false}, {RdnaOpcode::VCvtRpiI32F32, sdwaSelFull(), sdwaSelBytes() | sdwaSelWords(), sdwaSelFull(), false}, {RdnaOpcode::VCvtFlrI32F32, sdwaSelFull(), sdwaSelBytes() | sdwaSelWords(), sdwaSelFull(), false}, + {RdnaOpcode::VSinF32, sdwaSelFull(), sdwaSelBytes() | sdwaSelWords(), sdwaSelFull(), false}, + {RdnaOpcode::VCosF32, sdwaSelFull(), sdwaSelBytes() | sdwaSelWords(), sdwaSelFull(), false}, }; const Vop1SdwaRule* findVop1SdwaRule(RdnaOpcode opcode) { diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index 18db52f750..4985fb57b3 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -230,6 +230,7 @@ - [SDWA integer bit scans and reverse](../../../core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp) - the PS5's gfx1013 is not measured. `v_ffbh_u32`, `v_ffbl_b32`, `v_ffbh_i32` and `v_bfrev_b32` accept all source selects, zero/sign extension and byte/word destinations with pad, sign-extend or preserve. Source extraction precedes the 32-bit operation and destination placement; signed high-bit scan returns -1 for zero and all ones. Sources: [RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) §§6.8, 12.8, 13.3.7 Table 88; LLVM 19.1.0 [VOP1Instructions.td](https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/AMDGPU/VOP1Instructions.td#L347-L351) L1194-L1197 and [SIPeepholeSDWA.cpp](https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/AMDGPU/SIPeepholeSDWA.cpp) destination folding. Wave32/64 execution is checked on lavapipe; console comparison remains open. - [Integer dot clamp](../../../core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp) (`v_dot2_i32_i16`, `v_dot2_u32_u16`, `v_dot4_i32_i8`, `v_dot4_u32_u8`, `v_dot8_i32_i4`, `v_dot8_u32_u4`) (shader recompiler) - with the VOP3P clamp bit set these throw. No source says the PS5's gfx1013 has these instructions: LLVM's [`FeatureISAVersion10_1_3`](https://github.com/llvm/llvm-project/blob/131f7ebb08369c2837665bab7061977e37c5f3ec/llvm/lib/Target/AMDGPU/GCNFeatures.td#L2282-L2285) has no dot feature and Mesa leaves `CHIP_GFX1013` out of `has_accelerated_dot_product`. LLVM selects clamp for a saturating add of the dot2 and dot4 products to src2, with no corresponding generic dot8 saturating-add pattern, and wraps a sum of the two dot2 products that does not fit in 32 bits, where Mesa's `sdot_2x16_iadd_sat` sums in 64 bits and `udot_2x16_uadd_sat` leaves it undefined. Closes when: a clamped integer dot is measured on a PS5. - [SDWA integer-to-f32 destinations](../../../core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp) - the PS5's gfx1013 is not measured. `v_cvt_f32_i32` and `v_cvt_f32_u32` accept byte/word destinations with all source selects and zero/sign extension. The integer is selected and extended, converted once, then the low result byte/word is placed as bits with pad, sign-extend or preserve. Sources: [RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) §12.8 opcodes 5–6, §6.8 and §13.3.7 Table 88; LLVM 19.1.0 [VOP1Instructions.td L288–L289](https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/AMDGPU/VOP1Instructions.td#L288-L289) and [SIPeepholeSDWA.cpp](https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/AMDGPU/SIPeepholeSDWA.cpp) destination folding. Wave32/64 lavapipe readbacks cover exactly representable integers; console comparison and inexact conversion placement remain unverified. +- [SDWA destination selectors on `v_sin_f32`, `v_cos_f32`](../../../core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp) - the PS5's gfx1013 is not measured. `v_sin_f32` and `v_cos_f32` accept byte and word destination selectors (`BYTE_0`..`BYTE_3`, `WORD_0`, `WORD_1`) with unused modes `UNUSED_PAD`, `UNUSED_SEXT` and `UNUSED_PRESERVE` when the source selector is `DWORD`. Sources: [RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) §§6.8, 12.8, 13.3.7 Table 88; LLVM 19.1.0 [VOP1Instructions.td](https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/AMDGPU/VOP1Instructions.td) and [SIPeepholeSDWA.cpp](https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/AMDGPU/SIPeepholeSDWA.cpp). Execution verified in Wave32 and Wave64; console comparison remains unmeasured. - [Unsigned conversion of sign-extended SDWA sources on NVIDIA](../../../core/shader/recompiler/Translation/src/ConvertInstructions.cpp) (shader recompiler) - on an NVIDIA RTX 3060 (driver 610.88, Windows 11), `v_cvt_f32_u32` with an SDWA `SEXT` byte or word source whose top bit is set returns the float of the negative field for any destination: WORD_0 of `0xffffff00` gives -256.0 (`0xc3800000`), not 4294967040.0 (`0x4f7fffff`). The recompiler emits `OpBitFieldSExtract` on a `uint` and then `OpConvertUToF`; the module passes `spirv-val`, lavapipe returns the unsigned result, and converting the stored extended integer in a separate dispatch is correct, so the driver appears to fold the pair into a signed conversion. `v_cvt_f32_i32`, and `v_cvt_f32_u32` without `SEXT`, match. The SDWA destination test uses `SEXT` on `v_cvt_f32_u32` only for fields with the top bit clear - [Flag structurization](../../../core/shader/recompiler/ControlFlow/src/Structurizer.cpp) (shader recompiler) - a graph that every structurizer attempt refuses, irreducible and non-terminating graphs included, is rebuilt with one reach flag per block and one again flag per loop (`structurizeWithFlags`): each block keeps one copy and runs behind a guard on its flag, and each strongly connected component becomes a loop whose single latch repeats it while a back edge set its flag. The output has about 8 times the input's blocks and one route variable per block, and ends in an empty Return that never runs. Only blocks that start the top-level order unconditionally run unguarded, so resource planning also sees the static paths that skip a guarded block and can plan extra descriptor candidates for them. The attempts still run first. Shared merge splitting refuses an attempt once the graph has grown past 16 times its block count at the start of splitting (at least 64 blocks); succeeding attempts grew at most 10.6 times on random graphs of up to 128 blocks, and an attempt that would succeed only beyond the budget leaves the graph to a later attempt or to this construction. Splitting that adds one block per round stays within that budget until its 4 rounds per block run out, so the attempts can still take more than 10 s (9 of 2,000 random graphs of up to 128 blocks, 63 s at most). `APS5_FORCE_STRUCTURIZER_FALLBACK=1` builds every graph this way, for tests - [Shared loop latches](../../../core/shader/recompiler/ControlFlow/src/Structurizer.cpp) (shader recompiler) - a loop whose single latch holds instructions and is reached by one edge gets an empty continue block, so its continue construct is that one block. A latch that several `continue` paths share keeps its instructions in the continue construct, since isolating it would clone them into each path. When such a latch emits more than one SPIR-V block, as a memory or image access does, SPIRV-Cross (MoltenVK) folds the construct into a `for` increment and drops the header's `OpPhi` updates, so the loop never advances on Metal. No title is known to reach this case. From 5f67111fe4615ede92eede2367a13ead2a317447 Mon Sep 17 00:00:00 2001 From: ItsJustChrisE <53130043+ItsJustChrisE@users.noreply.github.com> Date: Wed, 4 Nov 2026 21:16:00 -0600 Subject: [PATCH 362/612] fix(agc): check registered image views in one registry lookup Co-Authored-By: Claude Opus 5.5 --- .../include/Driver/Dispatch/DispatchCache.hpp | 27 +++---- .../Graphics/src/ShaderResources.cpp | 28 +++---- .../libSceAgcDriver/tests/ShaderMemory.cpp | 81 +++++++++++++++++++ .../tests/cmake/agc_shader_memory_tests.cmake | 5 ++ .../prx/libc/include/GuestAllocations.hpp | 1 + core/libs/prx/libc/src/GuestAllocations.cpp | 33 +++++--- 6 files changed, 130 insertions(+), 45 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Dispatch/DispatchCache.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Dispatch/DispatchCache.hpp index c7cf99a0a1..47863c8276 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Dispatch/DispatchCache.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Dispatch/DispatchCache.hpp @@ -26,23 +26,18 @@ namespace AgcDriver::DriverDetail { inline bool CaptureRegisteredViewsCurrent(std::span views) { if (views.empty()) return true; - for (std::uint32_t attempt = 0; attempt < 2u; ++attempt) { - const auto before = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); - bool current = true; - for (const auto& view : views) { - const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(static_cast(view.address), false); - if (view.hasRange != (range != nullptr)) { - current = false; - break; - } - if (range != nullptr && (range->address != view.rangeAddress || range->bytes != view.rangeBytes || range->allocationAddress != view.allocationAddress || range->allocationBytes != view.allocationBytes || range->backingSerial != view.backingSerial || range->readable != view.readable || range->writable != view.writable || range->gpu != view.gpu)) { - current = false; - break; - } - } - if (before == GuestAllocations::GuestAllocationsGeneration_nid_postfix()) return current; + std::vector addresses; + addresses.reserve(views.size()); + for (const auto& view : views) addresses.push_back(static_cast(view.address)); + std::vector> ranges(views.size()); + GuestAllocations::GuestAllocationsLookupEach_nid_postfix(addresses.data(), addresses.size(), ranges.data()); + for (std::size_t index = 0; index < views.size(); ++index) { + const auto& view = views[index]; + const auto& range = ranges[index]; + if (view.hasRange != (range != nullptr)) return false; + if (range != nullptr && (range->address != view.rangeAddress || range->bytes != view.rangeBytes || range->allocationAddress != view.allocationAddress || range->allocationBytes != view.allocationBytes || range->backingSerial != view.backingSerial || range->readable != view.readable || range->writable != view.writable || range->gpu != view.gpu)) return false; } - return false; + return true; } inline constexpr std::size_t MaxDispatchVariants = 8; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index ea0ca7232b..63147be052 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -58,24 +58,18 @@ bool overlap(std::uint64_t first, std::size_t firstSize, std::uint64_t second, s bool registeredImageViewsCurrent(std::span views) { if (views.empty()) return true; - for (std::uint32_t attempt = 0; attempt < 2u; ++attempt) { - const auto before = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); - bool current = true; - for (const auto& view : views) { - const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(static_cast(view.address), false); - if (view.hasRange != (range != nullptr)) { - current = false; - break; - } - if (range != nullptr && (range->address != view.rangeAddress || range->bytes != view.rangeBytes || range->allocationAddress != view.allocationAddress || range->allocationBytes != view.allocationBytes || range->backingSerial != view.backingSerial || range->readable != view.readable || range->writable != view.writable || range->gpu != view.gpu)) { - current = false; - break; - } - } - const auto after = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); - if (before == after) return current; + std::vector addresses; + addresses.reserve(views.size()); + for (const auto& view : views) addresses.push_back(static_cast(view.address)); + std::vector> ranges(views.size()); + GuestAllocations::GuestAllocationsLookupEach_nid_postfix(addresses.data(), addresses.size(), ranges.data()); + for (std::size_t index = 0; index < views.size(); ++index) { + const auto& view = views[index]; + const auto& range = ranges[index]; + if (view.hasRange != (range != nullptr)) return false; + if (range != nullptr && (range->address != view.rangeAddress || range->bytes != view.rangeBytes || range->allocationAddress != view.allocationAddress || range->allocationBytes != view.allocationBytes || range->backingSerial != view.backingSerial || range->readable != view.readable || range->writable != view.writable || range->gpu != view.gpu)) return false; } - return false; + return true; } VkComponentSwizzle ComponentSwizzleFor(std::uint8_t dstSel) { diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index dd073c28ec..afd7e8a57a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -2454,6 +2454,82 @@ void verifyAllocationChurnSnapshot() { } } +void verifyRegisteredViewsUnderUnrelatedChurn() { + using namespace ShaderRecompiler; + constexpr std::size_t RangeBytes = 65536u; + constexpr std::uint32_t Checks = 20000u; + alignas(4096) static std::array texels{}; + alignas(4096) static std::array unrelated{}; + { + GuestAllocations::Mutation mutation; + mutation.Add(texels.data(), texels.size(), true, false, true); + } + const auto view = [] { + const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(reinterpret_cast(texels.data()), false); + require(range != nullptr, "registered view churn: the image range was not registered"); + RegisteredImageView captured; + captured.address = reinterpret_cast(texels.data()) + 256u; + captured.hasRange = true; + captured.rangeAddress = range->address; + captured.rangeBytes = range->bytes; + captured.allocationAddress = range->allocationAddress; + captured.allocationBytes = range->allocationBytes; + captured.backingSerial = range->backingSerial; + captured.readable = range->readable; + captured.writable = range->writable; + captured.gpu = range->gpu; + return captured; + }(); + std::array views{view, view, view}; + views[1].address = view.address + 4096u; + views[2].address = view.address + 8192u; + std::atomic stop{false}; + std::atomic mutations{0}; + std::thread churn([&] { + while (!stop.load(std::memory_order_acquire)) { + { + GuestAllocations::Mutation mutation; + mutation.Add(unrelated.data(), unrelated.size(), true, true, true); + } + { + GuestAllocations::Mutation mutation; + mutation.Remove(unrelated.data()); + } + mutations.fetch_add(2u, std::memory_order_relaxed); + } + }); + std::uint32_t rejected = 0u; + const auto generationBefore = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + while (mutations.load(std::memory_order_relaxed) < 64u) std::this_thread::yield(); + for (std::uint32_t check = 0; check < Checks; ++check) { + if (!AgcDriver::DriverDetail::CaptureRegisteredViewsCurrent(views)) ++rejected; + } + stop.store(true, std::memory_order_release); + churn.join(); + const auto generationAfter = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + require(generationAfter - generationBefore >= 64u, "registered view churn: the unrelated mappings did not move the allocation generation"); + const auto rejectedMessage = "registered view churn: " + std::to_string(rejected) + " of " + std::to_string(Checks) + " checks rejected views whose own range never changed while only an unrelated allocation moved"; + require(rejected == 0u, rejectedMessage.c_str()); + { + GuestAllocations::Mutation mutation; + mutation.Remove(texels.data()); + } + require(!AgcDriver::DriverDetail::CaptureRegisteredViewsCurrent(views), "registered view churn: views were accepted after their range was removed"); + { + GuestAllocations::Mutation mutation; + mutation.Add(texels.data(), texels.size(), true, false, true); + } + require(!AgcDriver::DriverDetail::CaptureRegisteredViewsCurrent(views), "registered view churn: views were accepted after their range was mapped again with a new backing"); + views[0].backingSerial = GuestAllocations::GuestAllocationsLookup_nid_postfix(reinterpret_cast(texels.data()), false)->backingSerial; + require(!AgcDriver::DriverDetail::CaptureRegisteredViewsCurrent(views), "registered view churn: views were accepted while one of them still names the old backing"); + views[1].backingSerial = views[2].backingSerial = views[0].backingSerial; + require(AgcDriver::DriverDetail::CaptureRegisteredViewsCurrent(views), "registered view churn: views matching the new backing were rejected"); + { + GuestAllocations::Mutation mutation; + mutation.Remove(texels.data()); + } +} + void verifySnapshotAllocationMoves() { using namespace ShaderRecompiler; constexpr std::size_t RangeBytes = 65536u; @@ -11575,6 +11651,11 @@ int main(int argc, char** argv) { std::cout << "Image table keys, maps, records and registration passed\n"; return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--registered-view-churn") { + verifyRegisteredViewsUnderUnrelatedChurn(); + std::cout << "Registered image views stay current while unrelated allocations change\n"; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--nested-snapshot-retry") { verifyNestedSnapshotRetry(); std::cout << "Nested snapshot retry preserves parent and child coverage\n"; diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_shader_memory_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_shader_memory_tests.cmake index 4460a3c25a..1e66e009a0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_shader_memory_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_shader_memory_tests.cmake @@ -30,6 +30,11 @@ if(BUILD_TESTING) if(WIN32) set_tests_properties(agc_shader_descriptor_phis PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_shader_registered_view_churn COMMAND agc_shader_memory_tests --registered-view-churn) + set_tests_properties(agc_shader_registered_view_churn PROPERTIES TIMEOUT 60) + if(WIN32) + set_tests_properties(agc_shader_registered_view_churn PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() add_test(NAME agc_shader_nested_snapshot_retry COMMAND agc_shader_memory_tests --nested-snapshot-retry) set_tests_properties(agc_shader_nested_snapshot_retry PROPERTIES TIMEOUT 20) if(WIN32) diff --git a/core/libs/prx/libc/include/GuestAllocations.hpp b/core/libs/prx/libc/include/GuestAllocations.hpp index 4ea19487df..cb6931bdcb 100644 --- a/core/libs/prx/libc/include/GuestAllocations.hpp +++ b/core/libs/prx/libc/include/GuestAllocations.hpp @@ -75,6 +75,7 @@ Lease GuestAllocationsAcquireMapped_nid_no_patch(const Mapped& mapped); Lease GuestAllocationsAcquireAll_nid_postfix(); Lease GuestAllocationsAcquireRegistered_nid_postfix(); std::shared_ptr GuestAllocationsLookup_nid_postfix(std::uintptr_t address, bool next); +void GuestAllocationsLookupEach_nid_postfix(const std::uintptr_t* addresses, std::size_t count, std::shared_ptr* ranges); // Changes whenever a mutation ends, so callers can cache facts about guest mappings between changes. std::uint64_t GuestAllocationsGeneration_nid_postfix(); // Precise invalidation for such caches: the callback runs, after the generation changed, for every diff --git a/core/libs/prx/libc/src/GuestAllocations.cpp b/core/libs/prx/libc/src/GuestAllocations.cpp index cb518ad307..a7fb66830e 100644 --- a/core/libs/prx/libc/src/GuestAllocations.cpp +++ b/core/libs/prx/libc/src/GuestAllocations.cpp @@ -71,6 +71,21 @@ void renewBacking(std::uintptr_t address, std::size_t bytes) { } } +std::shared_ptr lookupLocked(std::uintptr_t address, bool next) { + const auto& ranges = registry().ranges; + auto it = ranges.upper_bound(address); + if (it != ranges.begin()) { + const auto& previous = std::prev(it)->second; + if (previous->bytes != 0 && address < previous->address + previous->bytes) return previous; + } + if (next) { + for (; it != ranges.end(); ++it) { + if (it->second->bytes != 0) return it->second; + } + } + return nullptr; +} + bool isRetiring(const Range& range) { for (const auto& [begin, end] : registry().retiring) { if (range.address < end && begin < range.address + range.bytes) return true; @@ -837,20 +852,14 @@ Lease GuestAllocationsAcquireRegistered_nid_postfix() { return Lease(std::move(ranges)); } +void GuestAllocationsLookupEach_nid_postfix(const std::uintptr_t* addresses, std::size_t count, std::shared_ptr* ranges) { + std::lock_guard lock(registry().mutex); + for (std::size_t index = 0; index < count; ++index) ranges[index] = lookupLocked(addresses[index], false); +} + std::shared_ptr GuestAllocationsLookup_nid_postfix(std::uintptr_t address, bool next) { std::lock_guard lock(registry().mutex); - const auto& ranges = registry().ranges; - auto it = ranges.upper_bound(address); - if (it != ranges.begin()) { - const auto& previous = std::prev(it)->second; - if (previous->bytes != 0 && address < previous->address + previous->bytes) return previous; - } - if (next) { - for (; it != ranges.end(); ++it) { - if (it->second->bytes != 0) return it->second; - } - } - return nullptr; + return lookupLocked(address, next); } Lease GuestAllocationsAcquireAll_nid_postfix() { From 50c39aa4a08e96455e77c8b1d93460e8e8f41217 Mon Sep 17 00:00:00 2001 From: Kikoarl <7354010+Kikoarl@users.noreply.github.com> Date: Mon, 2 Nov 2026 23:21:10 +0100 Subject: [PATCH 363/612] feat(shader): translate buffer_atomic_csub without glc --- ...buffer_atomic_csub_translation_tests.cmake | 10 ++++ .../tests/execution/BufferAtomicCsub.cpp | 14 ++--- .../tests/BufferAtomicCsubTranslation.cpp | 60 +++++++++++++++++++ .../MemoryInstructionDispatch.cpp | 3 - docs/dev/debt/shader.md | 1 + 5 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/cmake/buffer_atomic_csub_translation_tests.cmake create mode 100644 core/libs/tests/BufferAtomicCsubTranslation.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/buffer_atomic_csub_translation_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/buffer_atomic_csub_translation_tests.cmake new file mode 100644 index 0000000000..605c6b5870 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/buffer_atomic_csub_translation_tests.cmake @@ -0,0 +1,10 @@ +if(BUILD_TESTING) + add_test_executable(buffer_atomic_csub_translation_tests ${LIBS_INCLUDE_DIR}/tests/BufferAtomicCsubTranslation.cpp) + target_link_libraries(buffer_atomic_csub_translation_tests PRIVATE shader_recompiler_agc_driver libc) + configure_windows_unwind(buffer_atomic_csub_translation_tests) + add_test(NAME buffer_atomic_csub_translation COMMAND buffer_atomic_csub_translation_tests) + if(WIN32) + set_tests_properties(buffer_atomic_csub_translation PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicCsub.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicCsub.cpp index 0b150b2da8..647217c7c2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicCsub.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicCsub.cpp @@ -168,16 +168,10 @@ void Check(const Workgroup& workgroup) { } } -void CheckNoReturnRejected(const AgcDriver::VulkanDevice& device) { +void CheckNoReturnAccepted(const AgcDriver::VulkanDevice& device) { const auto userData = UserData(); - const std::string reason = "buffer_atomic_csub without glc is not supported"; - std::string failure; - try { - static_cast(Recompile(device, NoReturnCode, Workgroups[0], userData)); - } catch (const std::exception& error) { - failure = error.what(); - } - Require(failure.find(reason) != std::string::npos, "buffer atomic csub: expected '" + reason + "', got '" + failure + "'"); + const auto recompiled = Recompile(device, NoReturnCode, Workgroups[0], userData); + Require(!recompiled.spirv.empty(), "buffer atomic csub: expected valid SPIR-V for instruction without glc"); } } @@ -190,7 +184,7 @@ int main() { Run(*device, workgroup); Check(workgroup); } - CheckNoReturnRejected(*device); + CheckNoReturnAccepted(*device); std::puts("buffer atomic csub tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/tests/BufferAtomicCsubTranslation.cpp b/core/libs/tests/BufferAtomicCsubTranslation.cpp new file mode 100644 index 0000000000..5530233d07 --- /dev/null +++ b/core/libs/tests/BufferAtomicCsubTranslation.cpp @@ -0,0 +1,60 @@ +#include "Translation/TranslationContext.hpp" +#include "RdnaDecoder/RdnaMemoryOpDecoder.hpp" +#include +#include +#include +#include + +using namespace ShaderRecompiler; + +namespace { + +void Require(bool value, const std::string& message) { + if (!value) throw std::runtime_error("buffer atomic csub: " + message); +} + +void CheckTranslation(bool glc) { + constexpr std::uint32_t destination = 5u; + constexpr std::uint32_t opcode = 0x34u; + const std::array code{ + (0x38u << 26u) | (opcode << 18u) | (static_cast(glc) << 14u) | (1u << 12u), + (10u << 24u) | (4u << 16u) | (destination << 8u) | 2u + }; + const RdnaInstruction instruction = DecodeRdnaMubuf(0u, code, 0u); + Require(instruction.op == RdnaOpcode::BufferAtomicCsub, "instruction does not decode to BufferAtomicCsub"); + Require(instruction.glc == glc, "instruction glc flag mismatch"); + Require(instruction.destination.reg == destination, "destination register index mismatch"); + + IrProgram program; + auto& block = program.CreateBlock(); + program.SetEntryBlock(block); + TranslationContext context(program, block, 256); + context.TranslateInstruction(instruction); + + const IrValue* atomicOperation = nullptr; + const IrValue* writeOperation = nullptr; + + for (const auto* value : block.Instructions()) { + if (value->Opcode() == IrOpcode::BufferAtomicUSubSat32) { + atomicOperation = value; + } + if (value->Opcode() == IrOpcode::SetVectorRegister && value->Argument(0)->Register().index == destination) { + writeOperation = value; + } + } + + Require(atomicOperation != nullptr, "BufferAtomicUSubSat32 was not emitted"); + if (glc) { + Require(writeOperation != nullptr, "destination register was not written when glc is set"); + } else { + Require(writeOperation == nullptr, "destination register was written when glc is clear"); + } +} + +} + +int main() { + CheckTranslation(false); + CheckTranslation(true); + return 0; +} diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp index b0680c202a..4cb3a6a5cd 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp @@ -145,9 +145,6 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { case RdnaOpcode::BufferAtomicSub: return bufferAtomic(inst, IrOpcode::BufferAtomicISub32); case RdnaOpcode::BufferAtomicCsub: - if (!inst.glc) { - throw std::runtime_error("buffer_atomic_csub without glc is not supported, at pc " + std::to_string(inst.programCounter)); - } return bufferAtomic(inst, IrOpcode::BufferAtomicUSubSat32); case RdnaOpcode::BufferAtomicSmin: return bufferAtomic(inst, IrOpcode::BufferAtomicSMin32); diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index ebd15dd050..68b048b0a5 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -34,6 +34,7 @@ ### Functional +- [`buffer_atomic_csub` without glc](../../../core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp) (shader recompiler) - `buffer_atomic_csub` without `glc` is translated as a non-returning atomic subtract; console hardware behaviour is unmeasured on a PS5 (GFX10.3), where LLVM defines csub atomics only in their returning form (`FeatureAtomicCSubNoRtnInsts` disabled in llvm `AMDGPU.td`) and the assembler requires `glc`. Non-returning behaviour is assumed, and no compiler target is known to emit it. Closes when: measured on a PS5 or an unhandled title shader is captured. - [Buffer descriptor type](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) - a buffer V# whose TYPE (word 3 bits 30-31) is not 0 throws `buffer descriptor uses an unsupported type`, except for vector stores through TYPE 2 or 3. Measured on a PS5 (CFI-1115B, firmware 12.20): through TYPE 2 or 3 with FORMAT 0 or a valid format, `buffer_store_dword` and `buffer_store_format_x` wrote nothing and did not fault, also with an unmapped base; loads and GLC atomics left the destination VGPR unchanged, which a null V# can't reproduce, so they throw. TYPE 0 with FORMAT 0 returned 0 for loads and wrote nothing. TYPE 1 faulted the GPU for every access and throws. Unmeasured and still throwing for TYPE 2 and 3: `tbuffer_*`, scalar loads, FORMAT above 77, GCN shaders and V#s loaded at run time; multi-dword and multi-component stores through TYPE 2 and 3 are unmeasured and throw. Closes when: those cases are measured on a PS5. - [Image format 131](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) (shader recompiler) - T# FORMAT 131 (`Format10_10_10_2Float` in `IrBufferFormat`) is refused: a sampled or non-atomic storage access fails with the `NumericClass` reason, since the [format table](../../../core/shader/recompiler/RdnaDecoder/src/RdnaDescriptorFormat.cpp) has no entry for 131, and the driver's [texture format table](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureFormat.cpp) refuses it as `unsupported guest texture format`. No Vulkan format has its layout, measured with `image_load` on RDNA2 (gfx1036, [hardware oracle](../HW_ORACLE.md)), not on the PS5's gfx1013: R, G and B are bits 0-9, 10-19 and 20-29, each an unsigned float with a 4-bit exponent (bias 7) over a 6-bit mantissa, denormal at exponent 0 and finite at exponent 15 (0x3ff reads 508); A is bits 30-31 as UNORM. - [Pixel interpolators past `SPI_PS_IN_CONTROL.NUM_INTERP`](../../../core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp) (shader recompiler) - when the IR reads an attribute at or past the guest interpolator count, the missing `SPI_PS_INPUT_CNTL` slots are filled as DEFAULT (`OFFSET` 0x20, constant 0/1) before info collection, and SPIR-V emission of `GetInterpolationParameter` for a missing binding returns the same defaults instead of throwing, matching Mesa's treatment of a PS input with no VS output. Component and mode bounds still throw. A retail title prepares a pixel shader with `NUM_INTERP` 0 while it still issues `v_interp` on attribute 0 (`undefined attribute` after the collector fill alone). Console behaviour for that pairing is not measured. From abb2644c052338c36e91860d4112d345e880e98b Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Thu, 5 Nov 2026 18:51:01 +1300 Subject: [PATCH 364/612] docs(libSceMsgDialog): sceMsgDialogOpen returns busy only while a message dialog runs --- docs/dev/debt/libSceMsgDialog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceMsgDialog.md b/docs/dev/debt/libSceMsgDialog.md index 9ecd857a71..09a30475ad 100644 --- a/docs/dev/debt/libSceMsgDialog.md +++ b/docs/dev/debt/libSceMsgDialog.md @@ -2,7 +2,7 @@ ### Silent stubs -- [libSceMsgDialog](../../../core/libs/prx/libSceMsgDialog/Export.cpp) - only a user message dialog with button type 0, 1, 3 or 4 shows UI, and only while the video output has a window to draw it. A progress bar, a system message, a user message of any other button type, a dialog opened with no window, one opened while another dialog is shown and one that no flip drew within three seconds show none: `sceMsgDialogOpen` opens them without `sceMsgDialogInitialize` as well, except that one opened while another is shown returns busy; a progress bar finishes after 30 seconds or on `sceMsgDialogClose`, a system message, another button type or a dialog with no window finishes on the second `sceMsgDialogGetStatus` or `sceMsgDialogUpdateStatus` call, and a shown one that no flip drew finishes at the first status call after three seconds; the first 160 characters of a user message are only logged. +- [libSceMsgDialog](../../../core/libs/prx/libSceMsgDialog/Export.cpp) - only a user message dialog with button type 0, 1, 3 or 4 shows UI, and only while the video output has a window to draw it. A progress bar, a system message, a user message of any other button type, a dialog opened with no window, one opened while another dialog is shown and one that no flip drew within three seconds show none: `sceMsgDialogOpen` opens them without `sceMsgDialogInitialize` as well, and returns busy only while a message dialog is already running; a progress bar finishes after 30 seconds or on `sceMsgDialogClose`, a system message, another button type or a dialog with no window finishes on the second `sceMsgDialogGetStatus` or `sceMsgDialogUpdateStatus` call, and a shown one that no flip drew finishes at the first status call after three seconds; the first 160 characters of a user message are only logged. - [libSceMsgDialog](../../../core/libs/prx/libSceMsgDialog/Export.cpp) - the button a player picks, or that a dialog without UI picks (2 for `YES_NO_FOCUS_NO`, otherwise 1), is kept but not returned, as `sceMsgDialogGetResult` is exported by libSceMsgDialog.native. - [libSceMsgDialog](../../../core/libs/prx/libSceMsgDialog/Export.cpp) - a progress bar dialog runs until `sceMsgDialogClose` or for 30 seconds, and `sceMsgDialogProgressBarInc`, `sceMsgDialogProgressBarSetMsg` and `sceMsgDialogProgressBarSetValue` ignore their arguments From d7454c493042531623a1dcf6641fb9e88b26fc13 Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Thu, 5 Nov 2026 18:54:43 +1300 Subject: [PATCH 365/612] test(relinker): pin the file-backed bounds error for an oversized FDE --- core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp b/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp index d536e627e4..e4806e0882 100644 --- a/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp +++ b/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp @@ -399,7 +399,9 @@ int main() { Io::WriteU32(fdeOverflowBytes, 96, 1000); bool fdeOverflowRejected = false; try { Relinker::UnusedNidFilter::ReadExceptionFunctions(fdeOverflowBytes, {augLoad, augEh}, {}, {}); } - catch (const std::exception&) { fdeOverflowRejected = true; } + catch (const Domain::RelinkerException& error) { + fdeOverflowRejected = std::string(error.what()).find("unwind range is not file-backed") != std::string::npos; + } Require(fdeOverflowRejected); std::cout << "35 unwind record cases passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } From ec49a4f03fb635775909e285a408243dcebd4339 Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Thu, 5 Nov 2026 18:40:23 +1300 Subject: [PATCH 366/612] fix(libSceNKWebKitRequirements): match SQLite mode=memory only as a URI parameter Apply review request from 2026-11-05: a path that contains mode=memory no longer counts as an in-memory database, so such a URI cannot create a file on the host. --- .../libSceNKWebKitRequirements/src/Sqlite.cpp | 41 +++++++++++-- core/libs/tests/GuestNKWebKitSqlite.cpp | 60 +++++++++++++++++++ docs/dev/debt/libSceNKWebKitRequirements.md | 3 +- 3 files changed, 98 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp b/core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp index 6af76c2bef..48f7a629ad 100644 --- a/core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp +++ b/core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace { @@ -56,7 +57,9 @@ int CallBusy(void* hooks, int count) { int CallAuthorizer(void* hooks, int action, const char* first, const char* second, const char* schema, const char* trigger) { + if (action == SQLITE_ATTACH && first != nullptr && *first != '\0') return SQLITE_DENY; auto* guest = static_cast(hooks); + if (guest->authorizer == nullptr) return SQLITE_OK; return guest->authorizer(guest->authorizerArgument, action, first, second, schema, trigger); } @@ -286,19 +289,47 @@ sqlite3_stmt* APS5_VABI sqlite3_next_stmt_nid_postfix(sqlite3* db, sqlite3_stmt* return sqlite3_next_stmt(db, statement); } +static bool MemoryUri(const char* filename) { + const char* path = filename + 5; + if (std::strchr(path, '%') != nullptr) return false; + const char* end = path + std::strcspn(path, "?#"); + if (std::string_view(path, static_cast(end - path)) == ":memory:") return true; + if (*end != '?') return false; + bool memory = false; + for (const char* parameter = end + 1; *parameter != '\0' && *parameter != '#';) { + const char* stop = parameter + std::strcspn(parameter, "&#"); + const std::string_view text(parameter, static_cast(stop - parameter)); + if (text.substr(0, 5) == "mode=") memory = text.substr(5) == "memory"; + parameter = *stop == '&' ? stop + 1 : stop; + } + return memory; +} + +static int GuardAttach(int result, sqlite3** db) { + if (result != SQLITE_OK || db == nullptr || *db == nullptr) return result; + sqlite3_mutex_enter(sqlite3_db_mutex(*db)); + GuestHooks* hooks = Hooks(*db); + const int installed = hooks ? sqlite3_set_authorizer(*db, CallAuthorizer, hooks) : SQLITE_NOMEM; + sqlite3_mutex_leave(sqlite3_db_mutex(*db)); + if (installed == SQLITE_OK) return SQLITE_OK; + sqlite3_close(*db); + *db = nullptr; + return installed; +} + int APS5_VABI sqlite3_open_v2_nid_postfix(const char* filename, sqlite3** db, int flags, const char* vfs) { static_cast(vfs); const bool uri = filename && (flags & SQLITE_OPEN_URI) && std::strncmp(filename, "file:", 5) == 0; - const bool memory = !filename || !*filename || std::strcmp(filename, ":memory:") == 0 || (flags & SQLITE_OPEN_MEMORY) || - (uri && (std::strncmp(filename, "file::memory:", 13) == 0 || std::strstr(filename, "mode=memory"))); + const bool memory = uri ? MemoryUri(filename) : + (!filename || !*filename || std::strcmp(filename, ":memory:") == 0 || (flags & SQLITE_OPEN_MEMORY)); if (memory) { - return sqlite3_open_v2(filename, db, flags, nullptr); + return GuardAttach(sqlite3_open_v2(filename, db, flags, nullptr), db); } if (uri) { NotImplemented_nid_no_patch("sqlite3_open_v2 file URI"); } const std::filesystem::path host = ResolvePath_nid_no_patch(filename); - const int result = sqlite3_open_v2(reinterpret_cast(host.u8string().c_str()), db, flags, nullptr); + const int result = GuardAttach(sqlite3_open_v2(reinterpret_cast(host.u8string().c_str()), db, flags, nullptr), db); if (result == SQLITE_OK && (flags & SQLITE_OPEN_READWRITE)) { for (const char* suffix : {"", "-wal", "-shm", "-journal"}) { std::filesystem::path file = host; @@ -324,7 +355,7 @@ void APS5_VABI sqlite3_result_error_nid_postfix(sqlite3_context* context, const int APS5_VABI sqlite3_set_authorizer_nid_postfix(sqlite3* db, NKWebKit::GuestSqliteAuthorizer authorizer, void* argument) { - if (!db || !authorizer) { + if (!db) { return sqlite3_set_authorizer(db, nullptr, nullptr); } sqlite3_mutex_enter(sqlite3_db_mutex(db)); diff --git a/core/libs/tests/GuestNKWebKitSqlite.cpp b/core/libs/tests/GuestNKWebKitSqlite.cpp index c276a59f33..bfe89e0b6a 100644 --- a/core/libs/tests/GuestNKWebKitSqlite.cpp +++ b/core/libs/tests/GuestNKWebKitSqlite.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,7 @@ constexpr int SQLITE_CONSTRAINT_UNIQUE = 2067; constexpr int SQLITE_OPEN_READWRITE = 0x2; constexpr int SQLITE_OPEN_CREATE = 0x4; constexpr int SQLITE_OPEN_URI = 0x40; +constexpr int SQLITE_OPEN_MEMORY = 0x80; constexpr int SQLITE_UTF8 = 1; constexpr int SQLITE_UTF16LE = 2; constexpr int SQLITE_INTEGER = 1; @@ -103,6 +105,10 @@ int APS5_VABI DenyDelete(void* argument, int action, const char*, const char*, c return action == SQLITE_DELETE ? SQLITE_DENY : SQLITE_OK; } +int APS5_VABI AllowAll(void*, int, const char*, const char*, const char*, const char*) { + return SQLITE_OK; +} + int APS5_VABI GiveUp(void* argument, int count) { Require(*static_cast(argument) == 0xB5, "busy argument"); Require(count == busyCalls, "busy count"); @@ -352,6 +358,60 @@ int main() { const auto directory = std::filesystem::temp_directory_path() / ("nk_sqlite_" + std::to_string(random())); std::filesystem::create_directories(directory); std::filesystem::current_path(directory); + constexpr int uriFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI; + for (const char* name : {"file:mode=memory.db?mode=rwc", "file:mode=memory.db", "file::memory:x", "file:later.db?mode=memory&mode=rwc", "file:encoded.db?mode=memory&mod%65=rwc", "file:m1.db?mode=rwc", "file:plain.db"}) { + for (const int flags : {uriFlags, uriFlags | SQLITE_OPEN_MEMORY}) { + sqlite3* host = nullptr; + try { + sqlite3_open_v2_nid_postfix(name, &host, flags, nullptr); + } catch (const std::exception&) { + } + if (host != nullptr) sqlite3_close_nid_postfix(host); + Require(std::filesystem::is_empty(directory), "a URI that only mentions mode=memory in its path creates no host file"); + } + } + for (const char* name : {"file:one?mode=memory", "file:two?cache=shared&mode=memory", "file::memory:?cache=private", "file:three?mode=rwc&mode=memory"}) { + sqlite3* inMemory = nullptr; + Require(sqlite3_open_v2_nid_postfix(name, &inMemory, uriFlags, nullptr) == SQLITE_OK, "memory URI parameter"); + Execute(inMemory, "CREATE TABLE t(v)"); + Require(sqlite3_close_nid_postfix(inMemory) == SQLITE_OK, "close memory URI parameter"); + Require(std::filesystem::is_empty(directory), "a memory URI creates no host file"); + } + const auto refused = [](sqlite3* connection, const char* sql) { + sqlite3_stmt* statement = nullptr; + const char* tail = nullptr; + int result = sqlite3_prepare_v2_nid_postfix(connection, sql, -1, &statement, &tail); + if (result == SQLITE_OK) { + do { + result = sqlite3_step_nid_postfix(statement); + } while (result == SQLITE_ROW); + } + if (statement != nullptr) sqlite3_finalize_nid_postfix(statement); + return result != SQLITE_OK && result != SQLITE_DONE; + }; + sqlite3* guard = Open(":memory:"); + Require(refused(guard, "ATTACH DATABASE 'attached.db' AS other"), "ATTACH is refused on an in-memory connection"); + Require(refused(guard, "VACUUM INTO 'vacuumed.db'"), "VACUUM INTO is refused on an in-memory connection"); + Require(std::filesystem::is_empty(directory), "ATTACH and VACUUM INTO create no host file"); + Execute(guard, "VACUUM"); + Require(sqlite3_close_nid_postfix(guard) == SQLITE_OK, "close in-memory guard"); + sqlite3* limited = nullptr; + Require(sqlite3_open_v2_nid_postfix("limited.db", &limited, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr) == SQLITE_OK, "open limited.db"); + Require(refused(limited, "ATTACH DATABASE 'attached.db' AS other"), "ATTACH is refused on a file connection"); + Require(refused(limited, "VACUUM INTO 'vacuumed.db'"), "VACUUM INTO is refused on a file connection"); + Require(!std::filesystem::exists(directory / "attached.db") && !std::filesystem::exists(directory / "vacuumed.db"), "ATTACH and VACUUM INTO create no host file from a file connection"); + Execute(limited, "VACUUM"); + Execute(limited, "ATTACH DATABASE '' AS scratch"); + Execute(limited, "DETACH DATABASE scratch"); + Require(sqlite3_set_authorizer_nid_postfix(limited, AllowAll, nullptr) == SQLITE_OK, "set a permissive guest authorizer"); + Require(refused(limited, "ATTACH DATABASE 'attached.db' AS other"), "ATTACH is refused with a guest authorizer set"); + Require(refused(limited, "VACUUM INTO 'vacuumed.db'"), "VACUUM INTO is refused with a guest authorizer set"); + Require(sqlite3_set_authorizer_nid_postfix(limited, nullptr, nullptr) == SQLITE_OK, "clear the guest authorizer"); + Require(refused(limited, "ATTACH DATABASE 'attached.db' AS other"), "ATTACH is refused after the guest clears its authorizer"); + Require(refused(limited, "VACUUM INTO 'vacuumed.db'"), "VACUUM INTO is refused after the guest clears its authorizer"); + Require(!std::filesystem::exists(directory / "attached.db") && !std::filesystem::exists(directory / "vacuumed.db"), "no host file after the guest sets and clears an authorizer"); + Require(sqlite3_close_nid_postfix(limited) == SQLITE_OK, "close limited.db"); + std::filesystem::remove(directory / "limited.db"); CheckFileDatabase(directory); Require(std::filesystem::is_regular_file(directory / "wal.db"), "guest path maps into the root"); const auto written = WrittenPaths_nid_no_patch(); diff --git a/docs/dev/debt/libSceNKWebKitRequirements.md b/docs/dev/debt/libSceNKWebKitRequirements.md index 3acc3ae518..21461070df 100644 --- a/docs/dev/debt/libSceNKWebKitRequirements.md +++ b/docs/dev/debt/libSceNKWebKitRequirements.md @@ -8,7 +8,8 @@ ### SQLite adapters - The 50 `sqlite3_*` exports in [Sqlite.cpp](../../../core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp) assume the public SQLite C prototypes because their names hash to the inventory NIDs; the console prototypes are not confirmed. They call SQLite 3.54.1 built from `3rdparty/sqlite` with `SQLITE_THREADSAFE=1` and `SQLITE_OMIT_LOAD_EXTENSION`. The console library's version and compile options are unknown. -- [`sqlite3_open_v2`](../../../core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp) ignores the guest VFS name and uses the host default VFS. It maps file names through the guest path resolver, but passes `:memory:`, empty names, `SQLITE_OPEN_MEMORY` and in-memory `file:` URIs unchanged. Other `file:` URIs opened with `SQLITE_OPEN_URI` throw through `NotImplemented_nid_no_patch`. A database that exists only inside `app0.zar` is refused. SQLite writes the database and its `-wal`, `-shm` and `-journal` files on the host directly; a read-write open records those paths so `sceKernelSync` flushes them, but SQLite's own sync calls are not routed through the guest file layer. +- [`sqlite3_open_v2`](../../../core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp) ignores the guest VFS name and uses the host default VFS. It maps file names through the guest path resolver, but passes `:memory:`, empty names and `SQLITE_OPEN_MEMORY` on a name that is not a `file:` URI unchanged. A `file:` URI opened with `SQLITE_OPEN_URI` passes unchanged only when its path is exactly `:memory:` or `mode=memory` is the last `mode` query parameter and the URI has no `%`; every other such URI throws through `NotImplemented_nid_no_patch`. A database that exists only inside `app0.zar` is refused. SQLite writes the database and its `-wal`, `-shm` and `-journal` files on the host directly; a read-write open records those paths so `sceKernelSync` flushes them, but SQLite's own sync calls are not routed through the guest file layer. +- [`sqlite3_open_v2`](../../../core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp) installs a host authorizer on every connection it opens that denies `ATTACH DATABASE` of any non-empty name, so a guest `ATTACH` or `VACUUM INTO` fails with `SQLITE_AUTH` instead of writing a host path that skips the guest path resolver; `ATTACH ''` and a plain `VACUUM` still work. The guest's own `sqlite3_set_authorizer` callback runs after it and cannot lift it. Resolving the guest path for those statements is not implemented. - [`sqlite3_bind_blob`](../../../core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp) and `sqlite3_bind_text16` copy data given with a guest destructor and call that destructor before returning, instead of when SQLite releases the value. - [`sqlite3_wal_hook`](../../../core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp) returns the host's previous argument, such as the default autocheckpoint frame count, until a guest hook is installed. From 9932dd3b1e30ccdfb38d414846e5a3eae8160fbe Mon Sep 17 00:00:00 2001 From: lakhantheboss <61951686+lakhantheboss@users.noreply.github.com> Date: Tue, 3 Nov 2026 13:18:13 +0530 Subject: [PATCH 367/612] feat(libSceNKWebKit): implement WTF::portAllowed --- core/libs/prx/libSceNKWebKit/Export.cpp | 4 - .../include/InventoryExports.hpp | 1 - .../prx/libSceNKWebKit/include/WtfExports.hpp | 1 + .../libs/prx/libSceNKWebKit/src/WtfString.cpp | 75 ++++++++ core/libs/prx/libSceNKWebKit/tests/Wtf.cpp | 171 ++++++++++++++++++ docs/dev/debt/libSceNKWebKit.md | 3 +- 6 files changed, 248 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceNKWebKit/Export.cpp b/core/libs/prx/libSceNKWebKit/Export.cpp index c7059c6d6f..fcc2a15587 100644 --- a/core/libs/prx/libSceNKWebKit/Export.cpp +++ b/core/libs/prx/libSceNKWebKit/Export.cpp @@ -18708,10 +18708,6 @@ int APS5_VABI _ZN3WTF11TimingScope11scopeDidEndEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF11portAllowedERKNS_3URLE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF11setDataFileEPKc() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp index b648255526..0364ec1c00 100644 --- a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp @@ -1633,7 +1633,6 @@ int APS5_VABI _ZN3WTF11ThreadGroup3addERKNS_14AbstractLockerERNS_6ThreadE(); int APS5_VABI _ZN3WTF11ThreadGroup3addERNS_6ThreadE(); int APS5_VABI _ZN3WTF11ThreadGroupD2Ev(); int APS5_VABI _ZN3WTF11TimingScope11scopeDidEndEv(); -int APS5_VABI _ZN3WTF11portAllowedERKNS_3URLE(); int APS5_VABI _ZN3WTF11setDataFileEPKc(); int APS5_VABI _ZN3WTF11setNonBlockEi(); int APS5_VABI _ZN3WTF12CountingLock10unlockSlowEv(); diff --git a/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp b/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp index 4dc794e0c8..9fb67a96aa 100644 --- a/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp @@ -71,6 +71,7 @@ bool APS5_VABI _ZN3WTF10hasElapsedERKNS_24TimeWithDynamicClockTypeE(const WTF::T bool APS5_VABI _ZN3WTF11CStringHash5equalERKNS_7CStringES3_(const NKWebKit::WtfAbi::CString& a, const NKWebKit::WtfAbi::CString& b); void* APS5_VABI _ZN3WTF11fastReallocEPvm(void* ptr, std::size_t size); void APS5_VABI _ZN3WTF11jsValueFreeEPv(void* ptr); +bool APS5_VABI _ZN3WTF11portAllowedERKNS_3URLE(const NKWebKit::WtfAbi::URL* url); WTF::MonotonicTime APS5_VABI _ZN3WTF13MonotonicTime3nowEv(); NKWebKit::WtfAbi::ProcessPrivileges APS5_VABI _ZN3WTF13allPrivilegesEv(); void* APS5_VABI _ZN3WTF13jsValueMallocEm(std::size_t size); diff --git a/core/libs/prx/libSceNKWebKit/src/WtfString.cpp b/core/libs/prx/libSceNKWebKit/src/WtfString.cpp index 0ec203f32c..a00366a320 100644 --- a/core/libs/prx/libSceNKWebKit/src/WtfString.cpp +++ b/core/libs/prx/libSceNKWebKit/src/WtfString.cpp @@ -1,3 +1,29 @@ +/* + * Copyright (C) 2004-2020 Apple Inc. All rights reserved. + * Copyright (C) 2012 Research In Motion Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceNKWebKit/include/WtfExports.hpp" @@ -10,6 +36,7 @@ #include #include #include +#include #include #include @@ -951,9 +978,57 @@ String ConcatViews(const StringView* parts, unsigned count) { return String(impl); } +bool IsUnicodeCompatibleASCIIWhitespace(char32_t c) { + return c <= ' ' && (c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '\f'); +} + +std::optional URLPort(const URL& url) { + unsigned portLength = (url.bits >> 3) & 7; + if (portLength <= 1) return std::nullopt; + auto view = View(url.impl); + if (url.hostEnd > view.length || portLength > view.length - url.hostEnd) return std::nullopt; + auto portView = Slice(view, url.hostEnd + 1, portLength - 1); + if (!portView.length) return std::nullopt; + unsigned i = 0; + while (i < portView.length && IsUnicodeCompatibleASCIIWhitespace(Unit(portView, i))) ++i; + if (i < portView.length && Unit(portView, i) == '+') ++i; + if (i == portView.length) return std::nullopt; + auto firstChar = Unit(portView, i); + if (firstChar < '0' || firstChar > '9') return std::nullopt; + std::uint32_t val = 0; + while (i < portView.length) { + auto ch = Unit(portView, i); + if (ch < '0' || ch > '9') break; + val = val * 10 + (ch - '0'); + if (val > 65535) return std::nullopt; + ++i; + } + while (i < portView.length && IsUnicodeCompatibleASCIIWhitespace(Unit(portView, i))) ++i; + if (i != portView.length) return std::nullopt; + return static_cast(val); +} + } // namespace extern "C" { +bool APS5_VABI _ZN3WTF11portAllowedERKNS_3URLE(const URL* urlPtr) { + const auto& url = Checked(urlPtr); + auto port = URLPort(url); + if (!port) return true; + static constexpr std::uint16_t blockedPortList[] = { + 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, + 77, 79, 87, 95, 101, 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, + 135, 137, 139, 143, 161, 179, 389, 427, 465, 512, 513, 514, 515, 526, 530, + 531, 532, 540, 548, 554, 556, 563, 587, 601, 636, 989, 990, 993, 995, 1719, + 1720, 1723, 2049, 3659, 4045, 4190, 5060, 5061, 6000, 6566, 6665, 6666, + 6667, 6668, 6669, 6679, 6697, 10080 + }; + if (!std::binary_search(std::begin(blockedPortList), std::end(blockedPortList), *port)) return true; + if ((*port == 21 || *port == 22) && URLProtocol(url, {"ftp", 3, true})) return true; + if (URLProtocol(url, {"file", 4, true})) return true; + return false; +} + bool APS5_VABI _ZN3WTF31equalIgnoringFragmentIdentifierERKNS_3URLES2_(const URL* a, const URL* b) { const auto& first = Checked(a); const auto& second = Checked(b); diff --git a/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp b/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp index 8508298479..5bfee36233 100644 --- a/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp +++ b/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp @@ -803,6 +803,7 @@ const auto URLSrcdoc = &_ZNK3WTF3URL13isAboutSrcDocEv; const auto URLJavaScript = &_ZNK3WTF3URL20protocolIsJavaScriptEv; const auto URLInvalidate = &_ZN3WTF3URL10invalidateEv; const auto URLEqualIgnoringFragment = &_ZN3WTF31equalIgnoringFragmentIdentifierERKNS_3URLES2_; +const auto URLPortAllowed = &_ZN3WTF11portAllowedERKNS_3URLE; const auto IsJavaScript = &_ZN3WTF20protocolIsJavaScriptENS_10StringViewE; const auto IsHTTP = &_ZN3WTF22protocolIsInHTTPFamilyENS_10StringViewE; const auto JSRealloc = &_ZN3WTF14jsValueReallocEPvm; @@ -1029,6 +1030,176 @@ void TestURLsAndReallocation() { Check(!URLEqualIgnoringFragment(&absent, &javascript)); Throws([&] { URLEqualIgnoringFragment(nullptr, &url); }); Throws([&] { URLEqualIgnoringFragment(&url, nullptr); }); + auto urlNoPortStr = FromUTF8Z(U8("http://example.test/path")); + URL urlNoPort{urlNoPortStr.impl, 1U | 2U | (0U << 3) | (4U << 6), 7, 7, 7, 19, 20, 24, 24}; + Check(URLPortAllowed(&urlNoPort)); + auto urlPort80Str = FromUTF8Z(U8("http://example.test:80/path")); + URL urlPort80{urlPort80Str.impl, 1U | 2U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(URLPortAllowed(&urlPort80)); + auto urlPort443Str = FromUTF8Z(U8("http://example.test:443/path")); + URL urlPort443{urlPort443Str.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(URLPortAllowed(&urlPort443)); + auto urlPort8080Str = FromUTF8Z(U8("http://example.test:8080/path")); + URL urlPort8080{urlPort8080Str.impl, 1U | 2U | (5U << 3) | (4U << 6), 7, 7, 7, 19, 25, 29, 29}; + Check(URLPortAllowed(&urlPort8080)); + auto urlPort0Str = FromUTF8Z(U8("http://example.test:0/path")); + URL urlPort0{urlPort0Str.impl, 1U | 2U | (2U << 3) | (4U << 6), 7, 7, 7, 19, 22, 26, 26}; + Check(URLPortAllowed(&urlPort0)); + auto urlPort10081Str = FromUTF8Z(U8("http://example.test:10081/path")); + URL urlPort10081{urlPort10081Str.impl, 1U | 2U | (6U << 3) | (4U << 6), 7, 7, 7, 19, 26, 30, 30}; + Check(URLPortAllowed(&urlPort10081)); + auto urlPort1Str = FromUTF8Z(U8("http://example.test:1/path")); + URL urlPort1{urlPort1Str.impl, 1U | 2U | (2U << 3) | (4U << 6), 7, 7, 7, 19, 22, 26, 26}; + Check(!URLPortAllowed(&urlPort1)); + auto urlPort7Str = FromUTF8Z(U8("http://example.test:7/path")); + URL urlPort7{urlPort7Str.impl, 1U | 2U | (2U << 3) | (4U << 6), 7, 7, 7, 19, 22, 26, 26}; + Check(!URLPortAllowed(&urlPort7)); + auto urlPort21Str = FromUTF8Z(U8("http://example.test:21/path")); + URL urlPort21{urlPort21Str.impl, 1U | 2U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(!URLPortAllowed(&urlPort21)); + auto urlPort22Str = FromUTF8Z(U8("http://example.test:22/path")); + URL urlPort22{urlPort22Str.impl, 1U | 2U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(!URLPortAllowed(&urlPort22)); + auto urlPort25Str = FromUTF8Z(U8("http://example.test:25/path")); + URL urlPort25{urlPort25Str.impl, 1U | 2U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(!URLPortAllowed(&urlPort25)); + auto urlPort87Str = FromUTF8Z(U8("http://example.test:87/path")); + URL urlPort87{urlPort87Str.impl, 1U | 2U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(!URLPortAllowed(&urlPort87)); + auto urlPort10080Str = FromUTF8Z(U8("http://example.test:10080/path")); + URL urlPort10080{urlPort10080Str.impl, 1U | 2U | (6U << 3) | (4U << 6), 7, 7, 7, 19, 26, 30, 30}; + Check(!URLPortAllowed(&urlPort10080)); + auto ftpPort20Str = FromUTF8Z(U8("ftp://example.test:20/path")); + URL ftpPort20{ftpPort20Str.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 18, 22, 26, 26}; + Check(!URLPortAllowed(&ftpPort20)); + auto ftpPort21Str = FromUTF8Z(U8("ftp://example.test:21/path")); + URL ftpPort21{ftpPort21Str.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 18, 22, 26, 26}; + Check(URLPortAllowed(&ftpPort21)); + auto ftpPort22Str = FromUTF8Z(U8("ftp://example.test:22/path")); + URL ftpPort22{ftpPort22Str.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 18, 22, 26, 26}; + Check(URLPortAllowed(&ftpPort22)); + auto ftpPort25Str = FromUTF8Z(U8("ftp://example.test:25/path")); + URL ftpPort25{ftpPort25Str.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 18, 22, 26, 26}; + Check(!URLPortAllowed(&ftpPort25)); + auto ftpUpper21Str = FromUTF8Z(U8("FTP://example.test:21/path")); + URL ftpUpper21{ftpUpper21Str.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 18, 22, 26, 26}; + Check(URLPortAllowed(&ftpUpper21)); + auto ftpUpper22Str = FromUTF8Z(U8("FTP://example.test:22/path")); + URL ftpUpper22{ftpUpper22Str.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 18, 22, 26, 26}; + Check(URLPortAllowed(&ftpUpper22)); + auto ftpUpper25Str = FromUTF8Z(U8("FTP://example.test:25/path")); + URL ftpUpper25{ftpUpper25Str.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 18, 22, 26, 26}; + Check(!URLPortAllowed(&ftpUpper25)); + auto filePort1Str = FromUTF8Z(U8("file://example.test:1/path")); + URL filePort1{filePort1Str.impl, 1U | 0U | (2U << 3) | (4U << 6), 7, 7, 7, 19, 22, 26, 26}; + Check(URLPortAllowed(&filePort1)); + auto filePort21Str = FromUTF8Z(U8("file://example.test:21/path")); + URL filePort21{filePort21Str.impl, 1U | 0U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(URLPortAllowed(&filePort21)); + auto filePort25Str = FromUTF8Z(U8("file://example.test:25/path")); + URL filePort25{filePort25Str.impl, 1U | 0U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(URLPortAllowed(&filePort25)); + auto filePort10080Str = FromUTF8Z(U8("file://example.test:10080/path")); + URL filePort10080{filePort10080Str.impl, 1U | 0U | (6U << 3) | (4U << 6), 7, 7, 7, 19, 26, 30, 30}; + Check(URLPortAllowed(&filePort10080)); + auto fileUpper25Str = FromUTF8Z(U8("FILE://example.test:25/path")); + URL fileUpper25{fileUpper25Str.impl, 1U | 0U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(URLPortAllowed(&fileUpper25)); + auto urlMinusPortStr = FromUTF8Z(U8("http://example.test:-80/path")); + URL urlMinusPort{urlMinusPortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(URLPortAllowed(&urlMinusPort)); + auto urlOverflowPortStr = FromUTF8Z(U8("http://example.test:65536/path")); + URL urlOverflowPort{urlOverflowPortStr.impl, 1U | 2U | (6U << 3) | (4U << 6), 7, 7, 7, 19, 26, 30, 30}; + Check(URLPortAllowed(&urlOverflowPort)); + auto urlBarePlusPortStr = FromUTF8Z(U8("http://example.test:+/path")); + URL urlBarePlusPort{urlBarePlusPortStr.impl, 1U | 2U | (2U << 3) | (4U << 6), 7, 7, 7, 19, 22, 26, 26}; + Check(URLPortAllowed(&urlBarePlusPort)); + auto urlPlus25PortStr = FromUTF8Z(U8("http://example.test:+25/path")); + URL urlPlus25Port{urlPlus25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlPlus25Port)); + auto urlSpace25PortStr = FromUTF8Z(U8("http://example.test: 25 /path")); + URL urlSpace25Port{urlSpace25PortStr.impl, 1U | 2U | (5U << 3) | (4U << 6), 7, 7, 7, 19, 25, 29, 29}; + Check(!URLPortAllowed(&urlSpace25Port)); + auto urlTab25PortStr = FromUTF8Z(U8("http://example.test:\t25/path")); + URL urlTab25Port{urlTab25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlTab25Port)); + auto urlLf25PortStr = FromUTF8Z(U8("http://example.test:\n25/path")); + URL urlLf25Port{urlLf25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlLf25Port)); + auto urlCr25PortStr = FromUTF8Z(U8("http://example.test:\r25/path")); + URL urlCr25Port{urlCr25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlCr25Port)); + auto urlFf25PortStr = FromUTF8Z(U8("http://example.test:\f25/path")); + URL urlFf25Port{urlFf25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlFf25Port)); + auto urlTrailingTab25PortStr = FromUTF8Z(U8("http://example.test:25\t/path")); + URL urlTrailingTab25Port{urlTrailingTab25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlTrailingTab25Port)); + auto urlTrailingLf25PortStr = FromUTF8Z(U8("http://example.test:25\n/path")); + URL urlTrailingLf25Port{urlTrailingLf25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlTrailingLf25Port)); + auto urlTrailingCr25PortStr = FromUTF8Z(U8("http://example.test:25\r/path")); + URL urlTrailingCr25Port{urlTrailingCr25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlTrailingCr25Port)); + auto urlTrailingFf25PortStr = FromUTF8Z(U8("http://example.test:25\f/path")); + URL urlTrailingFf25Port{urlTrailingFf25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlTrailingFf25Port)); + auto urlJunk25PortStr = FromUTF8Z(U8("http://example.test:25x/path")); + URL urlJunk25Port{urlJunk25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(URLPortAllowed(&urlJunk25Port)); + auto urlJunk80PortStr = FromUTF8Z(U8("http://example.test:80junk/path")); + URL urlJunk80Port{urlJunk80PortStr.impl, 1U | 2U | (7U << 3) | (4U << 6), 7, 7, 7, 19, 27, 31, 31}; + Check(URLPortAllowed(&urlJunk80Port)); + auto widePort8080 = Create16(u"http://wide.test:8080/path", 26); + URL urlWidePort8080{widePort8080.impl, 1U | 2U | (5U << 3) | (4U << 6), 7, 7, 7, 16, 22, 26, 26}; + Check(URLPortAllowed(&urlWidePort8080)); + auto widePort25 = Create16(u"http://wide.test:25/path", 24); + URL urlWidePort25{widePort25.impl, 1U | 2U | (3U << 3) | (4U << 6), 7, 7, 7, 16, 20, 24, 24}; + Check(!URLPortAllowed(&urlWidePort25)); + auto wideFtpPort21 = Create16(u"ftp://wide.test:21/path", 23); + URL urlWideFtpPort21{wideFtpPort21.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 15, 19, 23, 23}; + Check(URLPortAllowed(&urlWideFtpPort21)); + auto wideFilePort25 = Create16(u"file://wide.test:25/path", 24); + URL urlWideFilePort25{wideFilePort25.impl, 1U | 0U | (3U << 3) | (4U << 6), 7, 7, 7, 16, 20, 24, 24}; + Check(URLPortAllowed(&urlWideFilePort25)); + auto wideFtpUpper21 = Create16(u"FTP://wide.test:21/path", 23); + URL urlWideFtpUpper21{wideFtpUpper21.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 15, 19, 23, 23}; + Check(URLPortAllowed(&urlWideFtpUpper21)); + auto wideFileUpper25 = Create16(u"FILE://wide.test:25/path", 24); + URL urlWideFileUpper25{wideFileUpper25.impl, 1U | 0U | (3U << 3) | (4U << 6), 7, 7, 7, 16, 20, 24, 24}; + Check(URLPortAllowed(&urlWideFileUpper25)); + auto wideTab25 = Create16(u"http://wide.test:\t25/path", 25); + URL urlWideTab25{wideTab25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideTab25)); + auto wideLf25 = Create16(u"http://wide.test:\n25/path", 25); + URL urlWideLf25{wideLf25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideLf25)); + auto wideCr25 = Create16(u"http://wide.test:\r25/path", 25); + URL urlWideCr25{wideCr25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideCr25)); + auto wideFf25 = Create16(u"http://wide.test:\f25/path", 25); + URL urlWideFf25{wideFf25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideFf25)); + auto wideTrailingTab25 = Create16(u"http://wide.test:25\t/path", 25); + URL urlWideTrailingTab25{wideTrailingTab25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideTrailingTab25)); + auto wideTrailingLf25 = Create16(u"http://wide.test:25\n/path", 25); + URL urlWideTrailingLf25{wideTrailingLf25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideTrailingLf25)); + auto wideTrailingCr25 = Create16(u"http://wide.test:25\r/path", 25); + URL urlWideTrailingCr25{wideTrailingCr25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideTrailingCr25)); + auto wideTrailingFf25 = Create16(u"http://wide.test:25\f/path", 25); + URL urlWideTrailingFf25{wideTrailingFf25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideTrailingFf25)); + auto wideJunk25 = Create16(u"http://wide.test:25x/path", 25); + URL urlWideJunk25{wideJunk25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(URLPortAllowed(&urlWideJunk25)); + auto urlWraparoundPortStr = FromUTF8Z(U8("25://example.test/path")); + URL urlWraparoundPort{urlWraparoundPortStr.impl, 1U | 2U | (3U << 3) | (2U << 6), 5, 5, 5, ~0U, 23, 22, 22}; + Check(URLPortAllowed(&urlWraparoundPort)); + Check(URLPortAllowed(&absent)); + Throws([&] { URLPortAllowed(nullptr); }); URLInvalidate(&url); Check(url.impl == string.impl && url.bits == 0 && url.userStart == 0 && url.userEnd == 0); Check(url.passwordEnd == 0 && url.hostEnd == 0 && url.pathEnd == 0 && url.pathAfterLastSlash == 0 && url.queryEnd == 0); diff --git a/docs/dev/debt/libSceNKWebKit.md b/docs/dev/debt/libSceNKWebKit.md index 3c8a70d688..184997b072 100644 --- a/docs/dev/debt/libSceNKWebKit.md +++ b/docs/dev/debt/libSceNKWebKit.md @@ -35,7 +35,7 @@ Inventory prose retains historical counts; use the [generated progress report](h - The [revision-pinned identity inventory](https://gist.github.com/kyon-eth/d44995f181e1818977bd38f0e6fc0f34/8afcb14b0707a09b6274ff7f7bcf33ef46cfe2e7) records the module, library, symbol type and NIDs for the declarations in [Export.cpp](../../../core/libs/prx/libSceNKWebKit/Export.cpp); it is a metadata summary rather than independent verification. For the 255 absent from the public catalogue, [per-image ELF records](https://gist.github.com/kyon-eth/147be8dd556e9cced2577ccbed548972/1a659ecfe819e724675991a574a1aebb5fbb48e5) include the raw qualified names, module and library identities, symbol type, values and sizes; the record file SHA-256 is `44bdc4d6073192a8f41b7be359b75364b3d41dbbd11ce649924b8cebdd4ffe51`. Image labels are not authenticated. -- [Additional callable exports](../../../core/libs/prx/libSceNKWebKit/Export.cpp) - 7426 of the 8703 inventory declarations listed below throw through `NotImplemented_nid_no_patch`; signatures and behavior are unverified. The later 13.60-labelled image contains all 8703; 8448 match the public 3.20 catalogue, while 255 are absent and their presence in retail 3.20 is unverified. Per-image records show the latter in a separate 3.20-labelled image, but neither image label is authenticated. Identified names match NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). Data exports need separate analysis. +- [Additional callable exports](../../../core/libs/prx/libSceNKWebKit/Export.cpp) - 7425 of the 8703 inventory declarations listed below throw through `NotImplemented_nid_no_patch`; signatures and behavior are unverified. The later 13.60-labelled image contains all 8703; 8448 match the public 3.20 catalogue, while 255 are absent and their presence in retail 3.20 is unverified. Per-image records show the latter in a separate 3.20-labelled image, but neither image label is authenticated. Identified names match NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). Data exports need separate analysis. | Name | NID | | --- | --- | @@ -1593,7 +1593,6 @@ Inventory prose retains historical counts; use the [generated progress report](h | `_ZN3WTF11ThreadGroup3addERNS_6ThreadE` | `yI8ZKsn2NmU` | | `_ZN3WTF11ThreadGroupD2Ev` | `6L90iL-CjWA` | | `_ZN3WTF11TimingScope11scopeDidEndEv` | `vNFWwy493LY` | -| `_ZN3WTF11portAllowedERKNS_3URLE` | `tRrU4kb6AtE` | | `_ZN3WTF11setDataFileEPKc` | `mxYK3TBPDro` | | `_ZN3WTF11setNonBlockEi` | `ZH232ViZnko` | | `_ZN3WTF12CountingLock10unlockSlowEv` | `y9CL7tutRKQ` | From db1a5db6633e6b00bdf498a348890ed2f5eb23d3 Mon Sep 17 00:00:00 2001 From: lakhantheboss <61951686+lakhantheboss@users.noreply.github.com> Date: Tue, 3 Nov 2026 14:26:51 +0530 Subject: [PATCH 368/612] docs(libSceNKWebKit): document WTF::portAllowed provenance and limitations --- docs/dev/debt/libSceNKWebKit.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev/debt/libSceNKWebKit.md b/docs/dev/debt/libSceNKWebKit.md index 184997b072..d5551b38bb 100644 --- a/docs/dev/debt/libSceNKWebKit.md +++ b/docs/dev/debt/libSceNKWebKit.md @@ -22,6 +22,7 @@ Inventory prose retains historical counts; use the [generated progress report](h - [Out-of-line colors](../../../core/libs/prx/libSceNKWebKit/src/WebCoreColor.cpp) (libSceNKWebKit) - `WebCore::Color` operations on an out-of-line value (extended color spaces in [Color.h](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebCore/platform/graphics/Color.h)) throw; only inline 8-bit sRGBA values are handled. - [WTF host services](../../../core/libs/prx/libSceNKWebKit/src/WtfSystem.cpp) (libSceNKWebKit) - `fastMalloc`, `bmalloc` and Gigacage allocations come from the host allocator, and `numberOfProcessorCores`, `ramSize`, the clocks and random values come from the host, not the console; they throw when the host cannot answer. `WTFCrash` and failed assertions abort the process. - [WebCore::ResourceResponseBase](../../../core/libs/prx/libSceNKWebKit/src/ResourceResponse.cpp) (libSceNKWebKit) - the object layout in [ResourceResponseAbi.hpp](../../../core/libs/prx/libSceNKWebKit/src/ResourceResponseAbi.hpp) comes from [ResourceResponseBase.h](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebCore/platform/network/ResourceResponseBase.h) and is not checked against the PS5 module: `URL` at 0, four `AtomString` and the 64-bit expected length up to offset 80, then a 32-byte `HTTPHeaderMap` (two vectors of inline capacity 0 as in [HTTPHeaderMap.h](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebCore/platform/network/HTTPHeaderMap.h)). Only these fields are modelled because `std::optional` follows the `Box` and the size of the PS5 `CertificateInfo` is unknown, so the offsets of `m_httpStatusCode`, `m_isNull`, `m_initLevel`, `m_type`, `m_source` and the flags are not established. The getters (`url`, `mimeType`, `expectedContentLength`, `textEncodingName`, `httpStatusText`, `httpVersion`, `httpHeaderFields`, `isInHTTPFamily`) skip `lazyInit` because the curl [ResourceResponse.h](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebCore/platform/network/curl/ResourceResponse.h) has no `platformLazyInit`; the PS5 `ResourceResponse` is not known to match. `isInHTTPFamily` reads the valid bit of `URL::bits` and the scheme text, not the cached `m_protocolIsInHTTPFamily` bit, whose position is unknown. The 30 other exports (`ResourceResponseBase` constructors, `setURL`, `setType`, `setExpectedContentLength`, `setHTTPStatusCode`, `httpStatusCode`, `source`, `isSuccessful`, `isRedirection`, `isHTTP09`, `isAttachment*`, `suggestedFilename`, `sanitize*`, `syntheticRedirectResponse`, `cacheControlContains*`, `hasCacheValidatorFields`, `lazyInit`, `httpHeaderField*`, `addHTTPHeaderField*`, `setHTTPHeaderField*`) still throw `NotImplemented_nid_no_patch`: they write `m_isNull`, read fields after `CertificateInfo`, or need `HTTPHeaderMap`, `CertificateInfo`, `Vector` or `ResourceResponse` layouts. +- [URL port filtering](../../../core/libs/prx/libSceNKWebKit/src/WtfString.cpp) (libSceNKWebKit) - `WTF::portAllowed` and `URLPort` adapt [URL.cpp](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WTF/wtf/URL.cpp#L952-L1051) and [URL::port](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WTF/wtf/URL.cpp#L158-L161) from the pinned PS5 11.x WebKit source. The blocked-port table, scheme exceptions for `ftp` (21, 22) and `file`, and port parsing via `parseInteger` follow upstream semantics; unparseable ports yield `std::nullopt` in `URL::port()` and evaluate to allowed (`true`). Verified on Windows against synthetic `URL` layouts; physical console execution and Linux behavior are unverified. ### Known export names; ABI prototypes unverified From 169f433e6d3e7a016d44c46791a2711f00a9d3a0 Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Thu, 5 Nov 2026 20:05:20 +1300 Subject: [PATCH 369/612] test(agc): tolerate lavapipe's noisy implicit-LOD quads in the LOD bias sum test On llvmpipe about 9 percent of pixels, in the same 2x2 quads for every case, get a wrong implicit derivative and read level 0 or 6 on the unbiased case too, so the exact all-pixel check failed before any bias was applied. Other drivers keep the exact per-pixel check; on llvmpipe the expected level must hold for at least three quarters of the pixels (90 percent measured) and no case is relaxed otherwise. --- .../tests/execution/ImageSampleLodBiasSum.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleLodBiasSum.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleLodBiasSum.cpp index 58d0a4f205..f195572fd9 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleLodBiasSum.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleLodBiasSum.cpp @@ -156,11 +156,18 @@ struct Case { std::uint32_t level; }; -void Check(const Case& sample) { +void Check(const Case& sample, bool llvmpipe) { + const std::string label = "image sample LOD bias sum: S# field " + std::to_string(sample.field) + " with shader bias " + std::to_string(sample.shaderBias); + std::uint32_t matching = 0; for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) { const auto red = std::to_integer(Pixels[pixel * 4u]); - Require(red == sample.level * LevelStep, "image sample LOD bias sum: S# field " + std::to_string(sample.field) + " with shader bias " + std::to_string(sample.shaderBias) + " read " + std::to_string(red) + ", expected level " + std::to_string(sample.level)); + if (red == sample.level * LevelStep) { + ++matching; + continue; + } + Require(llvmpipe, label + " read " + std::to_string(red) + ", expected level " + std::to_string(sample.level)); } + Require(matching * 4u >= Width * Height * 3u, label + " matched level " + std::to_string(sample.level) + " on only " + std::to_string(matching) + " of " + std::to_string(Width * Height) + " pixels"); } } @@ -170,6 +177,7 @@ int main() { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; FillTexture(); + const bool llvmpipe = device->DeviceName().find("llvmpipe") != std::string::npos; constexpr std::array cases{{ {0u, 0.0f, 2u}, {0x100u, 0.0f, 3u}, @@ -185,7 +193,7 @@ int main() { auto sampler = SamplerDescriptor(); sampler[2] |= sample.field; Draw(*device, TextureDescriptor(), sampler, sample.shaderBias); - Check(sample); + Check(sample, llvmpipe); } std::puts("image sample LOD bias sum tests passed"); return 0; From a77ce5504b186ebb5a1280e4630dac230b9b9a3e Mon Sep 17 00:00:00 2001 From: Maz Date: Wed, 28 Oct 2026 20:27:40 +0400 Subject: [PATCH 370/612] feat(libc): export the time_put facet vtable as data Titles resolve NID OwfBD-2nhJQ as a 56-byte STT_OBJECT on console libc.prx; export seven zeroed uintptr_t slots so the data binding matches. Facet calls through the object still fault until methods are wired; _Getcat keeps using g_timePutVtable. --- core/libs/prx/libc/include/GuestLocale.hpp | 3 +++ core/libs/prx/libc/src/LocaleSupport.cpp | 1 + core/libs/tests/GuestLocale.cpp | 11 +++++++++++ docs/dev/debt/libc.md | 1 + 4 files changed, 16 insertions(+) diff --git a/core/libs/prx/libc/include/GuestLocale.hpp b/core/libs/prx/libc/include/GuestLocale.hpp index bd1176e893..e61ed37d2a 100644 --- a/core/libs/prx/libc/include/GuestLocale.hpp +++ b/core/libs/prx/libc/include/GuestLocale.hpp @@ -270,5 +270,8 @@ extern "C" std::uintptr_t _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_trai inline constexpr std::size_t kCodecvtCharVtableSlots = 13; static_assert(kCodecvtCharVtableSlots * sizeof(std::uintptr_t) == 104); extern "C" std::uintptr_t _ZTVSt7codecvtIcc9_MbstatetE_nid_postfix[kCodecvtCharVtableSlots]; +inline constexpr std::size_t kTimePutCharVtableSlots = 7; +static_assert(kTimePutCharVtableSlots * sizeof(std::uintptr_t) == 56); +extern "C" std::uintptr_t _ZTVSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[kTimePutCharVtableSlots]; #endif diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index 1c79844a1c..edd1527408 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -683,6 +683,7 @@ std::uintptr_t _ZTVSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE_nid std::uint64_t _ZNSt8numpunctIcE2idE_nid_postfix = 0; std::uint64_t _ZNSt8numpunctIwE2idE_nid_postfix = 0; std::uint64_t _ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0; +std::uintptr_t _ZTVSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[kTimePutCharVtableSlots] {}; std::uintptr_t _ZTVSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[13] {}; std::uintptr_t _ZTVSt8time_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix[13] {}; std::uintptr_t _ZTVSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix[17] {}; diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp index 78280f321e..42ea2a99db 100644 --- a/core/libs/tests/GuestLocale.cpp +++ b/core/libs/tests/GuestLocale.cpp @@ -553,6 +553,16 @@ static void CheckCtypeVtableObject() { } } +static void CheckTimePutVtableObject() { + Require(sizeof(_ZTVSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) == kTimePutCharVtableSlots * sizeof(std::uintptr_t)); + for (std::uintptr_t& slot : _ZTVSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } +} + static void CheckNumPutVtableObject() { Require(sizeof(_ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) == kNumPutCharVtableSlots * sizeof(std::uintptr_t)); for (std::uintptr_t& slot : _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) { @@ -621,6 +631,7 @@ int main() { CheckCtypeVtableObject(); CheckWideCtypeVtableObject(); CheckCodecvtVtableObject(); + CheckTimePutVtableObject(); _Locksyslock_nid_postfix(); _Locksyslock_nid_postfix(); _Unlocksyslock_nid_postfix(); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 2bbaa32db2..85942f15ec 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -298,6 +298,7 @@ - [ctype vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `GKWcAz6-G7k`) is fourteen zeroed `uintptr_t` slots matching `st_size` 112 on the title `libc.prx` from PPSA16833 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and eight `do_*` virtuals). Facet calls through the object fault until those methods are wired. - [ctype vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `qdHsu+gIxRo`) is eighteen zeroed `uintptr_t` slots matching `st_size` 144 on the title `libc.prx` from PPSA16833 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and twelve `do_*` virtuals). Facet calls through the object fault until those methods are wired. - [codecvt vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `aK1Ymf-NhAs`) is thirteen zeroed `uintptr_t` slots matching `st_size` 104 on title `libc.prx` from PPSA26344 and on `libSceLibcInternal.sprx` (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and seven conversion virtuals). Facet calls through the object fault until those methods are wired. +- [time_put vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `OwfBD-2nhJQ`) is seven zeroed `uintptr_t` slots matching `st_size` 56 on the title `libc.prx` from PPSA26344 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and one `do_put` virtual). Facet calls through the object fault until those methods are wired; `_Getcat` still installs `g_timePutVtable` for facets it builds. - [ios_base standard streams](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) `_Addstd` queues the stream in the standard stream table of Dinkumware [ios.cpp](https://github.com/ganboing/crt.vc12/blob/master/src/ios.cpp) (`stdstr`, `stdopens`, `_Addstd`, `_Ios_base_dtor`): it takes the first of slots 1 to 7 that is free or already holds the stream, and slot 8 (`NSTDSTR`) when none is, so slot 8 is shared by every stream past the first seven and registration never fails. It stores the 1-based slot index in the guest `IosBase::standardStream` (Dinkumware's `_Stdstr`, where 0 means not a standard stream) and counts the stream objects registered per slot; registering the same stream again reuses its slot and raises its count. Measured on a PS5 (firmware 13.60) from a homebrew payload calling the system `libSceLibcInternal` export: in a process whose own standard streams already hold slots 1 to 7, twelve distinct streams and a repeated one all get 8, written as a 64-bit value at offset 0x8 with no other byte of the object touched; the assignment of slots 1 to 7 to fresh streams is not observable there and follows VC12. The counts are `int`, not VC12's `char`, and the registration and the destructor lock the table where VC12 locks only `_Addstd`; a slot index past the table throws in the destructor instead of indexing out of bounds. The destructor decrements its slot's count and suppresses the rest of the destruction while the count stays positive, as `_Ios_base_dtor` does, and the slots of destroyed streams keep their pointer and stay reserved for that stream, as `stdstr` does. The streams themselves are destroyed by the title's own code as it registers them through `__cxa_atexit`; when the last reference closes, the unsupported stream storage and callback cleanup is still the throw it was and the locale is released as for a non-standard stream. - [std::_Pad, _Thrd_join, _Thrd_detach, _Thrd_id](../../../core/libs/prx/libc/src/Threads.cpp) (libc) follow Dinkumware V6.00 `thr/xthread` and `thr/threadcall.cpp` ([crt.vc12 `f1d47126`](https://github.com/ganboing/crt.vc12/tree/f1d47126/src/thr)), otherwise without console verification. `_Thrd_id` returns `scePthreadSelf()` unchanged, as measured on a PS5 (firmware 13.40) in two threads (#4164, #8711); the two-argument `_Launch` form starts with default attributes and the three-argument form forwards the caller's attribute pointer, as PPSA02530's `libc.prx` does at `0x18090`. The 32-byte pad layout is assumed from VC12, unverified. Failing primitives or thread start throw the guest `std::system_error`; null arguments throw. `_Thrd_join` returns `_Thrd_error` (4) on `pthread_join` failure and truncates the exit code to `int`; `_Thrd_detach` returns `_Thrd_error` (4) on `pthread_detach` failure and throws when none is registered - [collate vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `ruZtIwbCFjk`) is nine `uintptr_t` slots matching `st_size` 72 on the title `libc.prx` from PPSA16833 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and three `do_*` virtuals). Slots 2 through 8 hold the same `DestroyCollate`, `DeleteCollate`, `RetainFacet`, `ReleaseFacet`, `CollateCompare`, `CollateTransform` and `CollateHash` functions that `g_collateVtable` uses, so facet calls through the exported object work; offset-to-top and typeinfo remain zero, unlike the console's relocated entries, and `_Getcat` still installs `g_collateVtable` for facets it builds. From c6b15ab911d4528261d635214852207ac4ca9770 Mon Sep 17 00:00:00 2001 From: Maz Date: Wed, 28 Oct 2026 20:27:40 +0400 Subject: [PATCH 371/612] feat(libc): export the time_put facet vtable as data Titles resolve NID OwfBD-2nhJQ as a 56-byte STT_OBJECT on console libc.prx; export seven zeroed uintptr_t slots so the data binding matches. Facet calls through the object still fault until methods are wired; _Getcat keeps using g_timePutVtable. --- core/libs/prx/libc/include/GuestLocale.hpp | 3 +++ core/libs/prx/libc/src/LocaleSupport.cpp | 1 + core/libs/tests/GuestLocale.cpp | 11 +++++++++++ docs/dev/debt/libc.md | 1 + 4 files changed, 16 insertions(+) diff --git a/core/libs/prx/libc/include/GuestLocale.hpp b/core/libs/prx/libc/include/GuestLocale.hpp index bd1176e893..e61ed37d2a 100644 --- a/core/libs/prx/libc/include/GuestLocale.hpp +++ b/core/libs/prx/libc/include/GuestLocale.hpp @@ -270,5 +270,8 @@ extern "C" std::uintptr_t _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_trai inline constexpr std::size_t kCodecvtCharVtableSlots = 13; static_assert(kCodecvtCharVtableSlots * sizeof(std::uintptr_t) == 104); extern "C" std::uintptr_t _ZTVSt7codecvtIcc9_MbstatetE_nid_postfix[kCodecvtCharVtableSlots]; +inline constexpr std::size_t kTimePutCharVtableSlots = 7; +static_assert(kTimePutCharVtableSlots * sizeof(std::uintptr_t) == 56); +extern "C" std::uintptr_t _ZTVSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[kTimePutCharVtableSlots]; #endif diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index 1c79844a1c..edd1527408 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -683,6 +683,7 @@ std::uintptr_t _ZTVSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE_nid std::uint64_t _ZNSt8numpunctIcE2idE_nid_postfix = 0; std::uint64_t _ZNSt8numpunctIwE2idE_nid_postfix = 0; std::uint64_t _ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0; +std::uintptr_t _ZTVSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[kTimePutCharVtableSlots] {}; std::uintptr_t _ZTVSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[13] {}; std::uintptr_t _ZTVSt8time_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix[13] {}; std::uintptr_t _ZTVSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix[17] {}; diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp index 78280f321e..42ea2a99db 100644 --- a/core/libs/tests/GuestLocale.cpp +++ b/core/libs/tests/GuestLocale.cpp @@ -553,6 +553,16 @@ static void CheckCtypeVtableObject() { } } +static void CheckTimePutVtableObject() { + Require(sizeof(_ZTVSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) == kTimePutCharVtableSlots * sizeof(std::uintptr_t)); + for (std::uintptr_t& slot : _ZTVSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } +} + static void CheckNumPutVtableObject() { Require(sizeof(_ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) == kNumPutCharVtableSlots * sizeof(std::uintptr_t)); for (std::uintptr_t& slot : _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) { @@ -621,6 +631,7 @@ int main() { CheckCtypeVtableObject(); CheckWideCtypeVtableObject(); CheckCodecvtVtableObject(); + CheckTimePutVtableObject(); _Locksyslock_nid_postfix(); _Locksyslock_nid_postfix(); _Unlocksyslock_nid_postfix(); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 2bbaa32db2..85942f15ec 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -298,6 +298,7 @@ - [ctype vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `GKWcAz6-G7k`) is fourteen zeroed `uintptr_t` slots matching `st_size` 112 on the title `libc.prx` from PPSA16833 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and eight `do_*` virtuals). Facet calls through the object fault until those methods are wired. - [ctype vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `qdHsu+gIxRo`) is eighteen zeroed `uintptr_t` slots matching `st_size` 144 on the title `libc.prx` from PPSA16833 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and twelve `do_*` virtuals). Facet calls through the object fault until those methods are wired. - [codecvt vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `aK1Ymf-NhAs`) is thirteen zeroed `uintptr_t` slots matching `st_size` 104 on title `libc.prx` from PPSA26344 and on `libSceLibcInternal.sprx` (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and seven conversion virtuals). Facet calls through the object fault until those methods are wired. +- [time_put vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `OwfBD-2nhJQ`) is seven zeroed `uintptr_t` slots matching `st_size` 56 on the title `libc.prx` from PPSA26344 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and one `do_put` virtual). Facet calls through the object fault until those methods are wired; `_Getcat` still installs `g_timePutVtable` for facets it builds. - [ios_base standard streams](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) `_Addstd` queues the stream in the standard stream table of Dinkumware [ios.cpp](https://github.com/ganboing/crt.vc12/blob/master/src/ios.cpp) (`stdstr`, `stdopens`, `_Addstd`, `_Ios_base_dtor`): it takes the first of slots 1 to 7 that is free or already holds the stream, and slot 8 (`NSTDSTR`) when none is, so slot 8 is shared by every stream past the first seven and registration never fails. It stores the 1-based slot index in the guest `IosBase::standardStream` (Dinkumware's `_Stdstr`, where 0 means not a standard stream) and counts the stream objects registered per slot; registering the same stream again reuses its slot and raises its count. Measured on a PS5 (firmware 13.60) from a homebrew payload calling the system `libSceLibcInternal` export: in a process whose own standard streams already hold slots 1 to 7, twelve distinct streams and a repeated one all get 8, written as a 64-bit value at offset 0x8 with no other byte of the object touched; the assignment of slots 1 to 7 to fresh streams is not observable there and follows VC12. The counts are `int`, not VC12's `char`, and the registration and the destructor lock the table where VC12 locks only `_Addstd`; a slot index past the table throws in the destructor instead of indexing out of bounds. The destructor decrements its slot's count and suppresses the rest of the destruction while the count stays positive, as `_Ios_base_dtor` does, and the slots of destroyed streams keep their pointer and stay reserved for that stream, as `stdstr` does. The streams themselves are destroyed by the title's own code as it registers them through `__cxa_atexit`; when the last reference closes, the unsupported stream storage and callback cleanup is still the throw it was and the locale is released as for a non-standard stream. - [std::_Pad, _Thrd_join, _Thrd_detach, _Thrd_id](../../../core/libs/prx/libc/src/Threads.cpp) (libc) follow Dinkumware V6.00 `thr/xthread` and `thr/threadcall.cpp` ([crt.vc12 `f1d47126`](https://github.com/ganboing/crt.vc12/tree/f1d47126/src/thr)), otherwise without console verification. `_Thrd_id` returns `scePthreadSelf()` unchanged, as measured on a PS5 (firmware 13.40) in two threads (#4164, #8711); the two-argument `_Launch` form starts with default attributes and the three-argument form forwards the caller's attribute pointer, as PPSA02530's `libc.prx` does at `0x18090`. The 32-byte pad layout is assumed from VC12, unverified. Failing primitives or thread start throw the guest `std::system_error`; null arguments throw. `_Thrd_join` returns `_Thrd_error` (4) on `pthread_join` failure and truncates the exit code to `int`; `_Thrd_detach` returns `_Thrd_error` (4) on `pthread_detach` failure and throws when none is registered - [collate vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `ruZtIwbCFjk`) is nine `uintptr_t` slots matching `st_size` 72 on the title `libc.prx` from PPSA16833 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and three `do_*` virtuals). Slots 2 through 8 hold the same `DestroyCollate`, `DeleteCollate`, `RetainFacet`, `ReleaseFacet`, `CollateCompare`, `CollateTransform` and `CollateHash` functions that `g_collateVtable` uses, so facet calls through the exported object work; offset-to-top and typeinfo remain zero, unlike the console's relocated entries, and `_Getcat` still installs `g_collateVtable` for facets it builds. From ef8d4ecf7c6764fbda3676062dd6c5442155d26b Mon Sep 17 00:00:00 2001 From: Maz Date: Wed, 28 Oct 2026 20:39:35 +0400 Subject: [PATCH 372/612] feat(libc): export the money_get facet vtable as data Titles resolve NID 3n3wCJGFP7o as a 64-byte STT_OBJECT on console libc.prx; export eight zeroed uintptr_t slots so the data binding matches. Facet calls through the object still fault until methods are wired. --- core/libs/prx/libc/include/GuestLocale.hpp | 3 +++ core/libs/prx/libc/src/LocaleSupport.cpp | 1 + core/libs/tests/GuestLocale.cpp | 11 +++++++++++ docs/dev/debt/libc.md | 1 + 4 files changed, 16 insertions(+) diff --git a/core/libs/prx/libc/include/GuestLocale.hpp b/core/libs/prx/libc/include/GuestLocale.hpp index e61ed37d2a..f66cbf68dd 100644 --- a/core/libs/prx/libc/include/GuestLocale.hpp +++ b/core/libs/prx/libc/include/GuestLocale.hpp @@ -273,5 +273,8 @@ extern "C" std::uintptr_t _ZTVSt7codecvtIcc9_MbstatetE_nid_postfix[kCodecvtCharV inline constexpr std::size_t kTimePutCharVtableSlots = 7; static_assert(kTimePutCharVtableSlots * sizeof(std::uintptr_t) == 56); extern "C" std::uintptr_t _ZTVSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[kTimePutCharVtableSlots]; +inline constexpr std::size_t kMoneyGetCharVtableSlots = 8; +static_assert(kMoneyGetCharVtableSlots * sizeof(std::uintptr_t) == 64); +extern "C" std::uintptr_t _ZTVSt9money_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[kMoneyGetCharVtableSlots]; #endif diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index edd1527408..8b47b898de 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -687,6 +687,7 @@ std::uintptr_t _ZTVSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_ni std::uintptr_t _ZTVSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[13] {}; std::uintptr_t _ZTVSt8time_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix[13] {}; std::uintptr_t _ZTVSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix[17] {}; +std::uintptr_t _ZTVSt9money_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[kMoneyGetCharVtableSlots] {}; std::streamoff _ZSt7_BADOFF_nid_postfix = -1; std::fpos_t _ZSt4_Fpz_nid_postfix {}; diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp index 42ea2a99db..2e44999539 100644 --- a/core/libs/tests/GuestLocale.cpp +++ b/core/libs/tests/GuestLocale.cpp @@ -600,6 +600,16 @@ static void CheckTimeGetWideVtableObject() { } } +static void CheckMoneyGetVtableObject() { + Require(sizeof(_ZTVSt9money_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) == kMoneyGetCharVtableSlots * sizeof(std::uintptr_t)); + for (std::uintptr_t& slot : _ZTVSt9money_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } +} + int main() { std::array heapApi{}; heapApi[0] = reinterpret_cast(&TestHeapAllocate); @@ -632,6 +642,7 @@ int main() { CheckWideCtypeVtableObject(); CheckCodecvtVtableObject(); CheckTimePutVtableObject(); + CheckMoneyGetVtableObject(); _Locksyslock_nid_postfix(); _Locksyslock_nid_postfix(); _Unlocksyslock_nid_postfix(); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 85942f15ec..e2503d2fd9 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -299,6 +299,7 @@ - [ctype vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `qdHsu+gIxRo`) is eighteen zeroed `uintptr_t` slots matching `st_size` 144 on the title `libc.prx` from PPSA16833 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and twelve `do_*` virtuals). Facet calls through the object fault until those methods are wired. - [codecvt vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `aK1Ymf-NhAs`) is thirteen zeroed `uintptr_t` slots matching `st_size` 104 on title `libc.prx` from PPSA26344 and on `libSceLibcInternal.sprx` (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and seven conversion virtuals). Facet calls through the object fault until those methods are wired. - [time_put vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `OwfBD-2nhJQ`) is seven zeroed `uintptr_t` slots matching `st_size` 56 on the title `libc.prx` from PPSA26344 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and one `do_put` virtual). Facet calls through the object fault until those methods are wired; `_Getcat` still installs `g_timePutVtable` for facets it builds. +- [money_get vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `3n3wCJGFP7o`) is eight zeroed `uintptr_t` slots matching `st_size` 64 on the title `libc.prx` from PPSA26344 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and two `do_get` virtuals). Facet calls through the object fault until those methods are wired. - [ios_base standard streams](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) `_Addstd` queues the stream in the standard stream table of Dinkumware [ios.cpp](https://github.com/ganboing/crt.vc12/blob/master/src/ios.cpp) (`stdstr`, `stdopens`, `_Addstd`, `_Ios_base_dtor`): it takes the first of slots 1 to 7 that is free or already holds the stream, and slot 8 (`NSTDSTR`) when none is, so slot 8 is shared by every stream past the first seven and registration never fails. It stores the 1-based slot index in the guest `IosBase::standardStream` (Dinkumware's `_Stdstr`, where 0 means not a standard stream) and counts the stream objects registered per slot; registering the same stream again reuses its slot and raises its count. Measured on a PS5 (firmware 13.60) from a homebrew payload calling the system `libSceLibcInternal` export: in a process whose own standard streams already hold slots 1 to 7, twelve distinct streams and a repeated one all get 8, written as a 64-bit value at offset 0x8 with no other byte of the object touched; the assignment of slots 1 to 7 to fresh streams is not observable there and follows VC12. The counts are `int`, not VC12's `char`, and the registration and the destructor lock the table where VC12 locks only `_Addstd`; a slot index past the table throws in the destructor instead of indexing out of bounds. The destructor decrements its slot's count and suppresses the rest of the destruction while the count stays positive, as `_Ios_base_dtor` does, and the slots of destroyed streams keep their pointer and stay reserved for that stream, as `stdstr` does. The streams themselves are destroyed by the title's own code as it registers them through `__cxa_atexit`; when the last reference closes, the unsupported stream storage and callback cleanup is still the throw it was and the locale is released as for a non-standard stream. - [std::_Pad, _Thrd_join, _Thrd_detach, _Thrd_id](../../../core/libs/prx/libc/src/Threads.cpp) (libc) follow Dinkumware V6.00 `thr/xthread` and `thr/threadcall.cpp` ([crt.vc12 `f1d47126`](https://github.com/ganboing/crt.vc12/tree/f1d47126/src/thr)), otherwise without console verification. `_Thrd_id` returns `scePthreadSelf()` unchanged, as measured on a PS5 (firmware 13.40) in two threads (#4164, #8711); the two-argument `_Launch` form starts with default attributes and the three-argument form forwards the caller's attribute pointer, as PPSA02530's `libc.prx` does at `0x18090`. The 32-byte pad layout is assumed from VC12, unverified. Failing primitives or thread start throw the guest `std::system_error`; null arguments throw. `_Thrd_join` returns `_Thrd_error` (4) on `pthread_join` failure and truncates the exit code to `int`; `_Thrd_detach` returns `_Thrd_error` (4) on `pthread_detach` failure and throws when none is registered - [collate vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `ruZtIwbCFjk`) is nine `uintptr_t` slots matching `st_size` 72 on the title `libc.prx` from PPSA16833 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and three `do_*` virtuals). Slots 2 through 8 hold the same `DestroyCollate`, `DeleteCollate`, `RetainFacet`, `ReleaseFacet`, `CollateCompare`, `CollateTransform` and `CollateHash` functions that `g_collateVtable` uses, so facet calls through the exported object work; offset-to-top and typeinfo remain zero, unlike the console's relocated entries, and `_Getcat` still installs `g_collateVtable` for facets it builds. From e5e1f79a432f41d9e7569d56fc1495586ccce7d3 Mon Sep 17 00:00:00 2001 From: Maz Date: Fri, 23 Oct 2026 15:11:42 +0400 Subject: [PATCH 373/612] feat(libc): export the messages facet vtable as data Titles that resolve messages's vtable as data need a 72-byte object matching title libc.prx (PPSA26344). --- core/libs/prx/libc/src/LocaleSupport.cpp | 2 ++ core/libs/tests/GuestLocale.cpp | 10 ++++++++++ docs/dev/debt/libc.md | 2 ++ 3 files changed, 14 insertions(+) diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index 1c79844a1c..4713cd1ba7 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -670,6 +670,8 @@ std::uintptr_t _ZTVSt7collateIcE_nid_postfix[9] { reinterpret_cast(&CollateTransform), reinterpret_cast(&CollateHash), }; +std::uintptr_t _ZTVSt7collateIcE_nid_postfix[9] {}; +std::uintptr_t _ZTVSt8messagesIcE_nid_postfix[9] {}; std::uint64_t _ZNSt7codecvtIcc9_MbstatetE2idE_nid_postfix = 0; std::uintptr_t _ZTVSt7codecvtIwc9_MbstatetE_nid_postfix[13] {}; std::uintptr_t _ZTVSt7codecvtIcc9_MbstatetE_nid_postfix[kCodecvtCharVtableSlots] {}; diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp index 78280f321e..ddc6505da4 100644 --- a/core/libs/tests/GuestLocale.cpp +++ b/core/libs/tests/GuestLocale.cpp @@ -33,6 +33,7 @@ extern std::uintptr_t _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIc extern std::uintptr_t _ZTVSt5ctypeIcE_nid_postfix[14]; extern std::uintptr_t _ZTVSt5ctypeIwE_nid_postfix[18]; extern std::uintptr_t _ZTVSt7codecvtIcc9_MbstatetE_nid_postfix[13]; +extern std::uintptr_t _ZTVSt8messagesIcE_nid_postfix[9]; } @@ -101,6 +102,14 @@ static void CheckCollateVtableObject() { Require(_ZNKSt7collateIcE9transformEPKcS2__nid_postfix(&result, &facet, "xyz", "xyz" + 3) == &result); Require(result.size == 3 && result.capacity == 15 && std::memcmp(result.buffer, "xyz", 3) == 0); Require(vtable->hash(&facet, "abc", "abc" + 3) == -1792535898324117685LL); + for (std::uintptr_t& slot : _ZTVSt7collateIcE_nid_postfix) { +static void CheckMessagesVtableObject() { + for (std::uintptr_t& slot : _ZTVSt8messagesIcE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } } static void CheckCodecvtVtableObject() { @@ -621,6 +630,7 @@ int main() { CheckCtypeVtableObject(); CheckWideCtypeVtableObject(); CheckCodecvtVtableObject(); + CheckMessagesVtableObject(); _Locksyslock_nid_postfix(); _Locksyslock_nid_postfix(); _Unlocksyslock_nid_postfix(); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 2bbaa32db2..137b9a3870 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -303,6 +303,8 @@ - [collate vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `ruZtIwbCFjk`) is nine `uintptr_t` slots matching `st_size` 72 on the title `libc.prx` from PPSA16833 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and three `do_*` virtuals). Slots 2 through 8 hold the same `DestroyCollate`, `DeleteCollate`, `RetainFacet`, `ReleaseFacet`, `CollateCompare`, `CollateTransform` and `CollateHash` functions that `g_collateVtable` uses, so facet calls through the exported object work; offset-to-top and typeinfo remain zero, unlike the console's relocated entries, and `_Getcat` still installs `g_collateVtable` for facets it builds. - [num_get::id, time_get::id and the num_get vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) are data objects, as the public SDK's libSceLibcInternal table declares them and as the other facet ids and the `num_put` and `codecvt` vtables are kept: the ids start at zero and the vtable is 17 zeroed slots, the 136-byte size of `KfcTPbeaOqg` in the console's libSceLibcInternal, whose relocations fill the offset-to-top and typeinfo entries, the two destructors, `locale::facet::_Incref` and `_Decref`, and the eleven `do_get` of Dinkumware's crt.vc12 `xlocnum`, so a guest resolves the symbols and reads data, and a facet call through the vtable faults. libSceAmpr exported the three as throwing functions, which a data consumer reads as code; its qualified imports fall back to libc. - [printf family conversions](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) - Windows `FormatWindows` follows the title `libc.prx` `_Printf` measured from PPSA13934, PPSA07923, PPSA05916 and PPSA04158: `%q`, `%D`, `%O` and `%U` are unknown conversions; an unknown character is printed with its leading `%`, and width, `-` and discarded length modifiers behave as measured (`%5y` is ` %y`, `%hhz` is `%z`). `%%` takes a width. A trailing `%` reads past the format's NUL and is not reproduced; it throws. `#`, precision and a `0` without a width are ignored, as a payload process on a PS5 (firmware 13.60) printed `%#k` and `%.2k` as `%k`; `+`, space and `0` are ignored on an unknown conversion and a width pads it with spaces, in the `_s` forms too, and `'` is itself an unknown conversion (`%'d` prints `%'d`), as the `libc.prx` of PPSA07923 and PPSA13934 does. On Linux a format with an unknown conversion also goes to this formatter +- [messages vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `FnEnECMJGag`) is nine zeroed `uintptr_t` slots matching `st_size` 72 on title `libc.prx` from PPSA26344 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and three `do_*` virtuals). Facet calls through the object fault until those methods are wired. +- [printf family conversions](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) - Windows `FormatWindows` follows the title `libc.prx` `_Printf` measured from PPSA13934, PPSA07923, PPSA05916 and PPSA04158: `%q`, `%D`, `%O` and `%U` are unknown conversions; an unknown character is printed with its leading `%`, and width, `-` and discarded length modifiers behave as measured (`%5y` is ` %y`, `%hhz` is `%z`). `%%` takes a width. A trailing `%` reads past the format's NUL and is not reproduced; it throws. `#`, precision and a `0` without a width are ignored, as a payload process on a PS5 (firmware 13.60) printed `%#k` and `%.2k` as `%k`; `+`, space, `'` and `0` with a width on an unknown conversion are unverified and throw. On Linux a format with an unknown conversion also goes to this formatter - [_Cnd_register_at_thread_exit, _Cnd_unregister_at_thread_exit and _Cnd_do_broadcast_at_thread_exit](../../../core/libs/prx/libc/src/Threads.cpp) (libc) keep registrations in a process-wide list keyed by the host thread id, as Microsoft's [xnotify.cpp](https://github.com/microsoft/STL/blob/main/stl/src/xnotify.cpp) does: registering validates both handles, unregistering drops every slot that holds the mutex, and the broadcast of the current thread sets the supplied flag to 1, broadcasts the condition and unlocks the mutex, in that order. The exit path splices nodes, does not allocate and is `noexcept`. The handles are passed by value, following the modern UCRT signature; the VC12 form and the console signature are not verified. The broadcast runs when a `std::thread` exits through `_Pad::_Launch` and when a title calls `_Thrd_exit`; a libkernel `scePthreadCreate` thread that returns without `_Thrd_exit` never broadcasts and keeps its registration. - [printf length modifiers](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) - the `_s` forms (`snprintf_s`, `vswprintf_s` and the others) still throw for a length modifier that does not apply to its conversion, unmeasured on the console. The plain forms ignore it as the `libc.prx` of PPSA07923 and PPSA13934 does: `L` on an integer conversion or `%n`, any length but `l` on `%c` and `%s`, any but `L` on a floating conversion, and any on `%p`. - [child process registry](../../../core/libs/prx/libc/src/Process.cpp) (libc) - `RegisterChildProcess_nid_no_patch` and `WaitChildProcess_nid_no_patch` track the native handles of children created by `posix_spawn` ([libSceLibcInternal](../../../core/libs/prx/libSceLibcInternal/Spawn.cpp)) so libkernel can reap them on Windows, where the host has no `waitpid`. The registry lives in libc because libSceLibcInternal imports libc but not libkernel. `RegisterChildProcess_nid_no_patch` stores the process ID and handle; only reaped handles are closed, and a process that exits without waiting leaks them. Registration without a live child is not validated, and a duplicate process ID is accepted. The decoder maps recognized structured-exception codes to guest signals, keeping the guest `exit` byte layout for anything else. These exports are internal glue for libkernel, not console ABI: their behavior is unverified against any PS5 library and a title importing their NIDs directly is not expected. From 77ae839cf7ee31fb56716a310c42468740aa6ce7 Mon Sep 17 00:00:00 2001 From: Maz Date: Sun, 25 Oct 2026 23:40:47 +0400 Subject: [PATCH 374/612] fix(libc): restore separate GuestLocale vtable helpers --- core/libs/tests/GuestLocale.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp index ddc6505da4..80e322268e 100644 --- a/core/libs/tests/GuestLocale.cpp +++ b/core/libs/tests/GuestLocale.cpp @@ -103,6 +103,13 @@ static void CheckCollateVtableObject() { Require(result.size == 3 && result.capacity == 15 && std::memcmp(result.buffer, "xyz", 3) == 0); Require(vtable->hash(&facet, "abc", "abc" + 3) == -1792535898324117685LL); for (std::uintptr_t& slot : _ZTVSt7collateIcE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } +} + static void CheckMessagesVtableObject() { for (std::uintptr_t& slot : _ZTVSt8messagesIcE_nid_postfix) { Require(slot == 0); From f10408c3561734069db37d615cd37c8e74c1c307 Mon Sep 17 00:00:00 2001 From: Maz Date: Tue, 27 Oct 2026 01:45:51 +0400 Subject: [PATCH 375/612] fix(libc): clean duplicate locale symbols after rebase --- core/libs/prx/libc/src/LocaleSupport.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index 4713cd1ba7..f18c7da62a 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -670,7 +670,6 @@ std::uintptr_t _ZTVSt7collateIcE_nid_postfix[9] { reinterpret_cast(&CollateTransform), reinterpret_cast(&CollateHash), }; -std::uintptr_t _ZTVSt7collateIcE_nid_postfix[9] {}; std::uintptr_t _ZTVSt8messagesIcE_nid_postfix[9] {}; std::uint64_t _ZNSt7codecvtIcc9_MbstatetE2idE_nid_postfix = 0; std::uintptr_t _ZTVSt7codecvtIwc9_MbstatetE_nid_postfix[13] {}; From 495c7a070bec6ef99bb61fc50c093e97e4258762 Mon Sep 17 00:00:00 2001 From: Maz Date: Tue, 27 Oct 2026 10:16:12 +0400 Subject: [PATCH 376/612] fix(libc): drop collate all-zero loop after functional checks --- core/libs/tests/GuestLocale.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp index 80e322268e..280bb7f80d 100644 --- a/core/libs/tests/GuestLocale.cpp +++ b/core/libs/tests/GuestLocale.cpp @@ -102,12 +102,6 @@ static void CheckCollateVtableObject() { Require(_ZNKSt7collateIcE9transformEPKcS2__nid_postfix(&result, &facet, "xyz", "xyz" + 3) == &result); Require(result.size == 3 && result.capacity == 15 && std::memcmp(result.buffer, "xyz", 3) == 0); Require(vtable->hash(&facet, "abc", "abc" + 3) == -1792535898324117685LL); - for (std::uintptr_t& slot : _ZTVSt7collateIcE_nid_postfix) { - Require(slot == 0); - slot = 0x1111; - Require(slot == 0x1111); - slot = 0; - } } static void CheckMessagesVtableObject() { From 83ce30cb7a539b7d44a1b148d575ffa956943f87 Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Thu, 5 Nov 2026 18:40:59 +1300 Subject: [PATCH 377/612] docs(libc): drop the older printf conversions entry this branch duplicated --- docs/dev/debt/libc.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 137b9a3870..1238fa22c9 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -304,7 +304,6 @@ - [num_get::id, time_get::id and the num_get vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) are data objects, as the public SDK's libSceLibcInternal table declares them and as the other facet ids and the `num_put` and `codecvt` vtables are kept: the ids start at zero and the vtable is 17 zeroed slots, the 136-byte size of `KfcTPbeaOqg` in the console's libSceLibcInternal, whose relocations fill the offset-to-top and typeinfo entries, the two destructors, `locale::facet::_Incref` and `_Decref`, and the eleven `do_get` of Dinkumware's crt.vc12 `xlocnum`, so a guest resolves the symbols and reads data, and a facet call through the vtable faults. libSceAmpr exported the three as throwing functions, which a data consumer reads as code; its qualified imports fall back to libc. - [printf family conversions](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) - Windows `FormatWindows` follows the title `libc.prx` `_Printf` measured from PPSA13934, PPSA07923, PPSA05916 and PPSA04158: `%q`, `%D`, `%O` and `%U` are unknown conversions; an unknown character is printed with its leading `%`, and width, `-` and discarded length modifiers behave as measured (`%5y` is ` %y`, `%hhz` is `%z`). `%%` takes a width. A trailing `%` reads past the format's NUL and is not reproduced; it throws. `#`, precision and a `0` without a width are ignored, as a payload process on a PS5 (firmware 13.60) printed `%#k` and `%.2k` as `%k`; `+`, space and `0` are ignored on an unknown conversion and a width pads it with spaces, in the `_s` forms too, and `'` is itself an unknown conversion (`%'d` prints `%'d`), as the `libc.prx` of PPSA07923 and PPSA13934 does. On Linux a format with an unknown conversion also goes to this formatter - [messages vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `FnEnECMJGag`) is nine zeroed `uintptr_t` slots matching `st_size` 72 on title `libc.prx` from PPSA26344 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and three `do_*` virtuals). Facet calls through the object fault until those methods are wired. -- [printf family conversions](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) - Windows `FormatWindows` follows the title `libc.prx` `_Printf` measured from PPSA13934, PPSA07923, PPSA05916 and PPSA04158: `%q`, `%D`, `%O` and `%U` are unknown conversions; an unknown character is printed with its leading `%`, and width, `-` and discarded length modifiers behave as measured (`%5y` is ` %y`, `%hhz` is `%z`). `%%` takes a width. A trailing `%` reads past the format's NUL and is not reproduced; it throws. `#`, precision and a `0` without a width are ignored, as a payload process on a PS5 (firmware 13.60) printed `%#k` and `%.2k` as `%k`; `+`, space, `'` and `0` with a width on an unknown conversion are unverified and throw. On Linux a format with an unknown conversion also goes to this formatter - [_Cnd_register_at_thread_exit, _Cnd_unregister_at_thread_exit and _Cnd_do_broadcast_at_thread_exit](../../../core/libs/prx/libc/src/Threads.cpp) (libc) keep registrations in a process-wide list keyed by the host thread id, as Microsoft's [xnotify.cpp](https://github.com/microsoft/STL/blob/main/stl/src/xnotify.cpp) does: registering validates both handles, unregistering drops every slot that holds the mutex, and the broadcast of the current thread sets the supplied flag to 1, broadcasts the condition and unlocks the mutex, in that order. The exit path splices nodes, does not allocate and is `noexcept`. The handles are passed by value, following the modern UCRT signature; the VC12 form and the console signature are not verified. The broadcast runs when a `std::thread` exits through `_Pad::_Launch` and when a title calls `_Thrd_exit`; a libkernel `scePthreadCreate` thread that returns without `_Thrd_exit` never broadcasts and keeps its registration. - [printf length modifiers](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) - the `_s` forms (`snprintf_s`, `vswprintf_s` and the others) still throw for a length modifier that does not apply to its conversion, unmeasured on the console. The plain forms ignore it as the `libc.prx` of PPSA07923 and PPSA13934 does: `L` on an integer conversion or `%n`, any length but `l` on `%c` and `%s`, any but `L` on a floating conversion, and any on `%p`. - [child process registry](../../../core/libs/prx/libc/src/Process.cpp) (libc) - `RegisterChildProcess_nid_no_patch` and `WaitChildProcess_nid_no_patch` track the native handles of children created by `posix_spawn` ([libSceLibcInternal](../../../core/libs/prx/libSceLibcInternal/Spawn.cpp)) so libkernel can reap them on Windows, where the host has no `waitpid`. The registry lives in libc because libSceLibcInternal imports libc but not libkernel. `RegisterChildProcess_nid_no_patch` stores the process ID and handle; only reaped handles are closed, and a process that exits without waiting leaks them. Registration without a live child is not validated, and a duplicate process ID is accepted. The decoder maps recognized structured-exception codes to guest signals, keeping the guest `exit` byte layout for anything else. These exports are internal glue for libkernel, not console ABI: their behavior is unverified against any PS5 library and a title importing their NIDs directly is not expected. From 05ddcfc25708ce93c375dae09fab65650a1d6542 Mon Sep 17 00:00:00 2001 From: Maz Date: Fri, 23 Oct 2026 16:58:26 +0400 Subject: [PATCH 378/612] feat(libc): export the numpunct facet vtable as data Titles that resolve lJnP-cn0cvQ as data need an 88-byte object matching console title libc.prx. --- core/libs/prx/libc/src/LocaleSupport.cpp | 2 ++ core/libs/tests/GuestLocale.cpp | 10 ++++++++++ docs/dev/debt/libc.md | 1 + 3 files changed, 13 insertions(+) diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index 1c79844a1c..d7e34b6794 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -670,6 +670,8 @@ std::uintptr_t _ZTVSt7collateIcE_nid_postfix[9] { reinterpret_cast(&CollateTransform), reinterpret_cast(&CollateHash), }; +std::uintptr_t _ZTVSt7collateIcE_nid_postfix[9] {}; +std::uintptr_t _ZTVSt8numpunctIcE_nid_postfix[11] {}; std::uint64_t _ZNSt7codecvtIcc9_MbstatetE2idE_nid_postfix = 0; std::uintptr_t _ZTVSt7codecvtIwc9_MbstatetE_nid_postfix[13] {}; std::uintptr_t _ZTVSt7codecvtIcc9_MbstatetE_nid_postfix[kCodecvtCharVtableSlots] {}; diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp index 78280f321e..9aa3e3d56a 100644 --- a/core/libs/tests/GuestLocale.cpp +++ b/core/libs/tests/GuestLocale.cpp @@ -33,6 +33,7 @@ extern std::uintptr_t _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIc extern std::uintptr_t _ZTVSt5ctypeIcE_nid_postfix[14]; extern std::uintptr_t _ZTVSt5ctypeIwE_nid_postfix[18]; extern std::uintptr_t _ZTVSt7codecvtIcc9_MbstatetE_nid_postfix[13]; +extern std::uintptr_t _ZTVSt8numpunctIcE_nid_postfix[11]; } @@ -101,6 +102,14 @@ static void CheckCollateVtableObject() { Require(_ZNKSt7collateIcE9transformEPKcS2__nid_postfix(&result, &facet, "xyz", "xyz" + 3) == &result); Require(result.size == 3 && result.capacity == 15 && std::memcmp(result.buffer, "xyz", 3) == 0); Require(vtable->hash(&facet, "abc", "abc" + 3) == -1792535898324117685LL); + for (std::uintptr_t& slot : _ZTVSt7collateIcE_nid_postfix) { +static void CheckNumpunctVtableObject() { + for (std::uintptr_t& slot : _ZTVSt8numpunctIcE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } } static void CheckCodecvtVtableObject() { @@ -621,6 +630,7 @@ int main() { CheckCtypeVtableObject(); CheckWideCtypeVtableObject(); CheckCodecvtVtableObject(); + CheckNumpunctVtableObject(); _Locksyslock_nid_postfix(); _Locksyslock_nid_postfix(); _Unlocksyslock_nid_postfix(); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 2bbaa32db2..72a78f09bc 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -295,6 +295,7 @@ - [time_get vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `sEca1nUOueA`) is thirteen zeroed `uintptr_t` slots matching `st_size` 104 on console `libSceLibcInternal.sprx` (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and seven `do_*` virtuals). Facet calls through the object fault until those methods are wired. - [num_get vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `Y9C9GeKyZ3A`) is seventeen zeroed `uintptr_t` slots matching `st_size` 136 on console `libSceLibcInternal.sprx` (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and eleven `do_get` virtuals). Facet calls through the object fault until those methods are wired. - [num_put vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `1kZFcktOm+s`) is fourteen zeroed `uintptr_t` slots matching `st_size` 112 on the title `libc.prx` from PPSA16833 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and eight `do_put` virtuals). Facet calls through the object fault until those methods are wired. +- [numpunct vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `lJnP-cn0cvQ`) is eleven zeroed `uintptr_t` slots matching `st_size` 88 on title `libc.prx` from PPSA26344 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and five `do_*` virtuals). Facet calls through the object fault until those methods are wired. - [ctype vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `GKWcAz6-G7k`) is fourteen zeroed `uintptr_t` slots matching `st_size` 112 on the title `libc.prx` from PPSA16833 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and eight `do_*` virtuals). Facet calls through the object fault until those methods are wired. - [ctype vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `qdHsu+gIxRo`) is eighteen zeroed `uintptr_t` slots matching `st_size` 144 on the title `libc.prx` from PPSA16833 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and twelve `do_*` virtuals). Facet calls through the object fault until those methods are wired. - [codecvt vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `aK1Ymf-NhAs`) is thirteen zeroed `uintptr_t` slots matching `st_size` 104 on title `libc.prx` from PPSA26344 and on `libSceLibcInternal.sprx` (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and seven conversion virtuals). Facet calls through the object fault until those methods are wired. From b3add985c24d1912385241e569339d71a92a4058 Mon Sep 17 00:00:00 2001 From: Maz Date: Sun, 25 Oct 2026 23:40:40 +0400 Subject: [PATCH 379/612] fix(libc): restore separate GuestLocale vtable helpers --- core/libs/tests/GuestLocale.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp index 9aa3e3d56a..d0ccd0deb4 100644 --- a/core/libs/tests/GuestLocale.cpp +++ b/core/libs/tests/GuestLocale.cpp @@ -103,6 +103,13 @@ static void CheckCollateVtableObject() { Require(result.size == 3 && result.capacity == 15 && std::memcmp(result.buffer, "xyz", 3) == 0); Require(vtable->hash(&facet, "abc", "abc" + 3) == -1792535898324117685LL); for (std::uintptr_t& slot : _ZTVSt7collateIcE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } +} + static void CheckNumpunctVtableObject() { for (std::uintptr_t& slot : _ZTVSt8numpunctIcE_nid_postfix) { Require(slot == 0); From f9b1b94edad80911efcc36acf1c2185ca12bd3a8 Mon Sep 17 00:00:00 2001 From: Maz Date: Tue, 27 Oct 2026 01:45:44 +0400 Subject: [PATCH 380/612] fix(libc): clean duplicate locale symbols after rebase --- core/libs/prx/libc/src/LocaleSupport.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index d7e34b6794..0821ec2173 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -670,7 +670,6 @@ std::uintptr_t _ZTVSt7collateIcE_nid_postfix[9] { reinterpret_cast(&CollateTransform), reinterpret_cast(&CollateHash), }; -std::uintptr_t _ZTVSt7collateIcE_nid_postfix[9] {}; std::uintptr_t _ZTVSt8numpunctIcE_nid_postfix[11] {}; std::uint64_t _ZNSt7codecvtIcc9_MbstatetE2idE_nid_postfix = 0; std::uintptr_t _ZTVSt7codecvtIwc9_MbstatetE_nid_postfix[13] {}; From 5bb2a1d1e58ca47de65fc241a756a8706634f60e Mon Sep 17 00:00:00 2001 From: Maz Date: Tue, 27 Oct 2026 02:26:13 +0400 Subject: [PATCH 381/612] fix(libc): drop collate all-zero loop and restore SPIRV pins --- core/libs/tests/GuestLocale.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp index d0ccd0deb4..376ef377f1 100644 --- a/core/libs/tests/GuestLocale.cpp +++ b/core/libs/tests/GuestLocale.cpp @@ -102,12 +102,6 @@ static void CheckCollateVtableObject() { Require(_ZNKSt7collateIcE9transformEPKcS2__nid_postfix(&result, &facet, "xyz", "xyz" + 3) == &result); Require(result.size == 3 && result.capacity == 15 && std::memcmp(result.buffer, "xyz", 3) == 0); Require(vtable->hash(&facet, "abc", "abc" + 3) == -1792535898324117685LL); - for (std::uintptr_t& slot : _ZTVSt7collateIcE_nid_postfix) { - Require(slot == 0); - slot = 0x1111; - Require(slot == 0x1111); - slot = 0; - } } static void CheckNumpunctVtableObject() { From d66a92f4750cc6e3ab506587bc67ec5da00cdfd7 Mon Sep 17 00:00:00 2001 From: atharvjairath <54663702+atharvjairath@users.noreply.github.com> Date: Mon, 2 Nov 2026 16:18:42 +0530 Subject: [PATCH 382/612] feat(libressl3): implement key size, security and comparison queries --- .../prx/libSceLibreSsl3/AdditionalExports.cpp | 8 - core/libs/prx/libSceLibreSsl3/CMakeLists.txt | 14 ++ core/libs/prx/libSceLibreSsl3/Export.cpp | 5 - core/libs/prx/libSceLibreSsl3/include/Api.hpp | 6 +- .../prx/libSceLibreSsl3/src/PkeyQueries.cpp | 59 +++++ .../prx/libSceLibreSsl3/tests/PkeyQueries.cpp | 213 ++++++++++++++++++ docs/dev/debt/libSceLibreSsl3.md | 8 +- 7 files changed, 293 insertions(+), 20 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl3/src/PkeyQueries.cpp create mode 100644 core/libs/prx/libSceLibreSsl3/tests/PkeyQueries.cpp diff --git a/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp b/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp index df0619e169..ba182c5a95 100644 --- a/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp +++ b/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp @@ -335,10 +335,6 @@ int APS5_VABI X509_get_ext_by_NID_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI EVP_PKEY_security_bits_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI X509_check_akid_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -653,10 +649,6 @@ int APS5_VABI tls_config_verify_client_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI EVP_PKEY_cmp_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI d2i_ASN1_SEQUENCE_ANY_nid_postfix() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt index 99d71c09e2..42e9f95fe4 100644 --- a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt @@ -145,3 +145,17 @@ if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL "Linux" AND EXISTS "${LIBS_INCLU -- $) set_tests_properties(guest_libressl3_mixed_evp PROPERTIES TIMEOUT 20) endif() + +target_sources(libSceLibreSsl3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/PkeyQueries.cpp) +if(BUILD_TESTING) + add_test_executable(guest_libressl3_pkey_queries_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/PkeyQueries.cpp) + target_include_directories(guest_libressl3_pkey_queries_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl3_pkey_queries_tests PRIVATE libSceLibreSsl3) + configure_windows_unwind(guest_libressl3_pkey_queries_tests) + add_test(NAME guest_libressl3_pkey_queries COMMAND guest_libressl3_pkey_queries_tests) + set_tests_properties(guest_libressl3_pkey_queries PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_libressl3_pkey_queries PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibreSsl3/Export.cpp b/core/libs/prx/libSceLibreSsl3/Export.cpp index 47dc6effdf..5008a5b7fb 100644 --- a/core/libs/prx/libSceLibreSsl3/Export.cpp +++ b/core/libs/prx/libSceLibreSsl3/Export.cpp @@ -139,11 +139,6 @@ int APS5_VABI EVP_PKEY_set1_EC_KEY_nid_postfix(void) { return 0; } -int APS5_VABI EVP_PKEY_size_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI EVP_SignFinal_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl3/include/Api.hpp b/core/libs/prx/libSceLibreSsl3/include/Api.hpp index cfd1bc9bdd..224afad9a2 100644 --- a/core/libs/prx/libSceLibreSsl3/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl3/include/Api.hpp @@ -65,7 +65,7 @@ void* APS5_VABI EVP_PKEY_get0_DSA_nid_postfix(LibreSsl3EvpPkey* key); LibreSsl3Rsa* APS5_VABI EVP_PKEY_get0_RSA_nid_postfix(LibreSsl3EvpPkey* key); int APS5_VABI EVP_PKEY_new_mac_key_nid_postfix(void); int APS5_VABI EVP_PKEY_set1_EC_KEY_nid_postfix(void); -int APS5_VABI EVP_PKEY_size_nid_postfix(void); +int APS5_VABI EVP_PKEY_size_nid_postfix(const LibreSsl3EvpPkey* key); int APS5_VABI EVP_PKEY_up_ref_nid_postfix(LibreSsl3EvpPkey* key); int APS5_VABI EVP_SignFinal_nid_postfix(void); int APS5_VABI EVP_VerifyFinal_nid_postfix(void); @@ -209,7 +209,7 @@ int APS5_VABI tls_config_set_key_mem_nid_postfix(); int APS5_VABI Unknown_AiNud8LR8B4_nid_no_patch(); int APS5_VABI X509_cmp_current_time_nid_postfix(); int APS5_VABI X509_get_ext_by_NID_nid_postfix(); -int APS5_VABI EVP_PKEY_security_bits_nid_postfix(); +int APS5_VABI EVP_PKEY_security_bits_nid_postfix(const LibreSsl3EvpPkey* key); int APS5_VABI X509_check_akid_nid_postfix(); int APS5_VABI SSL_set_quic_use_legacy_codepoint_nid_postfix(); int APS5_VABI SSL_CTX_set_default_verify_paths_nid_postfix(); @@ -287,7 +287,7 @@ int APS5_VABI EC_POINT_dup_nid_postfix(); int APS5_VABI EVP_aes_128_ccm_nid_postfix(); int APS5_VABI RSA_set0_key_nid_postfix(); int APS5_VABI tls_config_verify_client_nid_postfix(); -int APS5_VABI EVP_PKEY_cmp_nid_postfix(); +int APS5_VABI EVP_PKEY_cmp_nid_postfix(const LibreSsl3EvpPkey* first, const LibreSsl3EvpPkey* second); int APS5_VABI d2i_ASN1_SEQUENCE_ANY_nid_postfix(); int APS5_VABI PKCS5_PBKDF2_HMAC_nid_postfix(); int APS5_VABI ASN1_OCTET_STRING_free_nid_postfix(); diff --git a/core/libs/prx/libSceLibreSsl3/src/PkeyQueries.cpp b/core/libs/prx/libSceLibreSsl3/src/PkeyQueries.cpp new file mode 100644 index 0000000000..6a5a06e372 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/src/PkeyQueries.cpp @@ -0,0 +1,59 @@ +#include "prx/libSceLibreSsl3/libSceLibreSsl3.hpp" + +#include +#include + +namespace { + +using PkeyQuery = int (APS5_VABI *)(const LibreSsl3EvpPkey*); +using PkeyCompare = int (APS5_VABI *)(const LibreSsl3EvpPkey*, const LibreSsl3EvpPkey*); + +static_assert(sizeof(PkeyQuery) == 8); +static_assert(sizeof(PkeyCompare) == 8); +static_assert(offsetof(LibreSsl3EvpPkey, method) == 8); + +template +TCallback ReadCallback_nid_no_patch(const void* method, std::size_t offset) { + TCallback callback; + std::memcpy(&callback, static_cast(method) + offset, sizeof(callback)); + return callback; +} + +} // namespace + +extern "C" { + +int APS5_VABI EVP_PKEY_size_nid_postfix(const LibreSsl3EvpPkey* key) { + if (!key || !key->method) + return 0; + const auto size = ReadCallback_nid_no_patch(key->method, 96); + return size ? size(key) : 0; +} + +int APS5_VABI EVP_PKEY_security_bits_nid_postfix(const LibreSsl3EvpPkey* key) { + if (!key) + return 0; + if (!key->method) + return -2; + const auto securityBits = ReadCallback_nid_no_patch(key->method, 112); + return securityBits ? securityBits(key) : -2; +} + +int APS5_VABI EVP_PKEY_cmp_nid_postfix(const LibreSsl3EvpPkey* first, const LibreSsl3EvpPkey* second) { + if (!first || !second) + throw std::invalid_argument("EVP_PKEY_cmp requires two keys"); + if (first->type != second->type) + return -1; + if (!first->method) + return -2; + const auto compareParameters = ReadCallback_nid_no_patch(first->method, 160); + if (compareParameters) { + const int result = compareParameters(first, second); + if (result <= 0) + return result; + } + const auto comparePublic = ReadCallback_nid_no_patch(first->method, 56); + return comparePublic ? comparePublic(first, second) : -2; +} + +} diff --git a/core/libs/prx/libSceLibreSsl3/tests/PkeyQueries.cpp b/core/libs/prx/libSceLibreSsl3/tests/PkeyQueries.cpp new file mode 100644 index 0000000000..1eedb36135 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/tests/PkeyQueries.cpp @@ -0,0 +1,213 @@ +#include "prx/libSceLibreSsl3/libSceLibreSsl3.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { + +using MethodBytes = std::array; + +int checks = 0; +int cases = 0; +int queryCalls = 0; +int parameterCalls = 0; +int publicCalls = 0; +int queryResult = 0; +int parameterResult = 0; +int publicResult = 0; +const LibreSsl3EvpPkey* expectedFirst; +const LibreSsl3EvpPkey* expectedSecond; +const void* replacementMethod; + +void Require(bool condition, const char* message) { + ++checks; + if (!condition) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +template +void SetCallback(MethodBytes& method, std::size_t offset, TCallback callback) { + std::memcpy(method.data() + offset, &callback, sizeof(callback)); +} + +int APS5_VABI Query(const LibreSsl3EvpPkey* key) { + Require(key == expectedFirst, "query key identity"); + ++queryCalls; + return queryResult; +} + +int APS5_VABI CompareParameters(const LibreSsl3EvpPkey* first, const LibreSsl3EvpPkey* second) { + Require(first == expectedFirst && second == expectedSecond, "parameter operands"); + ++parameterCalls; + if (replacementMethod) + const_cast(first)->method = replacementMethod; + return parameterResult; +} + +int APS5_VABI ComparePublic(const LibreSsl3EvpPkey* first, const LibreSsl3EvpPkey* second) { + Require(first == expectedFirst && second == expectedSecond, "public operands"); + ++publicCalls; + return publicResult; +} + +int APS5_VABI WrongQuery(const LibreSsl3EvpPkey*) { + Require(false, "query dispatched neighboring callback"); + return 0; +} + +int APS5_VABI WrongComparison(const LibreSsl3EvpPkey*, const LibreSsl3EvpPkey*) { + Require(false, "comparison dispatched neighboring callback"); + return 0; +} + +int APS5_VABI ThrowQuery(const LibreSsl3EvpPkey*) { + throw std::runtime_error("query callback failure"); +} + +int APS5_VABI ThrowComparison(const LibreSsl3EvpPkey*, const LibreSsl3EvpPkey*) { + throw std::runtime_error("comparison callback failure"); +} + +template +void RequireThrows(TAction action) { + bool caught = false; + try { + action(); + } catch (const TException&) { + caught = true; + } + Require(caught, "required exception missing"); +} + +template +auto Snapshot(const TObject& object) { + std::array bytes; + std::memcpy(bytes.data(), &object, bytes.size()); + return bytes; +} + +constexpr std::array Results{std::numeric_limits::min(), -7, -2, -1, 0, 1, 2, 128, 256, 4096, std::numeric_limits::max()}; + +void CheckQueries() { + Require(EVP_PKEY_size_nid_postfix(nullptr) == 0, "null key size"); + Require(EVP_PKEY_security_bits_nid_postfix(nullptr) == 0, "null key security bits"); + std::array payload{}; + LibreSsl3EvpPkey key{6, 17, nullptr, payload.data(), -3}; + const auto original = Snapshot(key); + Require(EVP_PKEY_size_nid_postfix(&key) == 0, "missing size method"); + Require(EVP_PKEY_security_bits_nid_postfix(&key) == -2, "missing security method"); + Require(Snapshot(key) == original, "missing-method queries mutated key"); + MethodBytes method; + method.fill(0xa5); + for (std::size_t offset : {88, 104, 120}) + SetCallback(method, offset, WrongQuery); + key.method = method.data(); + expectedFirst = &key; + for (int mask = 0; mask != 4; ++mask) { + SetCallback(method, 96, (mask & 1) ? Query : nullptr); + SetCallback(method, 112, (mask & 2) ? Query : nullptr); + for (int result : Results) { + ++cases; + queryResult = result; + const auto before = Snapshot(key); + const auto methodBefore = method; + const auto payloadBefore = payload; + int calls = queryCalls; + Require(EVP_PKEY_size_nid_postfix(&key) == ((mask & 1) ? result : 0), "size callback result"); + Require(queryCalls == calls + ((mask & 1) != 0), "size callback count"); + calls = queryCalls; + Require(EVP_PKEY_security_bits_nid_postfix(&key) == ((mask & 2) ? result : -2), "security callback result"); + Require(queryCalls == calls + ((mask & 2) != 0), "security callback count"); + Require(Snapshot(key) == before && method == methodBefore && payload == payloadBefore, "query mutated input"); + } + } + SetCallback(method, 96, ThrowQuery); + SetCallback(method, 112, ThrowQuery); + RequireThrows([&] { EVP_PKEY_size_nid_postfix(&key); }); + RequireThrows([&] { EVP_PKEY_security_bits_nid_postfix(&key); }); +} + +void CheckComparison() { + std::array firstPayload{}; + std::array secondPayload{}; + MethodBytes method; + method.fill(0x5a); + for (std::size_t offset : {48, 64, 152, 168}) + SetCallback(method, offset, WrongComparison); + LibreSsl3EvpPkey first{6, 41, method.data(), firstPayload.data(), 1}; + LibreSsl3EvpPkey second{6, 73, nullptr, secondPayload.data(), 0}; + expectedFirst = &first; + expectedSecond = &second; + RequireThrows([&] { EVP_PKEY_cmp_nid_postfix(nullptr, &second); }); + RequireThrows([&] { EVP_PKEY_cmp_nid_postfix(&first, nullptr); }); + RequireThrows([&] { EVP_PKEY_cmp_nid_postfix(nullptr, nullptr); }); + for (int mask = 0; mask != 16; ++mask) { + first.type = (mask & 1) ? 6 : 912; + first.method = (mask & 2) ? method.data() : nullptr; + SetCallback(method, 160, (mask & 4) ? CompareParameters : nullptr); + SetCallback(method, 56, (mask & 8) ? ComparePublic : nullptr); + for (int parameters : Results) { + for (int publicKey : Results) { + ++cases; + parameterResult = parameters; + publicResult = publicKey; + const auto firstBefore = Snapshot(first); + const auto secondBefore = Snapshot(second); + const auto methodBefore = method; + const auto payloadBefore = firstPayload; + const auto otherPayloadBefore = secondPayload; + const int parameterBefore = parameterCalls; + const int publicBefore = publicCalls; + const bool invokesParameters = (mask & 7) == 7; + const bool invokesPublic = (mask & 11) == 11 && (!invokesParameters || parameters > 0); + int expected = -2; + if (!(mask & 1)) expected = -1; + else if (invokesParameters && parameters <= 0) expected = parameters; + else if (invokesPublic) expected = publicKey; + Require(EVP_PKEY_cmp_nid_postfix(&first, &second) == expected, "comparison result"); + Require(parameterCalls == parameterBefore + invokesParameters, "parameter dispatch count"); + Require(publicCalls == publicBefore + invokesPublic, "public dispatch count"); + Require(Snapshot(first) == firstBefore && Snapshot(second) == secondBefore, "comparison mutated keys"); + Require(method == methodBefore && firstPayload == payloadBefore && secondPayload == otherPayloadBefore, "comparison mutated methods or payloads"); + } + } + } + first.type = second.type; + first.method = method.data(); + SetCallback(method, 160, ThrowComparison); + const int calls = publicCalls; + RequireThrows([&] { EVP_PKEY_cmp_nid_postfix(&first, &second); }); + Require(publicCalls == calls, "public comparison after parameter exception"); + SetCallback(method, 160, static_cast(nullptr)); + SetCallback(method, 56, ThrowComparison); + RequireThrows([&] { EVP_PKEY_cmp_nid_postfix(&first, &second); }); + SetCallback(method, 56, ComparePublic); + expectedSecond = &first; + publicResult = -17; + Require(EVP_PKEY_cmp_nid_postfix(&first, &first) == -17, "self comparison must dispatch"); + expectedSecond = &second; + MethodBytes nextMethod{}; + SetCallback(nextMethod, 56, ComparePublic); + SetCallback(method, 56, WrongComparison); + SetCallback(method, 160, CompareParameters); + parameterResult = 1; + publicResult = 29; + replacementMethod = nextMethod.data(); + Require(EVP_PKEY_cmp_nid_postfix(&first, &second) == 29, "public method refresh after parameter callback"); + replacementMethod = nullptr; +} + +} // namespace + +int main() { + CheckQueries(); + CheckComparison(); + std::printf("PKEY queries: %d matrix cases, %d checks\n", cases, checks); + return 0; +} diff --git a/docs/dev/debt/libSceLibreSsl3.md b/docs/dev/debt/libSceLibreSsl3.md index b7835765eb..247cd9e436 100644 --- a/docs/dev/debt/libSceLibreSsl3.md +++ b/docs/dev/debt/libSceLibreSsl3.md @@ -30,9 +30,11 @@ - [ASN1_TIME_diff](../../../core/libs/prx/libSceLibreSsl3/src/Time.cpp) has no console ABI or behavior verification. Its signature and date rules follow LibreSSL 4.0.0 [a_time.c](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/asn1/a_time.c), [a_time_tm.c](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/asn1/a_time_tm.c) and [a_time_posix.c](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/asn1/a_time_posix.c), using the existing 24-byte string layout. Null inputs use the host wall clock; the console clock relationship is unverified. Null output pointers throw after valid input parsing, where the reference dereferences them. Closes when: measured on a PS5. +- [EVP_PKEY_size, EVP_PKEY_security_bits and EVP_PKEY_cmp](../../../core/libs/prx/libSceLibreSsl3/src/PkeyQueries.cpp) have no console ABI or behavior verification. Dispatch follows [LibreSSL 4.0.0](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/evp/p_lib.c) using the existing 32-byte key and the LP64 method slots in that revision's `evp_local.h`: public comparison at 56, size at 96, security bits at 112 and parameter comparison at 160. Tests use caller-owned keys and callbacks; real key-constructor and algorithm-method interoperability remains unverified. Null comparison operands, undefined upstream, throw; null size/security queries and missing callbacks keep upstream's distinct results. Malformed method pointers remain unverified. Closes when: layouts and complete key lifetimes are measured on a console. + ### Additional callable export inventory -- [Additional exports](../../../core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp) declare 175 callable exports: 165 named and 10 known only by NID. Every entry throws. Parameterless `int` declarations are unknown-signature placeholders, not verified ABI contracts. Names/NIDs match the 13.60 export table. No functional implementation or title unblock is claimed. +- [Additional exports](../../../core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp) declare 173 remaining callable exports: 163 named and 10 known only by NID. Every entry throws. Parameterless `int` declarations are unknown-signature placeholders, not verified ABI contracts. Names/NIDs match the 13.60 export table. No functional implementation or title unblock is claimed. | Name | NID | | --- | --- | @@ -79,7 +81,6 @@ | `SSL_CTX_set_timeout` | `BBOgwZLvXHA` | | `tls_config_new` | `BQNyLN2nA9w` | | `X509_get_ext_by_NID` | `BRjfzlGULmU` | -| `EVP_PKEY_security_bits` | `BS5yWv6X-hg` | | `X509_check_akid` | `C7eFtWPfLdk` | | `SSL_set_quic_use_legacy_codepoint` | `CFRaBB2+R+s` | | `SSL_CTX_set_default_verify_paths` | `DZ6fywqpaik` | @@ -194,7 +195,6 @@ | `EVP_PKEY_CTX_free` | `ulzAPWCIBkw` | | `tls_free` | `uz3bWkbHZu4` | | `tls_config_verify_client` | `v1vHv4jOJxQ` | -| `EVP_PKEY_cmp` | `vLuIwBfrxrk` | | `d2i_ASN1_SEQUENCE_ANY` | `vR0po3nBkBs` | | `PKCS5_PBKDF2_HMAC` | `vnwZHU5FvBQ` | | `ASN1_OCTET_STRING_free` | `vo-cv5gnPUA` | @@ -212,4 +212,4 @@ | `ECDSA_SIG_get0` | `z8fsXF8JuL0` | | `EVP_MD_block_size` | `zCa75KLWWn4` | -- 115 of the 126 exports the 13.60 table lists only by NID carry, in the table above and in [Additional exports](../../../core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp), the LibreSSL names whose `tools/nid_names.py` hash equals the NID; the names were taken from the identifiers of the LibreSSL 3.8.4 and 4.0.0 `include`, `crypto`, `ssl` and `tls` sources. A name match does not show that the console function has LibreSSL's prototype or behavior; each keeps the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`. `AES_wrap_key`, `ECDSA_SIG_free`, `ECDSA_do_verify`, `ERR_load_ERR_strings`, `ERR_print_errors_fp` and `i2d_PUBKEY` also hash to the NIDs of six `unknown` rows, which keep their NID names; `PBYz0kpXwg8` hashes to no identifier of those sources. +- 113 of the 123 remaining exports the 13.60 table lists only by NID carry, in the table above and in [Additional exports](../../../core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp), the LibreSSL names whose `tools/nid_names.py` hash equals the NID; the names were taken from the identifiers of the LibreSSL 3.8.4 and 4.0.0 `include`, `crypto`, `ssl` and `tls` sources. A name match does not show that the console function has LibreSSL's prototype or behavior; each keeps the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`. `AES_wrap_key`, `ECDSA_SIG_free`, `ECDSA_do_verify`, `ERR_load_ERR_strings`, `ERR_print_errors_fp` and `i2d_PUBKEY` also hash to the NIDs of six `unknown` rows, which keep their NID names; `PBYz0kpXwg8` hashes to no identifier of those sources. From 8964fc297b33a2e165f27eb5252bc876d9876d93 Mon Sep 17 00:00:00 2001 From: atharvjairath <54663702+atharvjairath@users.noreply.github.com> Date: Tue, 3 Nov 2026 11:05:34 +0530 Subject: [PATCH 383/612] docs(libSceLibreSsl3): recount pending key exports --- docs/dev/debt/libSceLibreSsl3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceLibreSsl3.md b/docs/dev/debt/libSceLibreSsl3.md index 247cd9e436..cdaf4eec36 100644 --- a/docs/dev/debt/libSceLibreSsl3.md +++ b/docs/dev/debt/libSceLibreSsl3.md @@ -9,7 +9,7 @@ ### Unknown function info -- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 68 named callable exports, excluding `CRYPTO_memcmp`, `MD5`, `SHA1`, `d2i_ASN1_INTEGER`, `EVP_PKEY_up_ref`, `EC_KEY_up_ref`, `RSA_up_ref`, `EVP_PKEY_get0_RSA`, `EVP_PKEY_get0_DSA`, `EVP_PKEY_get0_DH`, `EVP_MD_CTX_new`, `EVP_MD_CTX_free`, `EVP_DigestInit`, `EVP_sha1`, `EVP_sha224`, `ERR_load_crypto_strings`, `ERR_peek_last_error`, `OpenSSL_version` and `X509_PUBKEY_get0_param`, remain unimplemented from the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl3.c), cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 67 named callable exports, excluding `CRYPTO_memcmp`, `MD5`, `SHA1`, `d2i_ASN1_INTEGER`, `EVP_PKEY_up_ref`, `EC_KEY_up_ref`, `RSA_up_ref`, `EVP_PKEY_get0_RSA`, `EVP_PKEY_get0_DSA`, `EVP_PKEY_get0_DH`, `EVP_MD_CTX_new`, `EVP_MD_CTX_free`, `EVP_DigestInit`, `EVP_sha1`, `EVP_sha224`, `ERR_load_crypto_strings`, `ERR_peek_last_error`, `OpenSSL_version`, `X509_PUBKEY_get0_param` and `EVP_PKEY_size`, remain unimplemented from the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl3.c), cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. ### Functional From 9406c8585e0eca130d0f1014c80f9544d09850bc Mon Sep 17 00:00:00 2001 From: Milos-Opletal <87488948+Milos-Opletal@users.noreply.github.com> Date: Thu, 5 Nov 2026 10:07:16 +0100 Subject: [PATCH 384/612] feat(libSceAppContent): implement sceAppContentDownloadDataGetAvailableSpaceKb and sceAppContentDownload1Expand --- core/libs/prx/libSceAppContent/Export.cpp | 14 +++++++++---- .../libs/prx/libSceAppContent/include/Api.hpp | 2 +- core/libs/tests/GuestAppContent.cpp | 21 +++++++++++++++++++ docs/dev/debt/libSceAppContent.md | 6 +++--- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libSceAppContent/Export.cpp b/core/libs/prx/libSceAppContent/Export.cpp index 7f4947a6c1..ce58e1546e 100644 --- a/core/libs/prx/libSceAppContent/Export.cpp +++ b/core/libs/prx/libSceAppContent/Export.cpp @@ -62,6 +62,7 @@ bool IsTemporaryMountPoint(const AppContentMountPoint* mountPoint) { return std::strncmp(mountPoint->data, TEMPORARY_MOUNT_POINT, sizeof(mountPoint->data)) == 0; } +std::atomic download1QuotaBytes{0}; std::atomic download2QuotaBytes{0}; bool IsDownloadMountPoint(const AppContentMountPoint* mountPoint) { @@ -173,10 +174,10 @@ int APS5_VABI sceAppContentDownloadDataGetAvailableSpaceKb(const AppContentMount const std::uint64_t quotaMiB = GetAppDownloadDataSizeMiB_nid_postfix(); if (quotaMiB > std::numeric_limits::max() / 1024u) throw std::overflow_error("AppContent download quota exceeds KiB range"); quotaKb = quotaMiB * 1024u; + } else if (std::strncmp(mountPoint->data, "/download1", sizeof(mountPoint->data)) == 0) { + quotaKb = download1QuotaBytes.load() / 1024u; } else if (std::strncmp(mountPoint->data, "/download2", sizeof(mountPoint->data)) == 0) { quotaKb = download2QuotaBytes.load() / 1024u; - } else { - NotImplemented_nid_no_patch(__func__); } if (quotaKb == 0) { *availableSpaceKb = 0; @@ -322,8 +323,13 @@ int APS5_VABI sceAppContentDownload0Shrink(void) { return 0; } -int APS5_VABI sceAppContentDownload1Expand(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceAppContentDownload1Expand(size_t size) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; + if (size == 0) return SCE_APP_CONTENT_ERROR_PARAMETER; + const auto directory = ResolvePath_nid_no_patch("/download1"); + if (std::filesystem::create_directories(directory)) RecordWrittenPath_nid_no_patch(directory); + size_t current = download1QuotaBytes.load(); + while (size > current && !download1QuotaBytes.compare_exchange_weak(current, size)) {} return 0; } diff --git a/core/libs/prx/libSceAppContent/include/Api.hpp b/core/libs/prx/libSceAppContent/include/Api.hpp index e6c6f7021a..ec4fc67145 100644 --- a/core/libs/prx/libSceAppContent/include/Api.hpp +++ b/core/libs/prx/libSceAppContent/include/Api.hpp @@ -36,7 +36,7 @@ int APS5_VABI sceAppContentCheckBundleLicenseOnDisc(uint32_t serviceLabel, const int APS5_VABI sceAppContentAppParamGetString(void); int APS5_VABI sceAppContentDownload0Expand(void); int APS5_VABI sceAppContentDownload0Shrink(void); -int APS5_VABI sceAppContentDownload1Expand(void); +int APS5_VABI sceAppContentDownload1Expand(size_t size); int APS5_VABI sceAppContentDownload1Shrink(void); int APS5_VABI sceAppContentDownload2Expand(size_t size); int APS5_VABI sceAppContentDownload2Shrink(void); diff --git a/core/libs/tests/GuestAppContent.cpp b/core/libs/tests/GuestAppContent.cpp index a57e43df2c..0086125a1d 100644 --- a/core/libs/tests/GuestAppContent.cpp +++ b/core/libs/tests/GuestAppContent.cpp @@ -38,6 +38,7 @@ int main() { Require(sceAppContentTemporaryDataGetAvailableSpaceKb(nullptr, nullptr) == notInitialized); Require(sceAppContentDownloadDataFormat(nullptr) == notInitialized); Require(sceAppContentDownloadDataGetAvailableSpaceKb(nullptr, nullptr) == notInitialized); + Require(sceAppContentDownload1Expand(1024) == notInitialized); Require(sceAppContentDownload2Expand(1024) == notInitialized); Require(sceAppContentAddcontDelete(0, nullptr) == notInitialized); Require(sceAppContentAddcontEnqueueDownload(0, nullptr) == notInitialized); @@ -94,6 +95,7 @@ int main() { Require(sceAppContentDownloadDataGetAvailableSpaceKb(nullptr, nullptr) == ErrorParameter); AppContentMountPoint invalidMount{}; Require(sceAppContentDownloadDataGetAvailableSpaceKb(&invalidMount, &availableKb) == ErrorNotMounted); + Require(availableKb == 12345); const auto downloadData = ResolvePath_nid_no_patch("/download0"); Require(sceAppContentDownloadDataFormat(&download) == 0); Require(std::filesystem::is_directory(downloadData) && std::filesystem::is_empty(downloadData)); @@ -123,6 +125,25 @@ int main() { Require(sceAppContentDownload2Expand(1024 * 1024) == 0); Require(sceAppContentDownloadDataGetAvailableSpaceKb(&download2Mount, &download2Space) == 0); Require(download2Space == 2048); + AppContentMountPoint download1Mount{}; + std::memcpy(download1Mount.data, "/download1", 11); + const auto download1Data = ResolvePath_nid_no_patch("/download1"); + std::filesystem::create_directories(download1Data); + { std::ofstream file(download1Data / "old.bin"); file << "download1"; } + Require(sceAppContentDownloadDataFormat(&download1Mount) == 0); + Require(std::filesystem::is_directory(download1Data) && std::filesystem::is_empty(download1Data)); + Require(sceAppContentDownload1Expand(0) == ErrorParameter); + Require(sceAppContentDownload1Expand(2 * 1024 * 1024) == 0); + Require(std::filesystem::is_directory(download1Data)); + size_t download1Space = 12345; + Require(sceAppContentDownloadDataGetAvailableSpaceKb(&download1Mount, &download1Space) == 0); + Require(download1Space == 2048); + Require(sceAppContentDownload1Expand(1024 * 1024) == 0); + Require(sceAppContentDownloadDataGetAvailableSpaceKb(&download1Mount, &download1Space) == 0); + Require(download1Space == 2048); + { std::ofstream file(download1Data / "used.bin", std::ios::binary); file << std::string(1024, 'x'); } + Require(sceAppContentDownloadDataGetAvailableSpaceKb(&download1Mount, &download1Space) == 0); + Require(download1Space == 2047); const int32_t expectedParams[] = {3, 23566, -7, 0, 2147483647}; for (uint32_t id = 0; id < 5; ++id) { diff --git a/docs/dev/debt/libSceAppContent.md b/docs/dev/debt/libSceAppContent.md index faac56021c..160869a9a8 100644 --- a/docs/dev/debt/libSceAppContent.md +++ b/docs/dev/debt/libSceAppContent.md @@ -8,15 +8,15 @@ | --- | --- | | `sceAppContentDownloadDataGetBlockSize` | `9ht66cHTj7g` | -- [sceAppContentDownloadDataGetAvailableSpaceKb](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - a title whose `param.json` declares `downloadDataSize` 0 gets 0 KB and OK; the console's behaviour for a title without a download data area is unverified (it may return an error instead). KytyPS5 does not special-case it, and shadPS4 stubs 1048576 KB. One disassembled title (PPSA15246) calls it with the `/download0` mount point and a zeroed 64-bit output, ignores the return value and multiplies the output by 1024, so for that title an error that leaves the output untouched and an OK with 0 KB look the same. `/download1` queries remain unimplemented because their quota model is unverified; `/download2` reflects the expanded quota. +- [sceAppContentDownloadDataGetAvailableSpaceKb](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - a title whose `param.json` declares `downloadDataSize` 0 gets 0 KB and OK; the console's behaviour for a title without a download data area is unverified (it may return an error instead). KytyPS5 does not special-case it, and shadPS4 stubs 1048576 KB. One disassembled title (PPSA15246) calls it with the `/download0` mount point and a zeroed 64-bit output, ignores the return value and multiplies the output by 1024, so for that title an error that leaves the output untouched and an OK with 0 KB look the same. `/download1` and `/download2` queries reflect their configured quota. - [sceAppContentDownloadDataFormat](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - empties the `/download0`, `/download1`, or `/download2` directory, creating it if needed. Imported by PPSA01317. A null mount point returns `PARAMETER` and another mount point returns `NOT_MOUNTED` as in `sceAppContentTemporaryDataFormat` and `sceAppContentDownloadDataGetAvailableSpaceKb`; the console's error codes for both and its behaviour with files still open are unverified -- [sceAppContentDownload0Expand, sceAppContentDownload0Shrink, sceAppContentDownload1Expand, sceAppContentDownload1Shrink and sceAppContentDownload2Shrink](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - partition expansion and shrink behaviour on hardware is unverified. `sceAppContentDownload2Expand` expands `/download2` and updates its reported quota; the other partition expansion and shrink functions throw through `NotImplemented_nid_no_patch`. +- [sceAppContentDownload0Expand, sceAppContentDownload0Shrink, sceAppContentDownload1Shrink and sceAppContentDownload2Shrink](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - partition expansion and shrink behaviour on hardware is unverified. `sceAppContentDownload1Expand` and `sceAppContentDownload2Expand` expand `/download1` and `/download2` and update their reported quotas; the other partition expansion and shrink functions throw through `NotImplemented_nid_no_patch`. - [sceAppContentUnknown_SWVxsi_ZBlw](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent, NID SWVxsi-ZBlw) - master data identifier query (UnknownMdid in [dlc_emu](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.cpp#L2258-L2262)). Reconstructed signature and return values on hardware are unverified; all calls throw through `NotImplemented_nid_no_patch`. - [libSceAppContent pending exports](../../../core/libs/prx/libSceAppContent/Export.cpp) - 8 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAppContent.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. ### Functional -- [Download partition expansion](../../../core/libs/prx/libSceAppContent/Export.cpp) - `/download2` expansion tracks size monotonically and reports available space in `sceAppContentDownloadDataGetAvailableSpaceKb`. The quota units and hardware shrink behavior remain unverified. +- [Download partition expansion](../../../core/libs/prx/libSceAppContent/Export.cpp) - `/download1` and `/download2` expansion tracks size monotonically and reports available space in `sceAppContentDownloadDataGetAvailableSpaceKb`. The quota units and hardware shrink behavior remain unverified. - [Download-data quota conversion](../../../core/libs/prx/libSceAppContent/Export.cpp) - console handling of `downloadDataSize` values too large to represent in 64-bit KiB is unverified. The host model throws `std::overflow_error` before writing the output or creating storage; no console error code is assigned. From bcdd941247eb71fe0569e4cfa487ad46ac5c59f0 Mon Sep 17 00:00:00 2001 From: hamb3r <48532909+hamb3r@users.noreply.github.com> Date: Tue, 3 Nov 2026 00:19:40 +0300 Subject: [PATCH 385/612] perf(libc): skip timer slack setup for short Linux waits --- core/libs/prx/libc/include/PreciseWait.hpp | 18 +++++----- core/libs/tests/PreciseWait.cpp | 39 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libc/include/PreciseWait.hpp b/core/libs/prx/libc/include/PreciseWait.hpp index e9f11a91da..79fabdd6f3 100644 --- a/core/libs/prx/libc/include/PreciseWait.hpp +++ b/core/libs/prx/libc/include/PreciseWait.hpp @@ -157,14 +157,16 @@ inline void PreciseSleepFromNanosWithSlack(std::uint64_t startNanos, unsigned lo } const std::uint64_t deadlineNanos = startNanos + nanos; #ifdef __linux__ - const bool lowSlack = allowLowSlack && PreciseLinuxLowTimerSlack(); - const unsigned long long spinTailNanos = lowSlack ? 15000ULL : 100000ULL; - if (nanos > spinTailNanos) [[unlikely]] { - const std::uint64_t wakeNanos = deadlineNanos - spinTailNanos; - struct timespec req; - req.tv_sec = static_cast(wakeNanos / 1000000000ULL); - req.tv_nsec = static_cast(wakeNanos % 1000000000ULL); - while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &req, nullptr) == EINTR) { + if (nanos > 15000ULL) [[unlikely]] { + const bool lowSlack = allowLowSlack && PreciseLinuxLowTimerSlack(); + const unsigned long long spinTailNanos = lowSlack ? 15000ULL : 100000ULL; + if (nanos > spinTailNanos) { + const std::uint64_t wakeNanos = deadlineNanos - spinTailNanos; + struct timespec req; + req.tv_sec = static_cast(wakeNanos / 1000000000ULL); + req.tv_nsec = static_cast(wakeNanos % 1000000000ULL); + while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &req, nullptr) == EINTR) { + } } } #elif defined(__APPLE__) diff --git a/core/libs/tests/PreciseWait.cpp b/core/libs/tests/PreciseWait.cpp index d9120b173c..47797a3945 100644 --- a/core/libs/tests/PreciseWait.cpp +++ b/core/libs/tests/PreciseWait.cpp @@ -5,9 +5,47 @@ #include #include #include +#include namespace { +#ifdef __linux__ +bool CheckLinuxTimerSlackBoundaries() { + bool passed = false; + std::thread worker([&] { + if (prctl(PR_SET_TIMERSLACK, 50000UL, 0UL, 0UL, 0UL) != 0) { + std::cerr << "Could not initialize timer slack for boundary checks\n"; + return; + } + const auto checkWait = [](unsigned long long nanos, bool allowLowSlack, long expectedSlack) { + const auto start = PreciseMonotonicNanos(); + PreciseSleepFromNanosWithSlack(start, nanos, allowLowSlack); + if (PreciseMonotonicNanos() < start + nanos) { + std::cerr << "Linux boundary wait returned before its deadline\n"; + return false; + } + if (prctl(PR_GET_TIMERSLACK, 0UL, 0UL, 0UL, 0UL) != expectedSlack) { + std::cerr << "Linux boundary wait changed timer slack unexpectedly\n"; + return false; + } + return true; + }; + for (const auto nanos : {0ULL, 1ULL, 14999ULL, 15000ULL}) { + if (!checkWait(nanos, true, 50000L)) return; + } + for (const auto nanos : {15001ULL, 99999ULL, 100000ULL, 100001ULL}) { + if (!checkWait(nanos, false, 50000L)) return; + } + for (const auto nanos : {15001ULL, 99999ULL, 100000ULL, 100001ULL}) { + if (!checkWait(nanos, true, 1L)) return; + } + passed = true; + }); + worker.join(); + return passed; +} +#endif + bool IsRunnerPreempted() { #ifdef _WIN32 char skip[8]{}; @@ -168,6 +206,7 @@ int main() { #endif #ifdef __linux__ + if (!CheckLinuxTimerSlackBoundaries()) return 1; if (PreciseLinuxLowTimerSlack() && prctl(PR_GET_TIMERSLACK, 0UL, 0UL, 0UL, 0UL) != 1L) { std::cerr << "PreciseLinuxLowTimerSlack did not configure 1ns timer slack\n"; return 1; From 6d3b60e32485ce4f75af6382935021f51bd3e573 Mon Sep 17 00:00:00 2001 From: hamb3r <48532909+hamb3r@users.noreply.github.com> Date: Tue, 3 Nov 2026 00:20:46 +0300 Subject: [PATCH 386/612] perf(libc): cache Linux spin-tail duration --- core/libs/prx/libc/include/PreciseWait.hpp | 21 ++++++--- core/libs/tests/PreciseWait.cpp | 50 ++++++++++++++++++++++ 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libc/include/PreciseWait.hpp b/core/libs/prx/libc/include/PreciseWait.hpp index 79fabdd6f3..fe20d8888c 100644 --- a/core/libs/prx/libc/include/PreciseWait.hpp +++ b/core/libs/prx/libc/include/PreciseWait.hpp @@ -142,12 +142,20 @@ inline std::uint64_t PreciseMonotonicNanos() { } #ifdef __linux__ -inline bool PreciseLinuxLowTimerSlack() { - static thread_local const bool lowSlack = [] { +inline unsigned long long PreciseLinuxSpinTailNanos_nid_no_patch() { + static thread_local unsigned long long cachedSpinTailNanos = 0; + unsigned long long spinTailNanos = cachedSpinTailNanos; + if (spinTailNanos == 0) [[unlikely]] { const long current = prctl(PR_GET_TIMERSLACK, 0UL, 0UL, 0UL, 0UL); - return current == 1L || prctl(PR_SET_TIMERSLACK, 1UL, 0UL, 0UL, 0UL) == 0; - }(); - return lowSlack; + const bool lowSlack = current == 1L || prctl(PR_SET_TIMERSLACK, 1UL, 0UL, 0UL, 0UL) == 0; + spinTailNanos = lowSlack ? 15000ULL : 100000ULL; + cachedSpinTailNanos = spinTailNanos; + } + return spinTailNanos; +} + +inline bool PreciseLinuxLowTimerSlack() { + return PreciseLinuxSpinTailNanos_nid_no_patch() == 15000ULL; } #endif @@ -158,8 +166,7 @@ inline void PreciseSleepFromNanosWithSlack(std::uint64_t startNanos, unsigned lo const std::uint64_t deadlineNanos = startNanos + nanos; #ifdef __linux__ if (nanos > 15000ULL) [[unlikely]] { - const bool lowSlack = allowLowSlack && PreciseLinuxLowTimerSlack(); - const unsigned long long spinTailNanos = lowSlack ? 15000ULL : 100000ULL; + const unsigned long long spinTailNanos = allowLowSlack ? PreciseLinuxSpinTailNanos_nid_no_patch() : 100000ULL; if (nanos > spinTailNanos) { const std::uint64_t wakeNanos = deadlineNanos - spinTailNanos; struct timespec req; diff --git a/core/libs/tests/PreciseWait.cpp b/core/libs/tests/PreciseWait.cpp index 47797a3945..f624cc5f25 100644 --- a/core/libs/tests/PreciseWait.cpp +++ b/core/libs/tests/PreciseWait.cpp @@ -6,10 +6,59 @@ #include #include #include +#ifdef __linux__ +#include +#include +#include +#include +#endif namespace { #ifdef __linux__ +bool CheckLinuxTimerSlackFailures() { + for (unsigned mode = 0; mode < 5; ++mode) { + bool passed = false; + std::thread worker([&] { + if (prctl(PR_SET_TIMERSLACK, mode == 3 ? 1UL : 50000UL, 0UL, 0UL, 0UL) != 0) return; + if (mode == 4 && !PreciseLinuxLowTimerSlack()) return; + const bool denyRead = mode == 1 || mode == 2 || mode == 4; + const bool denyWrite = mode != 1; + sock_filter filter[] = { + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_prctl, 0, 4), + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, PR_GET_TIMERSLACK, 1, 0), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, PR_SET_TIMERSLACK, 0, 1), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | EPERM), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), + }; + filter[3].jt = denyRead ? 1 : 2; + filter[4].jt = denyWrite ? 0 : 1; + sock_fprog program{static_cast(sizeof(filter) / sizeof(filter[0])), filter}; + if (prctl(PR_SET_NO_NEW_PRIVS, 1UL, 0UL, 0UL, 0UL) != 0 || + prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program) != 0) return; + const bool expectedLowSlack = mode == 1 || mode == 3 || mode == 4; + if (PreciseLinuxLowTimerSlack() != expectedLowSlack || + PreciseLinuxLowTimerSlack() != expectedLowSlack) return; + for (const auto nanos : {1ULL, 15000ULL, 15001ULL, 99999ULL, 100000ULL, 100001ULL, 1000000ULL}) { + const auto start = PreciseMonotonicNanos(); + PreciseSleepFromNanosWithSlack(start, nanos, true); + if (PreciseMonotonicNanos() < start + nanos) return; + } + if (!denyRead && prctl(PR_GET_TIMERSLACK, 0UL, 0UL, 0UL, 0UL) != + (expectedLowSlack ? 1L : 50000L)) return; + passed = true; + }); + worker.join(); + if (!passed) { + std::cerr << "Linux timer slack failure scenario " << mode << " failed\n"; + return false; + } + } + return true; +} + bool CheckLinuxTimerSlackBoundaries() { bool passed = false; std::thread worker([&] { @@ -207,6 +256,7 @@ int main() { #ifdef __linux__ if (!CheckLinuxTimerSlackBoundaries()) return 1; + if (!CheckLinuxTimerSlackFailures()) return 1; if (PreciseLinuxLowTimerSlack() && prctl(PR_GET_TIMERSLACK, 0UL, 0UL, 0UL, 0UL) != 1L) { std::cerr << "PreciseLinuxLowTimerSlack did not configure 1ns timer slack\n"; return 1; From 6f2f0befb0ee6a9961557dcd4a3eebbf913aab33 Mon Sep 17 00:00:00 2001 From: hamb3r <48532909+hamb3r@users.noreply.github.com> Date: Tue, 3 Nov 2026 00:23:23 +0300 Subject: [PATCH 387/612] refactor(libc): isolate Linux timer sleep path --- core/libs/prx/libc/include/PreciseWait.hpp | 15 +++++++++------ core/libs/tests/PreciseWait.cpp | 17 ++++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/core/libs/prx/libc/include/PreciseWait.hpp b/core/libs/prx/libc/include/PreciseWait.hpp index fe20d8888c..1f59838458 100644 --- a/core/libs/prx/libc/include/PreciseWait.hpp +++ b/core/libs/prx/libc/include/PreciseWait.hpp @@ -157,6 +157,14 @@ inline unsigned long long PreciseLinuxSpinTailNanos_nid_no_patch() { inline bool PreciseLinuxLowTimerSlack() { return PreciseLinuxSpinTailNanos_nid_no_patch() == 15000ULL; } + +[[gnu::noinline]] inline void PreciseSleepLinuxTimer_nid_no_patch(std::uint64_t wakeNanos) { + struct timespec req; + req.tv_sec = static_cast(wakeNanos / 1000000000ULL); + req.tv_nsec = static_cast(wakeNanos % 1000000000ULL); + while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &req, nullptr) == EINTR) { + } +} #endif inline void PreciseSleepFromNanosWithSlack(std::uint64_t startNanos, unsigned long long nanos, bool allowLowSlack) { @@ -168,12 +176,7 @@ inline void PreciseSleepFromNanosWithSlack(std::uint64_t startNanos, unsigned lo if (nanos > 15000ULL) [[unlikely]] { const unsigned long long spinTailNanos = allowLowSlack ? PreciseLinuxSpinTailNanos_nid_no_patch() : 100000ULL; if (nanos > spinTailNanos) { - const std::uint64_t wakeNanos = deadlineNanos - spinTailNanos; - struct timespec req; - req.tv_sec = static_cast(wakeNanos / 1000000000ULL); - req.tv_nsec = static_cast(wakeNanos % 1000000000ULL); - while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &req, nullptr) == EINTR) { - } + PreciseSleepLinuxTimer_nid_no_patch(deadlineNanos - spinTailNanos); } } #elif defined(__APPLE__) diff --git a/core/libs/tests/PreciseWait.cpp b/core/libs/tests/PreciseWait.cpp index f624cc5f25..3ecd193dc9 100644 --- a/core/libs/tests/PreciseWait.cpp +++ b/core/libs/tests/PreciseWait.cpp @@ -1,17 +1,17 @@ #include "prx/libc/include/PreciseWait.hpp" #include #include -#include -#include -#include -#include -#include #ifdef __linux__ #include #include #include #include +#include #endif +#include +#include +#include +#include namespace { @@ -179,6 +179,11 @@ int main() { } } +#ifdef __linux__ + if (!CheckLinuxTimerSlackBoundaries()) return 1; + if (!CheckLinuxTimerSlackFailures()) return 1; +#endif + PreciseSleepUs(0); for (const unsigned long long micros : {50ULL, 1000ULL}) { @@ -255,8 +260,6 @@ int main() { #endif #ifdef __linux__ - if (!CheckLinuxTimerSlackBoundaries()) return 1; - if (!CheckLinuxTimerSlackFailures()) return 1; if (PreciseLinuxLowTimerSlack() && prctl(PR_GET_TIMERSLACK, 0UL, 0UL, 0UL, 0UL) != 1L) { std::cerr << "PreciseLinuxLowTimerSlack did not configure 1ns timer slack\n"; return 1; From 8ba5f73f92a6395aa3773df66a9d137e2574ca31 Mon Sep 17 00:00:00 2001 From: pushkarreddyy <212133856+pushkarreddyy@users.noreply.github.com> Date: Sat, 31 Oct 2026 19:57:07 +0530 Subject: [PATCH 388/612] fix(libSceNpAuth): return INVALID_ARGUMENT from async poll and wait for unwritable pointers --- core/libs/prx/libSceNpAuth/Export.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceNpAuth/Export.cpp b/core/libs/prx/libSceNpAuth/Export.cpp index 026144f14c..92ee749530 100644 --- a/core/libs/prx/libSceNpAuth/Export.cpp +++ b/core/libs/prx/libSceNpAuth/Export.cpp @@ -130,7 +130,8 @@ int APS5_VABI sceNpAuthPollAsync(int req_id, int* result) { it->second.polled = true; return NP_POLL_ASYNC_RUNNING; } - *result = it->second.result; + const int res = it->second.result; + if (!GuestCopyOut_nid_no_patch(result, &res, sizeof(res))) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; return NP_POLL_ASYNC_FINISHED; } @@ -155,7 +156,8 @@ int APS5_VABI sceNpAuthWaitAsync(int req_id, int* result) { auto it = g_requests.find(req_id); if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; if (!it->second.isAsync || !it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ID; - *result = it->second.result; + const int res = it->second.result; + if (!GuestCopyOut_nid_no_patch(result, &res, sizeof(res))) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; return NP_POLL_ASYNC_FINISHED; } From 750fd73f9378c5cb641d9d275efa17bcb43ad2c4 Mon Sep 17 00:00:00 2001 From: pushkarreddyy <212133856+pushkarreddyy@users.noreply.github.com> Date: Sat, 31 Oct 2026 19:57:41 +0530 Subject: [PATCH 389/612] test(libSceNpAuth): cover invalid pointer rejection in async poll and wait --- core/libs/tests/GuestNpAuth.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/libs/tests/GuestNpAuth.cpp b/core/libs/tests/GuestNpAuth.cpp index 31efb7ba57..9f3a5ebb24 100644 --- a/core/libs/tests/GuestNpAuth.cpp +++ b/core/libs/tests/GuestNpAuth.cpp @@ -120,6 +120,8 @@ int main() { int asyncWait = 0; Require(sceNpAuthWaitAsync(asyncReq, &asyncWait) == 0); Require(asyncWait == signedOut); + Require(sceNpAuthPollAsync(asyncReq, reinterpret_cast(0x10)) == invalidArgument); + Require(sceNpAuthWaitAsync(asyncReq, reinterpret_cast(0x10)) == invalidArgument); Require(sceNpAuthGetAuthorizationCodeV3(asyncReq2, param, authCode, &issuerId) == 0); Require(sceNpAuthGetAuthorizationCodeV3(asyncReq2, param, authCode, &issuerId) == invalidArgument); From 89234bbc30fb29e7cefd2365e0ac5526675b9ccd Mon Sep 17 00:00:00 2001 From: pushkarreddyy <212133856+pushkarreddyy@users.noreply.github.com> Date: Sat, 31 Oct 2026 19:58:15 +0530 Subject: [PATCH 390/612] docs(libSceNpAuth): document INVALID_ARGUMENT on unwritable pointers in async poll and wait --- docs/dev/debt/libSceNpAuth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceNpAuth.md b/docs/dev/debt/libSceNpAuth.md index 83f702e202..3d79ff7da7 100644 --- a/docs/dev/debt/libSceNpAuth.md +++ b/docs/dev/debt/libSceNpAuth.md @@ -4,7 +4,7 @@ - [sceNpAuthAbortRequest, sceNpAuthGetAuthorizationCode, sceNpAuthGetAuthorizationCodeA, sceNpAuthGetAuthorizationCodeV3, sceNpAuthGetIdToken, sceNpAuthGetIdTokenA, sceNpAuthGetIdTokenV3, sceNpAuthPollAsync, sceNpAuthWaitAsync](../../../core/libs/prx/libSceNpAuth/Export.cpp) (libSceNpAuth) - request tracking, `ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND` (0x80550306) on unknown IDs, `ORBIS_NP_AUTH_ERROR_INVALID_ID` (0x80550307) on non-async or unprocessed requests in Poll/Wait, `ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT` (0x80550301) on null arguments, and `ORBIS_NP_AUTH_ERROR_ABORTED` (0x80550304) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp) (PS4); the A calls forward to their V3 counterparts, which in shadPS4 ([AuthorizationCode A](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L189-L193), [V3](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L195-L199); [IdToken A](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L375-L378), [V3](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L380-L383)) call the same helper and differ only in a version flag; async requests defer `SCE_NP_ERROR_SIGNED_OUT` to Poll/Wait while sync requests fail immediately; PSN is not contacted. Call sites in three disassembled titles (PPSA02225, PPSA15246, PPSA30601; static reading only) show the V3 parameter layouts: the code request parameter is 0x20 bytes (size, a 32-bit value that the titles take from their user object, a pointer to a client-ID-like string, a pointer to a constant string) and the call is `(reqId, param, code[128], int* issuerId)`; the ID token request parameter is 0x28 bytes (size, the same kind of 32-bit value, a pointer to a client-ID-like string, a pointer to a second buffer (256 bytes in two titles: a 256-byte copy in one, a 256-byte zeroed area holding a 16-character string in the other) and a pointer to a constant string) and the call is `(reqId, param, token)`. The client-ID-like string is 36 characters plus a terminator in one title and a 128-byte buffer in two. In two titles the calls follow `sceNpAuthCreateAsyncRequest` (whose parameter holds only a size of 0x18) and are polled with `sceNpAuthPollAsync` (PPSA15246 at 0x4feefd with one non-blocking poll per run of a title function, PPSA30601 at 0x16edb90 in a blocking loop that polls once and then up to 40 more times, each after a 100 ms sleep); the third title is a synchronous caller that takes the request ID from an argument-less call. All three call `sceNpAuthDeleteRequest` once the request has finished or failed (immediately after the call in the synchronous title, after a poll return of 0 in the non-blocking one, on every path in the blocking one). The titles treat a poll return of 0 as finished and any negative result as a failure without looking at the specific code; one title, when the 41st poll still returns non-zero, treats a return of exactly 1 as a timeout and calls `sceNpAuthAbortRequest`, and reports 0x80550301 and 0x80550306 from Poll as an invalid result pointer and an unknown request ID. The size fields are not checked here; the console's size checks and the behaviour of the calls on a console are unverified -- [sceNpAuthPollAsync and sceNpAuthWaitAsync](../../../core/libs/prx/libSceNpAuth/Export.cpp) (libSceNpAuth) - PS5 abort-before-start behavior is unmeasured. Poll/Wait keep returning `ORBIS_NP_AUTH_ERROR_INVALID_ID` without changing output after a pre-start abort, including subsequent rejected getter calls, following [shadPS4 StartRequest and FindAsyncRequest](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp). +- [sceNpAuthPollAsync and sceNpAuthWaitAsync](../../../core/libs/prx/libSceNpAuth/Export.cpp) (libSceNpAuth) - return `ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT` on null or unwritable result pointers. PS5 abort-before-start behavior is unmeasured. Poll/Wait keep returning `ORBIS_NP_AUTH_ERROR_INVALID_ID` without changing output after a pre-start abort, including subsequent rejected getter calls, following [shadPS4 StartRequest and FindAsyncRequest](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp). ### Functional From 984d2ad23739a2d3b0bf2358cbed203719f5b963 Mon Sep 17 00:00:00 2001 From: pushkarreddyy <212133856+pushkarreddyy@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:46:51 +0530 Subject: [PATCH 391/612] fix(libSceNpAuth): release request mutex before copying out result in async poll and wait --- core/libs/prx/libSceNpAuth/Export.cpp | 32 ++++++++++++++++----------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/core/libs/prx/libSceNpAuth/Export.cpp b/core/libs/prx/libSceNpAuth/Export.cpp index 92ee749530..f25f1a4cff 100644 --- a/core/libs/prx/libSceNpAuth/Export.cpp +++ b/core/libs/prx/libSceNpAuth/Export.cpp @@ -122,15 +122,18 @@ int APS5_VABI sceNpAuthGetIdToken(int req_id, const void* param, void* id_token) int APS5_VABI sceNpAuthPollAsync(int req_id, int* result) { if (!result) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; - std::lock_guard lock(g_requestMutex); - auto it = g_requests.find(req_id); - if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; - if (!it->second.isAsync || !it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ID; - if (!it->second.polled) { - it->second.polled = true; - return NP_POLL_ASYNC_RUNNING; + int res = 0; + { + std::lock_guard lock(g_requestMutex); + auto it = g_requests.find(req_id); + if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; + if (!it->second.isAsync || !it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ID; + if (!it->second.polled) { + it->second.polled = true; + return NP_POLL_ASYNC_RUNNING; + } + res = it->second.result; } - const int res = it->second.result; if (!GuestCopyOut_nid_no_patch(result, &res, sizeof(res))) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; return NP_POLL_ASYNC_FINISHED; } @@ -152,11 +155,14 @@ int APS5_VABI sceNpAuthSetTimeout(std::int32_t requestId, std::int32_t resolveRe int APS5_VABI sceNpAuthWaitAsync(int req_id, int* result) { if (!result) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; - std::lock_guard lock(g_requestMutex); - auto it = g_requests.find(req_id); - if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; - if (!it->second.isAsync || !it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ID; - const int res = it->second.result; + int res = 0; + { + std::lock_guard lock(g_requestMutex); + auto it = g_requests.find(req_id); + if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; + if (!it->second.isAsync || !it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ID; + res = it->second.result; + } if (!GuestCopyOut_nid_no_patch(result, &res, sizeof(res))) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; return NP_POLL_ASYNC_FINISHED; } From e37478d18f4891c838d4c6158f20d06d933985d6 Mon Sep 17 00:00:00 2001 From: pushkarreddyy <212133856+pushkarreddyy@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:47:52 +0530 Subject: [PATCH 392/612] test(libSceNpAuth): verify result remains available after bad-pointer async query failure --- core/libs/tests/GuestNpAuth.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/libs/tests/GuestNpAuth.cpp b/core/libs/tests/GuestNpAuth.cpp index 9f3a5ebb24..3681d0bf85 100644 --- a/core/libs/tests/GuestNpAuth.cpp +++ b/core/libs/tests/GuestNpAuth.cpp @@ -122,6 +122,9 @@ int main() { Require(asyncWait == signedOut); Require(sceNpAuthPollAsync(asyncReq, reinterpret_cast(0x10)) == invalidArgument); Require(sceNpAuthWaitAsync(asyncReq, reinterpret_cast(0x10)) == invalidArgument); + asyncWait = 0; + Require(sceNpAuthWaitAsync(asyncReq, &asyncWait) == 0); + Require(asyncWait == signedOut); Require(sceNpAuthGetAuthorizationCodeV3(asyncReq2, param, authCode, &issuerId) == 0); Require(sceNpAuthGetAuthorizationCodeV3(asyncReq2, param, authCode, &issuerId) == invalidArgument); From 31733f53b6a183c8c0b7f073d17171a1591425d8 Mon Sep 17 00:00:00 2001 From: pushkarreddyy <212133856+pushkarreddyy@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:48:59 +0530 Subject: [PATCH 393/612] docs(libSceNpAuth): clarify host-safety fallback and running status in async poll and wait --- docs/dev/debt/libSceNpAuth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceNpAuth.md b/docs/dev/debt/libSceNpAuth.md index 3d79ff7da7..33eb240c55 100644 --- a/docs/dev/debt/libSceNpAuth.md +++ b/docs/dev/debt/libSceNpAuth.md @@ -4,7 +4,7 @@ - [sceNpAuthAbortRequest, sceNpAuthGetAuthorizationCode, sceNpAuthGetAuthorizationCodeA, sceNpAuthGetAuthorizationCodeV3, sceNpAuthGetIdToken, sceNpAuthGetIdTokenA, sceNpAuthGetIdTokenV3, sceNpAuthPollAsync, sceNpAuthWaitAsync](../../../core/libs/prx/libSceNpAuth/Export.cpp) (libSceNpAuth) - request tracking, `ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND` (0x80550306) on unknown IDs, `ORBIS_NP_AUTH_ERROR_INVALID_ID` (0x80550307) on non-async or unprocessed requests in Poll/Wait, `ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT` (0x80550301) on null arguments, and `ORBIS_NP_AUTH_ERROR_ABORTED` (0x80550304) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp) (PS4); the A calls forward to their V3 counterparts, which in shadPS4 ([AuthorizationCode A](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L189-L193), [V3](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L195-L199); [IdToken A](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L375-L378), [V3](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L380-L383)) call the same helper and differ only in a version flag; async requests defer `SCE_NP_ERROR_SIGNED_OUT` to Poll/Wait while sync requests fail immediately; PSN is not contacted. Call sites in three disassembled titles (PPSA02225, PPSA15246, PPSA30601; static reading only) show the V3 parameter layouts: the code request parameter is 0x20 bytes (size, a 32-bit value that the titles take from their user object, a pointer to a client-ID-like string, a pointer to a constant string) and the call is `(reqId, param, code[128], int* issuerId)`; the ID token request parameter is 0x28 bytes (size, the same kind of 32-bit value, a pointer to a client-ID-like string, a pointer to a second buffer (256 bytes in two titles: a 256-byte copy in one, a 256-byte zeroed area holding a 16-character string in the other) and a pointer to a constant string) and the call is `(reqId, param, token)`. The client-ID-like string is 36 characters plus a terminator in one title and a 128-byte buffer in two. In two titles the calls follow `sceNpAuthCreateAsyncRequest` (whose parameter holds only a size of 0x18) and are polled with `sceNpAuthPollAsync` (PPSA15246 at 0x4feefd with one non-blocking poll per run of a title function, PPSA30601 at 0x16edb90 in a blocking loop that polls once and then up to 40 more times, each after a 100 ms sleep); the third title is a synchronous caller that takes the request ID from an argument-less call. All three call `sceNpAuthDeleteRequest` once the request has finished or failed (immediately after the call in the synchronous title, after a poll return of 0 in the non-blocking one, on every path in the blocking one). The titles treat a poll return of 0 as finished and any negative result as a failure without looking at the specific code; one title, when the 41st poll still returns non-zero, treats a return of exactly 1 as a timeout and calls `sceNpAuthAbortRequest`, and reports 0x80550301 and 0x80550306 from Poll as an invalid result pointer and an unknown request ID. The size fields are not checked here; the console's size checks and the behaviour of the calls on a console are unverified -- [sceNpAuthPollAsync and sceNpAuthWaitAsync](../../../core/libs/prx/libSceNpAuth/Export.cpp) (libSceNpAuth) - return `ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT` on null or unwritable result pointers. PS5 abort-before-start behavior is unmeasured. Poll/Wait keep returning `ORBIS_NP_AUTH_ERROR_INVALID_ID` without changing output after a pre-start abort, including subsequent rejected getter calls, following [shadPS4 StartRequest and FindAsyncRequest](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp). +- [sceNpAuthPollAsync and sceNpAuthWaitAsync](../../../core/libs/prx/libSceNpAuth/Export.cpp) (libSceNpAuth) - returning `ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT` for non-null unmapped result pointers is a host-safety fallback (console unmeasured); the first `sceNpAuthPollAsync` call returns running status without reading the pointer. PS5 abort-before-start behavior is unmeasured. Poll/Wait keep returning `ORBIS_NP_AUTH_ERROR_INVALID_ID` without changing output after a pre-start abort, following [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp). ### Functional From 6c99743c3d2c8262bbca9bd6660fd1d5c01b4517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Ruiz=20Pe=C3=B1a?= Date: Thu, 5 Nov 2026 14:15:16 +0100 Subject: [PATCH 394/612] fix(libSceNpSessionSignaling): refuse the unverified custom property set --- .../prx/libSceNpSessionSignaling/Export.cpp | 8 +++---- core/libs/tests/GuestNpSessionSignaling.cpp | 22 +++++++++++++------ docs/dev/debt/libSceNpSessionSignaling.md | 4 ++-- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/core/libs/prx/libSceNpSessionSignaling/Export.cpp b/core/libs/prx/libSceNpSessionSignaling/Export.cpp index caae6447b6..7ffb337a7f 100644 --- a/core/libs/prx/libSceNpSessionSignaling/Export.cpp +++ b/core/libs/prx/libSceNpSessionSignaling/Export.cpp @@ -302,14 +302,12 @@ int APS5_VABI sceNpSessionSignalingJoinActivate() { } int APS5_VABI sceNpSessionSignalingSetCustomProperty(std::uint32_t contextId, const std::uint32_t* property, std::uint32_t* previous) { + (void)previous; std::lock_guard lock(g_mutex); if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; if (!property) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; - auto* context = FindContext(contextId); - if (!context) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; - if (previous) *previous = context->customProperty; - context->customProperty = *property; - return 0; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + NotImplemented_nid_no_patch(__func__); } int APS5_VABI sceNpSessionSignalingManualUdpAbortConnection() { diff --git a/core/libs/tests/GuestNpSessionSignaling.cpp b/core/libs/tests/GuestNpSessionSignaling.cpp index 34a6c383e6..1ac672b368 100644 --- a/core/libs/tests/GuestNpSessionSignaling.cpp +++ b/core/libs/tests/GuestNpSessionSignaling.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace { @@ -84,14 +85,20 @@ int main() { std::uint32_t previousProperty = 0; int callbackMarker = 0; int userDataMarker = 0; - Require(sceNpSessionSignalingSetCustomProperty(contextId, &staleProperty, &previousProperty) == 0 && previousProperty == 0, "set property before context reuse"); + bool staleSetRefused = false; + try { sceNpSessionSignalingSetCustomProperty(contextId, &staleProperty, &previousProperty); } + catch (const std::runtime_error&) { staleSetRefused = true; } + Require(staleSetRefused, "set property before context reuse was not refused"); Require(sceNpSessionSignalingRegisterInternalCallback(contextId, &callbackMarker, &userDataMarker) == 0, "register callback before context reuse"); Require(sceNpSessionSignalingDestroyContext(contextId) == 0, "destroy context before reuse two"); const std::int32_t secondReuseUser = 11; std::memcpy(contextParam, &secondReuseUser, sizeof(secondReuseUser)); Require(sceNpSessionSignalingCreateContext2(reinterpret_cast(contextParam), &contextId) == 0, "reuse context slot two"); Require(sceNpSessionSignalingGetCustomProperty(contextId, &previousProperty) == 0 && previousProperty == 0, "reused context two kept state"); - Require(sceNpSessionSignalingSetCustomProperty(otherContexts[0], &staleProperty, &previousProperty) == 0 && previousProperty == 0, "set property before context one reuse"); + staleSetRefused = false; + try { sceNpSessionSignalingSetCustomProperty(otherContexts[0], &staleProperty, &previousProperty); } + catch (const std::runtime_error&) { staleSetRefused = true; } + Require(staleSetRefused, "set property before context one reuse was not refused"); Require(sceNpSessionSignalingRegisterInternalCallback(otherContexts[0], &callbackMarker, &userDataMarker) == 0, "register callback before context one reuse"); Require(sceNpSessionSignalingDestroyContext(otherContexts[0]) == 0, "destroy context before reuse one"); const std::int32_t thirdReuseUser = 12; @@ -177,11 +184,12 @@ int main() { Require(sceNpSessionSignalingSetCustomProperty(firstId, &property, nullptr) == ContextNotFound, "set custom property stale context"); std::uint32_t value = 0x12345678u; std::uint32_t previous = 0xa5a5a5a5u; - Require(sceNpSessionSignalingSetCustomProperty(contextId, &value, &previous) == 0 && previous == 0, "set custom property"); - Require(sceNpSessionSignalingGetCustomProperty(contextId, &property) == 0 && property == value, "get custom property"); - value = 0xabcdef01u; - Require(sceNpSessionSignalingSetCustomProperty(contextId, &value, &previous) == 0 && previous == 0x12345678u, "replace custom property"); - Require(sceNpSessionSignalingGetCustomProperty(contextId, &property) == 0 && property == value, "get replaced custom property"); + bool setRefused = false; + try { sceNpSessionSignalingSetCustomProperty(contextId, &value, &previous); } + catch (const std::runtime_error&) { setRefused = true; } + Require(setRefused, "valid custom property set was not refused"); + Require(previous == 0xa5a5a5a5u, "refused custom property set wrote the previous value"); + Require(sceNpSessionSignalingGetCustomProperty(contextId, &property) == 0 && property == 0, "refused custom property set changed the property"); Require(sceNpSessionSignalingRegisterInternalCallback(firstId, nullptr, nullptr) == ContextNotFound, "register callback stale context"); Require(sceNpSessionSignalingRegisterInternalCallback(contextId, nullptr, nullptr) == 0, "register callback"); const char* const activateSessionId = reinterpret_cast(sessionId); diff --git a/docs/dev/debt/libSceNpSessionSignaling.md b/docs/dev/debt/libSceNpSessionSignaling.md index 9e3e4197fd..36ff78cd5c 100644 --- a/docs/dev/debt/libSceNpSessionSignaling.md +++ b/docs/dev/debt/libSceNpSessionSignaling.md @@ -8,7 +8,7 @@ - [sceNpSessionSignalingCreateJoinActivate, sceNpSessionSignalingJoinActivate, sceNpSessionSignalingManualUdpAbortConnection, sceNpSessionSignalingManualUdpCreateJoin, sceNpSessionSignalingManualUdpEstablishConnection and sceNpSessionSignalingManualUdpJoin](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpSessionSignaling.c) and the [13.60 inventory](https://github.com/xdCloudy/PS5-13.60-Function-Stub-Library/blob/bfec40deb4c47a17d7603ee2bc79aa6862c0c56b/data/firmware-13.60/libscenpsessionsignaling-58c8806476.jsonl), but no credible prototypes were found. The parameterless declarations are import-resolution placeholders, not an ABI claim, and they still throw through `NotImplemented_nid_no_patch`. -- [sceNpSessionSignalingInitializeWithPort, sceNpSessionSignalingSetCustomProperty and sceNpSessionSignalingRegisterInternalCallback](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) - signatures from the 11.00 wrappers. Initialize (`0xab80`) passes port 0x217b and reserved 0 to the initializer at vtable slot 0x10; InitializeWithPort (`0xab40`) forwards `(param, port, reserved)` there too, but the initializer's validation is unmeasured; the host opens no socket and accepts both. SetCustomProperty (`0xb3c0`) checks init, null property and context, then calls slot 0x78 with the opaque pointer and copies the returned dword to the optional previous output; GetCustomProperty (`0xb470`) calls slot 0x80 and copies its dword to the optional output. This establishes the 32-bit previous/output exchange, not the internal four-byte load, so the host models one 32-bit value. RegisterInternalCallback stores both pointers without invoking them; the callback signature is unknown. +- [sceNpSessionSignalingInitializeWithPort, sceNpSessionSignalingSetCustomProperty and sceNpSessionSignalingRegisterInternalCallback](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) - Initialize (`0xab80`) and InitializeWithPort (`0xab40`) reach manager vtable slot 0x10 (`0x1c7c0`): it rejects a null parameter, a second initialization and a failed pool, uses the low 16 bits of the port and accepts any reserved value; the host opens no socket and ignores both. SetCustomProperty (`0xb3c0`) checks init, null property and context, then calls slot 0x78 (`0x114c0` -> `0x14420`), which applies `strnlen` to the property pointer and exchanges a dword; the four-byte load/store is unverified, so the host refuses a valid request with `NotImplemented_nid_no_patch`. GetCustomProperty (`0xb470`) calls slot 0x80 (`0x11500` -> `0x14630`) and returns the fresh-context zero. RegisterInternalCallback stores both pointers without invoking them; the callback signature is unknown. ### Functional @@ -18,6 +18,6 @@ - [sceNpSessionSignalingGetMemoryInfo and sceNpSessionSignalingGetConnectionStatistics](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - the wrapper checks initialization (0x80553301) and a null output (0x80553303) before delegating. The host stores the pool size from initialization and reports zero used and peak sizes because it allocates no signaling pool, and reports all connection statistics as zero because no connection ever activates. The console's actual usage and statistics values were not measured. -- [sceNpSessionSignalingGetCustomProperty and sceNpSessionSignalingSetCustomProperty](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - the host keeps one 32-bit custom property per context. Get allows a null output and returns the current value; Set rejects a null property pointer, returns the previous value through the optional output and stores the new value. Reusing a destroyed context slot clears the property and both callback pointers, so a new context starts from the same state as a fresh slot. The input width is inferred from the wrapper's opaque pointer and the 32-bit previous/getter exchange described above, not measured on a console. +- [sceNpSessionSignalingGetCustomProperty and sceNpSessionSignalingSetCustomProperty](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - Get allows a null output and returns the zero of a fresh context. Set rejects a null property pointer and an unknown context through the wrapper's errors, then refuses a valid request through `NotImplemented_nid_no_patch`: the internal slot 0x78 path applies `strnlen` to the property pointer and exchanges a dword, so the four-byte load, storage and replacement are not established. Reusing a destroyed context slot keeps the property and both callback pointers clear, so a new context starts from the same state as a fresh slot. - [sceNpSessionSignalingRegisterInternalCallback](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - stores the callback and user-data pointers in the context and never invokes them. Null pointers are accepted, as the wrapper does not check them, and the call returns 0 after forwarding to the internal object regardless of its result. The callback signature is unknown. From 44a92277ec256811d5a19e2a3d2e934388f7472f Mon Sep 17 00:00:00 2001 From: gslashm <121226870+gslashm@users.noreply.github.com> Date: Thu, 5 Nov 2026 15:05:30 +0200 Subject: [PATCH 395/612] feat(libSceNKWebKit): implement Inspector::IdentifiersFactory _ZN9Inspector18IdentifiersFactory16createIdentifierEv _ZN9Inspector18IdentifiersFactory9requestIdEm --- core/libs/prx/libSceNKWebKit/Export.cpp | 8 --- .../include/InventoryExports.hpp | 2 - .../prx/libSceNKWebKit/include/WtfExports.hpp | 2 + .../src/InspectorIdentifiersFactory.cpp | 52 ++++++++++++++++ .../tests/InspectorIdentifiersFactory.cpp | 59 +++++++++++++++++++ docs/dev/debt/libSceNKWebKit.md | 6 +- 6 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 core/libs/prx/libSceNKWebKit/src/InspectorIdentifiersFactory.cpp create mode 100644 core/libs/prx/libSceNKWebKit/tests/InspectorIdentifiersFactory.cpp diff --git a/core/libs/prx/libSceNKWebKit/Export.cpp b/core/libs/prx/libSceNKWebKit/Export.cpp index ff1b9f1640..d368e37c65 100644 --- a/core/libs/prx/libSceNKWebKit/Export.cpp +++ b/core/libs/prx/libSceNKWebKit/Export.cpp @@ -33318,14 +33318,6 @@ int APS5_VABI _ZN9Inspector17BackendDispatcherD2Ev() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN9Inspector18IdentifiersFactory16createIdentifierEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN9Inspector18IdentifiersFactory9requestIdEm() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN9Inspector18InjectedScriptBaseC1ERKN3WTF6StringE() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp index c5f917a3a0..bc791765f6 100644 --- a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp @@ -5321,8 +5321,6 @@ int APS5_VABI _ZN9Inspector17BackendDispatcher27registerDispatcherForDomainERKN3 int APS5_VABI _ZN9Inspector17BackendDispatcher8dispatchERKN3WTF6StringE(); int APS5_VABI _ZN9Inspector17BackendDispatcherD1Ev(); int APS5_VABI _ZN9Inspector17BackendDispatcherD2Ev(); -int APS5_VABI _ZN9Inspector18IdentifiersFactory16createIdentifierEv(); -int APS5_VABI _ZN9Inspector18IdentifiersFactory9requestIdEm(); int APS5_VABI _ZN9Inspector18InjectedScriptBaseC1ERKN3WTF6StringE(); int APS5_VABI _ZN9Inspector18InjectedScriptBaseC1ERKS0_(); int APS5_VABI _ZN9Inspector18InjectedScriptBaseC2ERKN3WTF6StringE(); diff --git a/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp b/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp index 4dc794e0c8..87e7b3319f 100644 --- a/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp @@ -186,6 +186,8 @@ void* APS5_VABI _ZN8Gigacage29tryAllocateZeroedVirtualPagesENS_4KindEm(std::uint void APS5_VABI _ZN8Gigacage4freeENS_4KindEPv(std::uint32_t, void* ptr); void* APS5_VABI _ZN8Gigacage6mallocENS_4KindEm(std::uint32_t, std::size_t size); void* APS5_VABI _ZN8Gigacage9tryMallocENS_4KindEm(std::uint32_t, std::size_t size); +NKWebKit::WtfAbi::String APS5_VABI _ZN9Inspector18IdentifiersFactory16createIdentifierEv(); +NKWebKit::WtfAbi::String APS5_VABI _ZN9Inspector18IdentifiersFactory9requestIdEm(std::uint64_t identifier); bool APS5_VABI _ZNK3WTF10StringImpl10startsWithEDs(const NKWebKit::WtfAbi::StringImpl* self, char16_t value); bool APS5_VABI _ZNK3WTF10StringImpl10startsWithEPKcj(const NKWebKit::WtfAbi::StringImpl* self, const char* data, unsigned length); std::size_t APS5_VABI _ZNK3WTF10StringImpl11sizeInBytesEv(const NKWebKit::WtfAbi::StringImpl* self); diff --git a/core/libs/prx/libSceNKWebKit/src/InspectorIdentifiersFactory.cpp b/core/libs/prx/libSceNKWebKit/src/InspectorIdentifiersFactory.cpp new file mode 100644 index 0000000000..ee275a1843 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/InspectorIdentifiersFactory.cpp @@ -0,0 +1,52 @@ +// Derived from Sony PS5 WebKit 11.x revision 80f77e94c6, Source/JavaScriptCore/inspector/IdentifiersFactory.cpp +/* + * Copyright (C) 2014-2018 Apple Inc. All rights reserved. + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "prx/libSceNKWebKit/include/WtfExports.hpp" + +#include +#include + +namespace { + +std::uint64_t lastUsedIdentifier = 0; + +NKWebKit::WtfAbi::String AddPrefixToIdentifier(std::uint64_t identifier) { + const std::string text = "0." + std::to_string(identifier); + return _ZN3WTF10StringImpl6createEPKhj(reinterpret_cast(text.data()), + static_cast(text.size())); +} + +} + +extern "C" NKWebKit::WtfAbi::String APS5_VABI _ZN9Inspector18IdentifiersFactory16createIdentifierEv() { + return AddPrefixToIdentifier(++lastUsedIdentifier); +} + +extern "C" NKWebKit::WtfAbi::String APS5_VABI _ZN9Inspector18IdentifiersFactory9requestIdEm( + std::uint64_t identifier) { + return identifier ? AddPrefixToIdentifier(identifier) : NKWebKit::WtfAbi::String(); +} diff --git a/core/libs/prx/libSceNKWebKit/tests/InspectorIdentifiersFactory.cpp b/core/libs/prx/libSceNKWebKit/tests/InspectorIdentifiersFactory.cpp new file mode 100644 index 0000000000..26a7faae26 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/tests/InspectorIdentifiersFactory.cpp @@ -0,0 +1,59 @@ +#include "prx/libSceNKWebKit/libSceNKWebKit.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "Inspector::IdentifiersFactory check failed at line %u\n", location.line()); + std::abort(); + } +} + +void RequireString(const NKWebKit::WtfAbi::String& value, const char* expected) { + const auto length = static_cast(std::strlen(expected)); + Require(value.impl != nullptr); + Require(value.impl->length == length); + Require((value.impl->hashAndFlags & 4) != 0); + Require(std::memcmp(value.impl->data, expected, length) == 0); +} + +using CreateIdentifier = NKWebKit::WtfAbi::String (APS5_VABI *)(); +using RequestId = NKWebKit::WtfAbi::String (APS5_VABI *)(std::uint64_t); +static_assert(std::is_same_v); +static_assert(std::is_same_v); + +void TestCreateIdentifier() { + RequireString(_ZN9Inspector18IdentifiersFactory16createIdentifierEv(), "0.1"); + RequireString(_ZN9Inspector18IdentifiersFactory16createIdentifierEv(), "0.2"); + std::printf("Inspector::IdentifiersFactory::createIdentifier: first identifiers passed\n"); +} + +void TestRequestId() { + const auto zero = _ZN9Inspector18IdentifiersFactory9requestIdEm(0); + Require(zero.impl == nullptr); + RequireString(_ZN9Inspector18IdentifiersFactory9requestIdEm(42), "0.42"); + RequireString(_ZN9Inspector18IdentifiersFactory9requestIdEm(1), "0.1"); + RequireString(_ZN9Inspector18IdentifiersFactory9requestIdEm(18446744073709551615ULL), + "0.18446744073709551615"); + std::printf("Inspector::IdentifiersFactory::requestId: zero, decimal and largest value passed\n"); +} + +void TestRequestIdDoesNotAdvance() { + RequireString(_ZN9Inspector18IdentifiersFactory16createIdentifierEv(), "0.3"); + std::printf("Inspector::IdentifiersFactory: requestId leaves the counter alone\n"); +} + +} + +int main() { + TestCreateIdentifier(); + TestRequestId(); + TestRequestIdDoesNotAdvance(); +} diff --git a/docs/dev/debt/libSceNKWebKit.md b/docs/dev/debt/libSceNKWebKit.md index 6a03a4225c..9ea55cf342 100644 --- a/docs/dev/debt/libSceNKWebKit.md +++ b/docs/dev/debt/libSceNKWebKit.md @@ -12,6 +12,8 @@ Inventory prose retains historical counts; use the [generated progress report](h - [PAL::SessionID exports](../../../core/libs/prx/libSceNKWebKit/src/PalSessionID.cpp) have no console ABI or behavior verification. They follow the [PS5 11.x WebKit source](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebCore/PAL/pal/SessionID.cpp). The generators throw after `enableGenerationProtection` instead of reproducing the `RELEASE_ASSERT` crash. Closes when: compared with a console module. +- [Inspector::IdentifiersFactory exports](../../../core/libs/prx/libSceNKWebKit/src/InspectorIdentifiersFactory.cpp) have no console ABI or behavior verification. They follow the [PS5 11.x WebKit source](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/JavaScriptCore/inspector/IdentifiersFactory.cpp). Closes when: compared with a console module. + ### Functional - [HitTestResult getters](../../../core/libs/prx/libSceNKWebKit/src/HitTestResult.cpp) (libSceNKWebKit) - WKHitTestResult* getters return image/link/media/PDF URLs, link text, bounding box and content editable flag from the host object. @@ -36,7 +38,7 @@ Inventory prose retains historical counts; use the [generated progress report](h - The [revision-pinned identity inventory](https://gist.github.com/kyon-eth/d44995f181e1818977bd38f0e6fc0f34/8afcb14b0707a09b6274ff7f7bcf33ef46cfe2e7) records the module, library, symbol type and NIDs for the declarations in [Export.cpp](../../../core/libs/prx/libSceNKWebKit/Export.cpp); it is a metadata summary rather than independent verification. For the 255 absent from the public catalogue, [per-image ELF records](https://gist.github.com/kyon-eth/147be8dd556e9cced2577ccbed548972/1a659ecfe819e724675991a574a1aebb5fbb48e5) include the raw qualified names, module and library identities, symbol type, values and sizes; the record file SHA-256 is `44bdc4d6073192a8f41b7be359b75364b3d41dbbd11ce649924b8cebdd4ffe51`. Image labels are not authenticated. -- [Additional callable exports](../../../core/libs/prx/libSceNKWebKit/Export.cpp) - 7417 of the 8703 inventory declarations listed below throw through `NotImplemented_nid_no_patch`; signatures and behavior are unverified. The later 13.60-labelled image contains all 8703; 8448 match the public 3.20 catalogue, while 255 are absent and their presence in retail 3.20 is unverified. Per-image records show the latter in a separate 3.20-labelled image, but neither image label is authenticated. Identified names match NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). Data exports need separate analysis. +- [Additional callable exports](../../../core/libs/prx/libSceNKWebKit/Export.cpp) - 7415 of the 8703 inventory declarations listed below throw through `NotImplemented_nid_no_patch`; signatures and behavior are unverified. The later 13.60-labelled image contains all 8703; 8448 match the public 3.20 catalogue, while 255 are absent and their presence in retail 3.20 is unverified. Per-image records show the latter in a separate 3.20-labelled image, but neither image label is authenticated. Identified names match NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). Data exports need separate analysis. | Name | NID | | --- | --- | @@ -5247,8 +5249,6 @@ Inventory prose retains historical counts; use the [generated progress report](h | `_ZN9Inspector17BackendDispatcher8dispatchERKN3WTF6StringE` | `1HngmisEcBY` | | `_ZN9Inspector17BackendDispatcherD1Ev` | `vZuL5d4VEJQ` | | `_ZN9Inspector17BackendDispatcherD2Ev` | `l33jN8bqpfs` | -| `_ZN9Inspector18IdentifiersFactory16createIdentifierEv` | `B0MuvNsV9e0` | -| `_ZN9Inspector18IdentifiersFactory9requestIdEm` | `YGPQ2f427zQ` | | `_ZN9Inspector18InjectedScriptBaseC1ERKN3WTF6StringE` | `9j+Pah7d6mg` | | `_ZN9Inspector18InjectedScriptBaseC1ERKS0_` | `Kd+yyekmbjg` | | `_ZN9Inspector18InjectedScriptBaseC2ERKN3WTF6StringE` | `8pViWaQiKhA` | From 176b899bd8811e36e6de50a069a3d07fc4a164b9 Mon Sep 17 00:00:00 2001 From: akkadbakkad89 Date: Thu, 5 Nov 2026 19:07:29 +0530 Subject: [PATCH 396/612] feat(libSceNpCppWebApi): implement twelve Matches2V1 factories and DataStatus vector iterators Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- core/libs/prx/libSceNpCppWebApi/Export.cpp | 197 +++++++++++++----- .../prx/libSceNpCppWebApi/include/Api.hpp | 28 +-- docs/dev/debt/libSceNpCppWebApi.md | 2 +- 3 files changed, 158 insertions(+), 69 deletions(-) diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp index 199895450d..d658955335 100644 --- a/core/libs/prx/libSceNpCppWebApi/Export.cpp +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -1844,6 +1844,14 @@ static int VectorPushBackString(IntrusivePtr* self, const CommonString* element) static_assert(sizeof(CommonIterator) == 0x10 && offsetof(CommonIterator, element) == 0x08); +static IntrusivePtr* IntrusiveVectorElements(const IntrusivePtr* self, const bool end, const char* function) { + const auto* body = static_cast(self->object); + if (body == nullptr) return nullptr; + if (body->magic != VectorBodyMagic || body->elementKind != 1) NotImplemented_nid_no_patch(function); + auto* elements = static_cast(body->elements); + return end && body->size != 0 ? elements + body->size : elements; +} + static CommonString* StringVectorElements(const IntrusivePtr* self, const bool end, const char* function) { const auto* body = static_cast(self->object); if (body == nullptr) return nullptr; @@ -2623,16 +2631,16 @@ std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1 return HostVectorSize(self, __func__); } -void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE5beginEv(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return nullptr; +CommonIterator* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE5beginEv(CommonIterator* iterator, const IntrusivePtr* self) { + iterator->referenceCount = 0; + iterator->element = IntrusiveVectorElements(self, false, __func__); + return iterator; } -void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE3endEv(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return nullptr; +CommonIterator* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE3endEv(CommonIterator* iterator, const IntrusivePtr* self) { + iterator->referenceCount = 0; + iterator->element = IntrusiveVectorElements(self, true, __func__); + return iterator; } void* APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V59Container11getChildrenEv(void* self) { @@ -3938,6 +3946,76 @@ static int TerminateMatchesParameter(void* self, const bool hasText, const std:: return 0; } +static void APS5_VABI DestroyMatchesModel(void* object) { + std::free(object); +} + +static void APS5_VABI DestroyMatchesModelString16(void* object) { + DestroyMatchesString(MatchesStringAt(object, 16)); + std::free(object); +} + +static void APS5_VABI DestroyAddedPlayer(void* object) { + DestroyMatchesString(MatchesStringAt(object, 16)); + DestroyMatchesString(MatchesStringAt(object, 56)); + DestroyMatchesString(MatchesStringAt(object, 96)); + DestroyMatchesString(MatchesStringAt(object, 136)); + std::free(object); +} + +static void ReleaseMatchesMember(void* object, const std::size_t offset, const std::size_t referenceCountOffset) { + ReleaseIntrusivePtr(reinterpret_cast(static_cast(object) + offset), referenceCountOffset, __func__); +} + +static void APS5_VABI DestroyCreateMatchRequest(void* object) { + DestroyMatchesString(MatchesStringAt(object, 24)); + DestroyMatchesString(MatchesStringAt(object, 56)); + ReleaseMatchesMember(object, 152, ReferenceCountOffset); + std::free(object); +} + +static void APS5_VABI DestroyRequestMatchTeam(void* object) { + DestroyMatchesString(MatchesStringAt(object, 16)); + DestroyMatchesString(MatchesStringAt(object, 48)); + ReleaseMatchesMember(object, 88, VectorReferenceCountOffset); + std::free(object); +} + +static void APS5_VABI DestroyRequestTeamStatistic(void* object) { + DestroyMatchesString(MatchesStringAt(object, 16)); + ReleaseMatchesMember(object, 48, VectorReferenceCountOffset); + ReleaseMatchesMember(object, 80, VectorReferenceCountOffset); + std::free(object); +} + +static void APS5_VABI DestroyMatchesModelVectors16And48(void* object) { + ReleaseMatchesMember(object, 16, VectorReferenceCountOffset); + ReleaseMatchesMember(object, 48, VectorReferenceCountOffset); + std::free(object); +} + +static void APS5_VABI DestroyUpdateMatchDetailRequest(void* object) { + DestroyMatchesString(MatchesStringAt(object, 24)); + ReleaseMatchesMember(object, 112, ReferenceCountOffset); + std::free(object); +} + +static void APS5_VABI DestroyRequestMatchResults(void* object) { + ReleaseMatchesMember(object, 32, ReferenceCountOffset); + std::free(object); +} + +static int MatchesFactoryCreate(const void* context, IntrusivePtr* out, const std::size_t size, void (APS5_VABI* deleter)(void*)) { + if (context == nullptr || out == nullptr) return ParameterInvalidArgument; + void* object = std::calloc(1, size); + if (object == nullptr) return MatchesOutOfMemory; + ReferenceCount(object, ReferenceCountOffset).store(1); + out->object = object; + out->deleter = deleter; + out->libContext = const_cast(context); + return 0; +} + int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V111AddedPlayer12setAccountIdEPKc(void* self, const char* value) { const int result = AssignMatchesString(MatchesStringAt(self, 96), value); if (result < 0) return result; @@ -3991,9 +4069,16 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V117LeaveMatchRequest17setNpServiceLa return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118AddedPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_11AddedPlayerEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118AddedPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_11AddedPlayerEEE(void* self, const char* name, std::int32_t playerType, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0xb0, &DestroyAddedPlayer); + if (created != 0) return created; + ConstructMatchesString(MatchesStringAt(out->object, 16), out->libContext); + ConstructMatchesString(MatchesStringAt(out->object, 56), out->libContext); + ConstructMatchesString(MatchesStringAt(out->object, 96), out->libContext); + ConstructMatchesString(MatchesStringAt(out->object, 136), out->libContext); + const int assigned = AssignMatchesString(MatchesStringAt(out->object, 16), name); + if (assigned < 0) return assigned; + *reinterpret_cast(static_cast(out->object) + 0x30) = playerType; return 0; } @@ -4077,10 +4162,11 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestMatchResults20setCooperati return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RemovedPlayerFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RemovedPlayerEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RemovedPlayerFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RemovedPlayerEEE(void* self, const char* name, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0x38, &DestroyMatchesModelString16); + if (created != 0) return created; + ConstructMatchesString(MatchesStringAt(out->object, 16), out->libContext); + return AssignMatchesString(MatchesStringAt(out->object, 16), name); } int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120ReportResultsRequest17setNpServiceLabelERKi(void* self, const int* value) { @@ -4096,10 +4182,11 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120ReportResultsRequest18setMatchSta return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestMemberFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RequestMemberEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestMemberFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RequestMemberEEE(void* self, const char* name, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0x30, &DestroyMatchesModelString16); + if (created != 0) return created; + ConstructMatchesString(MatchesStringAt(out->object, 16), out->libContext); + return AssignMatchesString(MatchesStringAt(out->object, 16), name); } int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestPlayerResults8setScoreERKd(void* self, const double* value) { @@ -4142,10 +4229,12 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123JoinMatchRequestFactory6createEPN return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123RequestMatchTeamFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_16RequestMatchTeamEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123RequestMatchTeamFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_16RequestMatchTeamEEE(void* self, const char* name, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0x78, &DestroyRequestMatchTeam); + if (created != 0) return created; + ConstructMatchesString(MatchesStringAt(out->object, 16), out->libContext); + ConstructMatchesString(MatchesStringAt(out->object, 48), out->libContext); + return AssignMatchesString(MatchesStringAt(out->object, 16), name); } int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124LeaveMatchRequestFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_13RemovedPlayerEEEEEPNS9_INS3_17LeaveMatchRequestEEE(void* self) { @@ -4200,10 +4289,12 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchStatusRequest17setNpSe return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(void* self, const char* name, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0xb8, &DestroyCreateMatchRequest); + if (created != 0) return created; + ConstructMatchesString(MatchesStringAt(out->object, 24), out->libContext); + ConstructMatchesString(MatchesStringAt(out->object, 56), out->libContext); + return AssignMatchesString(MatchesStringAt(out->object, 24), name); } int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125RequestMatchPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_18RequestMatchPlayerEEE(void* self) { @@ -4224,15 +4315,14 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126AdditionalStatisticFactory6create return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestInGameRosterFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestInGameRosterFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self, IntrusivePtr* out) { + return MatchesFactoryCreate(self, out, 0x50, &DestroyMatchesModelVectors16And48); } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestMatchResultsFactory6createEPNS1_6Common10LibContextENS3_14ResultsVersionEPNS5_12IntrusivePtrINS3_19RequestMatchResultsEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestMatchResultsFactory6createEPNS1_6Common10LibContextENS3_14ResultsVersionEPNS5_12IntrusivePtrINS3_19RequestMatchResultsEEE(void* self, std::int32_t version, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0x40, &DestroyRequestMatchResults); + if (created != 0) return created; + *reinterpret_cast(static_cast(out->object) + 0x10) = version; return 0; } @@ -4248,16 +4338,15 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestPlayerResultsFactory6creat return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestTeamStatisticFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_20RequestTeamStatisticEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestTeamStatisticFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_20RequestTeamStatisticEEE(void* self, const char* name, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0x70, &DestroyRequestTeamStatistic); + if (created != 0) return created; + ConstructMatchesString(MatchesStringAt(out->object, 16), out->libContext); + return AssignMatchesString(MatchesStringAt(out->object, 16), name); } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestMatchStatisticsFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_22RequestMatchStatisticsEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestMatchStatisticsFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_22RequestMatchStatisticsEEE(void* self, IntrusivePtr* out) { + return MatchesFactoryCreate(self, out, 0x50, &DestroyMatchesModelVectors16And48); } int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestPlayerStatisticFactory6createEPNS1_6Common10LibContextEPKcRKNS5_6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEEPNSB_INS3_22RequestPlayerStatisticEEE(void* self) { @@ -4272,21 +4361,21 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V130RequestTeamMemberResultFactory6cr return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131RequestCompetitiveResultFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24RequestCompetitiveResultEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131RequestCompetitiveResultFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24RequestCompetitiveResultEEE(void* self, IntrusivePtr* out) { + return MatchesFactoryCreate(self, out, 0x50, &DestroyMatchesModelVectors16And48); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchDetailRequestFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24UpdateMatchDetailRequestEEE(void* self, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0xa8, &DestroyUpdateMatchDetailRequest); + if (created != 0) return created; + ConstructMatchesString(MatchesStringAt(out->object, 24), out->libContext); return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchDetailRequestFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24UpdateMatchDetailRequestEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchStatusRequestFactory6createEPNS1_6Common10LibContextENS3_12UpdateStatusEPNS5_12IntrusivePtrINS3_24UpdateMatchStatusRequestEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchStatusRequestFactory6createEPNS1_6Common10LibContextENS3_12UpdateStatusEPNS5_12IntrusivePtrINS3_24UpdateMatchStatusRequestEEE(void* self, std::int32_t status, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0x20, &DestroyMatchesModel); + if (created != 0) return created; + *reinterpret_cast(static_cast(out->object) + 0x18) = status; return 0; } diff --git a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp index 178fdd1025..76abf99f39 100644 --- a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp +++ b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp @@ -477,8 +477,8 @@ std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1 void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE5beginEv(void* self); void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE3endEv(void* self); std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE4sizeEv(const void* self); -void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE5beginEv(void* self); -void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE3endEv(void* self); +CommonIterator* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE5beginEv(CommonIterator* iterator, const IntrusivePtr* self); +CommonIterator* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE3endEv(CommonIterator* iterator, const IntrusivePtr* self); void* APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V59Container11getChildrenEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainerC1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer10initializeEPNS1_6Common10LibContextE(void* self, const std::uint64_t* context); @@ -867,7 +867,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V116JoinMatchRequest17setNpServiceLab int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V116RequestMatchTeam10setMembersERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_13RequestMemberEEEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V116RequestMatchTeam11setTeamNameEPKc(void* self, const char* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V117LeaveMatchRequest17setNpServiceLabelERKi(void* self, const int* value); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118AddedPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_11AddedPlayerEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118AddedPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_11AddedPlayerEEE(void* self, const char* name, std::int32_t playerType, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest15setInGameRosterERKNS1_6Common12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest17setExpirationTimeERKi(void* self, const int* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest17setNpServiceLabelERKi(void* self, const int* value); @@ -880,17 +880,17 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestInGameRoster10setPlayersER int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestInGameRoster8setTeamsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_16RequestMatchTeamEEEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestMatchResults20setCompetitiveResultERKNS1_6Common12IntrusivePtrINS3_24RequestCompetitiveResultEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestMatchResults20setCooperativeResultERKNS3_24RequestCooperativeResultE(void* self, const int* value); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RemovedPlayerFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RemovedPlayerEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RemovedPlayerFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RemovedPlayerEEE(void* self, const char* name, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120ReportResultsRequest17setNpServiceLabelERKi(void* self, const int* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120ReportResultsRequest18setMatchStatisticsERKNS1_6Common12IntrusivePtrINS3_22RequestMatchStatisticsEEE(void* self, const IntrusivePtr* value); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestMemberFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RequestMemberEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestMemberFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RequestMemberEEE(void* self, const char* name, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestPlayerResults8setScoreERKd(void* self, const double* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestTeamStatistic23setTeamMemberStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_26RequestTeamMemberStatisticEEEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestTeamStatistic8setStatsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V122RequestMatchStatistics17setTeamStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_20RequestTeamStatisticEEEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V122RequestMatchStatistics19setPlayerStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_22RequestPlayerStatisticEEEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123JoinMatchRequestFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_11AddedPlayerEEEEEPNS9_INS3_16JoinMatchRequestEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123RequestMatchTeamFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_16RequestMatchTeamEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123RequestMatchTeamFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_16RequestMatchTeamEEE(void* self, const char* name, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124LeaveMatchRequestFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_13RemovedPlayerEEEEEPNS9_INS3_17LeaveMatchRequestEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124RequestCompetitiveResult14setTeamResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_18RequestTeamResultsEEEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124RequestCompetitiveResult16setPlayerResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_20RequestPlayerResultsEEEEE(void* self, const IntrusivePtr* value); @@ -899,21 +899,21 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest17setExpi int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest17setNpServiceLabelERKi(void* self, const int* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest9setZoneIdEPKc(void* self, const char* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchStatusRequest17setNpServiceLabelERKi(void* self, const int* value); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(void* self, const char* name, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125RequestMatchPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_18RequestMatchPlayerEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125RequestTeamResultsFactory6createEPNS1_6Common10LibContextEPKciPNS5_12IntrusivePtrINS3_18RequestTeamResultsEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126AdditionalStatisticFactory6createEPNS1_6Common10LibContextEPKcS9_PNS5_12IntrusivePtrINS3_19AdditionalStatisticEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestInGameRosterFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestMatchResultsFactory6createEPNS1_6Common10LibContextENS3_14ResultsVersionEPNS5_12IntrusivePtrINS3_19RequestMatchResultsEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestInGameRosterFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self, IntrusivePtr* out); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestMatchResultsFactory6createEPNS1_6Common10LibContextENS3_14ResultsVersionEPNS5_12IntrusivePtrINS3_19RequestMatchResultsEEE(void* self, std::int32_t version, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127ReportResultsRequestFactory6createEPNS1_6Common10LibContextENS5_12IntrusivePtrINS3_19RequestMatchResultsEEEPNS8_INS3_20ReportResultsRequestEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestPlayerResultsFactory6createEPNS1_6Common10LibContextEPKciPNS5_12IntrusivePtrINS3_20RequestPlayerResultsEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestTeamStatisticFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_20RequestTeamStatisticEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestMatchStatisticsFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_22RequestMatchStatisticsEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestTeamStatisticFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_20RequestTeamStatisticEEE(void* self, const char* name, IntrusivePtr* out); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestMatchStatisticsFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_22RequestMatchStatisticsEEE(void* self, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestPlayerStatisticFactory6createEPNS1_6Common10LibContextEPKcRKNS5_6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEEPNSB_INS3_22RequestPlayerStatisticEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V130RequestTeamMemberResultFactory6createEPNS1_6Common10LibContextEPKcdPNS5_12IntrusivePtrINS3_23RequestTeamMemberResultEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131RequestCompetitiveResultFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24RequestCompetitiveResultEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchDetailRequestFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24UpdateMatchDetailRequestEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchStatusRequestFactory6createEPNS1_6Common10LibContextENS3_12UpdateStatusEPNS5_12IntrusivePtrINS3_24UpdateMatchStatusRequestEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131RequestCompetitiveResultFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24RequestCompetitiveResultEEE(void* self, IntrusivePtr* out); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchDetailRequestFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24UpdateMatchDetailRequestEEE(void* self, IntrusivePtr* out); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchStatusRequestFactory6createEPNS1_6Common10LibContextENS3_12UpdateStatusEPNS5_12IntrusivePtrINS3_24UpdateMatchStatusRequestEEE(void* self, std::int32_t status, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V133RequestTeamMemberStatisticFactory6createEPNS1_6Common10LibContextEPKcRKNS5_6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEEPNSB_INS3_26RequestTeamMemberStatisticEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi10leaveMatchEiRKNS4_21ParameterToLeaveMatchERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi11createMatchEiRKNS4_22ParameterToCreateMatchERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_19CreateMatchResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(void* self); diff --git a/docs/dev/debt/libSceNpCppWebApi.md b/docs/dev/debt/libSceNpCppWebApi.md index 11f34e62bf..1186c51691 100644 --- a/docs/dev/debt/libSceNpCppWebApi.md +++ b/docs/dev/debt/libSceNpCppWebApi.md @@ -33,4 +33,4 @@ -- [sce::Np::CppWebApi::Matches::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA21564/PPSA23732 (builds `J03093737`/`J03341606`). The 34 request/model setters store the value and the is-set flag at the measured offsets; the 10 vector setters copy the `IntrusivePtr` handle and increment the body reference count at +0x30 instead of deep-copying the elements, which is a host choice. A constructor-created empty vector (null backing pointer) is a valid value: those setters copy the empty handle and set the flag instead of returning 0x80553512. The six `ParameterTo*` objects allocate a host implementation of the module's layout (0x40 bytes with the `Common::String` at +0x0 and the request body at +0x20; `ParameterToCreateMatch` uses 0x20 with the body at +0x0): `initialize` takes the actual string and body arguments and returns 0x80553503 for a null context or text, 0x80553502 for an unset context, 0x8055350f when already initialized, 0x80553504 on allocation failure and 0x80553512 for an empty request body; `terminate` returns 0x80553510. The host allocator stands in for the module's `LibContext` allocator. The 22 factories are unsupported and throw `NotImplemented_nid_no_patch` rather than returning a signed-out result; only the six API verbs keep `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`. The three accessors read +0x30/+0x48/+0x28. Response layouts beyond those fields are unverified. +- [sce::Np::CppWebApi::Matches::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA21564/PPSA23732 (builds `J03093737`/`J03341606`). The 34 request/model setters store the value and the is-set flag at the measured offsets; the 10 vector setters copy the `IntrusivePtr` handle and increment the body reference count at +0x30 instead of deep-copying the elements, which is a host choice. A constructor-created empty vector (null backing pointer) is a valid value: those setters copy the empty handle and set the flag instead of returning 0x80553512. The six `ParameterTo*` objects allocate a host implementation of the module's layout and `initialize`/`terminate` follow the measured results. The host allocator stands in for the module's `LibContext` allocator. Twelve factories are implemented from the PPSA03530 (GTA Vice City) bundled module's create protocol: null context or out returns 0x80553503, allocation failure returns 0x80553504, the object is wrapped `{object, deleter, context}` into `out` and each create parameter is stored at its measured offset. `CreateMatchRequestFactory`'s host size 0xb8 exceeds the module's 0x88 to keep the measured setter range at +152..+176 inside the allocation. The remaining ten factories still throw `NotImplemented_nid_no_patch`: their second create parameters have no measured field offset. The `TitleCloudStorage2V1::DataStatus` `Vector::begin`/`end` iterators produce the host iterator, matching the module's `[self]`/`[self+0x10]` element reads; `Product::getLabel` and the TitleCloudStorage `TransactionBase::start` still throw, their bodies unmeasured past the first checks. Only the six API verbs keep `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`. The three accessors read +0x30/+0x48/+0x28. Response layouts beyond those fields are unverified. \ No newline at end of file From d1a47c538bd62c576cc75a121c5d0821ea7f7784 Mon Sep 17 00:00:00 2001 From: akkadbakkad89 Date: Thu, 5 Nov 2026 19:07:31 +0530 Subject: [PATCH 397/612] test(libSceNpCppWebApi): cover Matches2V1 factories and DataStatus vector iterators Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- core/libs/tests/GuestNpCppWebApiMatches.cpp | 66 +++++++++++++++++++ .../GuestNpCppWebApiTitleCloudStorage.cpp | 5 ++ 2 files changed, 71 insertions(+) diff --git a/core/libs/tests/GuestNpCppWebApiMatches.cpp b/core/libs/tests/GuestNpCppWebApiMatches.cpp index 10f6fe2c62..112cabe714 100644 --- a/core/libs/tests/GuestNpCppWebApiMatches.cpp +++ b/core/libs/tests/GuestNpCppWebApiMatches.cpp @@ -247,5 +247,71 @@ int main() { Require(_ZN3sce2Np9CppWebApi7Matches2V18MatchApi22ParameterToCreateMatch10initializeEPNS1_6Common10LibContextENS6_12IntrusivePtrINS3_18CreateMatchRequestEEE(createParameter.data(), &context, &joinBody) == 0); Require(_ZN3sce2Np9CppWebApi7Matches2V18MatchApi22ParameterToCreateMatch9terminateEv(createParameter.data()) == 0); + IntrusivePtr created{}; + Require(_ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(nullptr, "room", &created) == static_cast(0x80553503)); + Require(created.object == nullptr); + Require(_ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(&context, "room", nullptr) == static_cast(0x80553503)); + Require(_ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(&context, nullptr, &created) == static_cast(0x80553503)); + Require(_ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(&context, "room", &created) == 0); + Require(created.object != nullptr && created.deleter != nullptr && created.libContext == &context); + Require(Read(static_cast(created.object), 0) == 1); + Require(Read(static_cast(created.object), 24 + 0x08) != nullptr && std::strcmp(Read(static_cast(created.object), 24 + 0x08), "room") == 0); + created.deleter(created.object); + created = {}; + + Require(_ZN3sce2Np9CppWebApi7Matches2V120RemovedPlayerFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RemovedPlayerEEE(&context, "player", &created) == 0); + Require(created.object != nullptr && created.libContext == &context); + Require(Read(static_cast(created.object), 16 + 0x08) != nullptr && std::strcmp(Read(static_cast(created.object), 16 + 0x08), "player") == 0); + created.deleter(created.object); + created = {}; + + Require(_ZN3sce2Np9CppWebApi7Matches2V118AddedPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_11AddedPlayerEEE(&context, "player", 7, &created) == 0); + Require(created.object != nullptr); + Require(Read(static_cast(created.object), 16 + 0x08) != nullptr && std::strcmp(Read(static_cast(created.object), 16 + 0x08), "player") == 0); + Require(Read(static_cast(created.object), 0x30) == 7); + created.deleter(created.object); + created = {}; + + Require(_ZN3sce2Np9CppWebApi7Matches2V126RequestMatchResultsFactory6createEPNS1_6Common10LibContextENS3_14ResultsVersionEPNS5_12IntrusivePtrINS3_19RequestMatchResultsEEE(&context, 3, &created) == 0); + Require(created.object != nullptr); + Require(Read(static_cast(created.object), 0x10) == 3); + created.deleter(created.object); + created = {}; + + Require(_ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchStatusRequestFactory6createEPNS1_6Common10LibContextENS3_12UpdateStatusEPNS5_12IntrusivePtrINS3_24UpdateMatchStatusRequestEEE(&context, 2, &created) == 0); + Require(Read(static_cast(created.object), 0x18) == 2); + created.deleter(created.object); + created = {}; + + Require(_ZN3sce2Np9CppWebApi7Matches2V126RequestInGameRosterFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_19RequestInGameRosterEEE(&context, &created) == 0); + Require(created.object != nullptr && created.deleter != nullptr && created.libContext == &context); + created.deleter(created.object); + created = {}; + + alignas(8) std::array membersBody{}; + Write(membersBody.data(), 0x00, 0x434e507056656342ULL); + Write(membersBody.data(), 0x30, 1); + IntrusivePtr membersVector{}; + membersVector.object = membersBody.data(); + membersVector.libContext = &context; + Require(_ZN3sce2Np9CppWebApi7Matches2V123RequestMatchTeamFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_16RequestMatchTeamEEE(&context, "team", &created) == 0); + Require(_ZN3sce2Np9CppWebApi7Matches2V116RequestMatchTeam10setMembersERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_13RequestMemberEEEEE(created.object, &membersVector) == 0); + Require(Read(membersBody.data(), 0x30) == 2); + created.deleter(created.object); + Require(Read(membersBody.data(), 0x30) == 1); + created = {}; + + alignas(8) std::array rosterObject{}; + Write(rosterObject.data(), 0, 1); + IntrusivePtr rosterHandle{}; + rosterHandle.object = rosterObject.data(); + rosterHandle.libContext = &context; + Require(_ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(&context, "room", &created) == 0); + Require(_ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest15setInGameRosterERKNS1_6Common12IntrusivePtrINS3_19RequestInGameRosterEEE(created.object, &rosterHandle) == 0); + Require(Read(rosterObject.data(), 0) == 2); + created.deleter(created.object); + Require(Read(rosterObject.data(), 0) == 1); + created = {}; + return 0; } diff --git a/core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp b/core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp index da9a621cb3..bfefd1d03d 100644 --- a/core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp +++ b/core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp @@ -187,5 +187,10 @@ int main() { } TestInitializers(); TestSetters(); + alignas(8) IntrusivePtr emptyVector{}; + CommonIterator iterator{}; + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE5beginEv(&iterator, &emptyVector) == &iterator); + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE3endEv(&iterator, &emptyVector) == &iterator); + Require(iterator.element == nullptr); return 0; } From b1b858d881f6e469b7784f755c38fc8d70cf3e74 Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Fri, 6 Nov 2026 02:41:42 +1300 Subject: [PATCH 398/612] test(libSceNpSessionSignaling): drop the assertions that a valid custom property set throws SetCustomProperty refuses a valid request until its store is known, and the tests asserted that refusal. A test that pins an unimplemented path fails as soon as the path is implemented. On Windows the refusal's runtime_error also does not reach the test's catch and terminates guest_np_session_signaling (0xc0000409); on Linux it is caught. Keep the null-property and stale-context cases. Co-authored-by: edubox <3165165+edubox@users.noreply.github.com> --- core/libs/tests/GuestNpSessionSignaling.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/core/libs/tests/GuestNpSessionSignaling.cpp b/core/libs/tests/GuestNpSessionSignaling.cpp index 1ac672b368..f923f5c1f7 100644 --- a/core/libs/tests/GuestNpSessionSignaling.cpp +++ b/core/libs/tests/GuestNpSessionSignaling.cpp @@ -5,7 +5,6 @@ #include #include #include -#include namespace { @@ -81,24 +80,15 @@ int main() { Require(sceNpSessionSignalingDestroyContext(firstId) == ContextNotFound, "destroy stale context"); Require(sceNpSessionSignalingCreateContext2(reinterpret_cast(contextParam), &contextId) == 0, "reuse context slot"); Require(contextId != firstId, "reused context kept stale id"); - std::uint32_t staleProperty = 0x12345678u; std::uint32_t previousProperty = 0; int callbackMarker = 0; int userDataMarker = 0; - bool staleSetRefused = false; - try { sceNpSessionSignalingSetCustomProperty(contextId, &staleProperty, &previousProperty); } - catch (const std::runtime_error&) { staleSetRefused = true; } - Require(staleSetRefused, "set property before context reuse was not refused"); Require(sceNpSessionSignalingRegisterInternalCallback(contextId, &callbackMarker, &userDataMarker) == 0, "register callback before context reuse"); Require(sceNpSessionSignalingDestroyContext(contextId) == 0, "destroy context before reuse two"); const std::int32_t secondReuseUser = 11; std::memcpy(contextParam, &secondReuseUser, sizeof(secondReuseUser)); Require(sceNpSessionSignalingCreateContext2(reinterpret_cast(contextParam), &contextId) == 0, "reuse context slot two"); Require(sceNpSessionSignalingGetCustomProperty(contextId, &previousProperty) == 0 && previousProperty == 0, "reused context two kept state"); - staleSetRefused = false; - try { sceNpSessionSignalingSetCustomProperty(otherContexts[0], &staleProperty, &previousProperty); } - catch (const std::runtime_error&) { staleSetRefused = true; } - Require(staleSetRefused, "set property before context one reuse was not refused"); Require(sceNpSessionSignalingRegisterInternalCallback(otherContexts[0], &callbackMarker, &userDataMarker) == 0, "register callback before context one reuse"); Require(sceNpSessionSignalingDestroyContext(otherContexts[0]) == 0, "destroy context before reuse one"); const std::int32_t thirdReuseUser = 12; @@ -182,14 +172,6 @@ int main() { Require(sceNpSessionSignalingGetCustomProperty(contextId, &property) == 0 && property == 0, "default custom property"); Require(sceNpSessionSignalingSetCustomProperty(contextId, nullptr, nullptr) == InvalidArgument, "null custom property value"); Require(sceNpSessionSignalingSetCustomProperty(firstId, &property, nullptr) == ContextNotFound, "set custom property stale context"); - std::uint32_t value = 0x12345678u; - std::uint32_t previous = 0xa5a5a5a5u; - bool setRefused = false; - try { sceNpSessionSignalingSetCustomProperty(contextId, &value, &previous); } - catch (const std::runtime_error&) { setRefused = true; } - Require(setRefused, "valid custom property set was not refused"); - Require(previous == 0xa5a5a5a5u, "refused custom property set wrote the previous value"); - Require(sceNpSessionSignalingGetCustomProperty(contextId, &property) == 0 && property == 0, "refused custom property set changed the property"); Require(sceNpSessionSignalingRegisterInternalCallback(firstId, nullptr, nullptr) == ContextNotFound, "register callback stale context"); Require(sceNpSessionSignalingRegisterInternalCallback(contextId, nullptr, nullptr) == 0, "register callback"); const char* const activateSessionId = reinterpret_cast(sessionId); From 3c2c054fa2f2b5c8b2d663e213244bee5e35f40b Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Thu, 5 Nov 2026 14:54:05 +0100 Subject: [PATCH 399/612] feat(libSceIpmi): implement the empty base handlers and destructors --- core/libs/prx/libSceIpmi/CMakeLists.txt | 13 +++++ core/libs/prx/libSceIpmi/PendingExports.cpp | 65 +++++++++------------ core/libs/prx/libSceIpmi/include/Api.hpp | 26 ++++----- core/libs/tests/GuestIpmiNoOps.cpp | 47 +++++++++++++++ docs/dev/debt/libSceIpmi.md | 4 +- 5 files changed, 102 insertions(+), 53 deletions(-) create mode 100644 core/libs/tests/GuestIpmiNoOps.cpp diff --git a/core/libs/prx/libSceIpmi/CMakeLists.txt b/core/libs/prx/libSceIpmi/CMakeLists.txt index f549ae49f1..cffb530135 100644 --- a/core/libs/prx/libSceIpmi/CMakeLists.txt +++ b/core/libs/prx/libSceIpmi/CMakeLists.txt @@ -1,3 +1,16 @@ add_library(libSceIpmi SHARED EXCLUDE_FROM_ALL Export.cpp PendingExports.cpp) target_include_directories(libSceIpmi PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceIpmi) + +if(BUILD_TESTING) + add_test_executable(guest_ipmi_noops_tests ${LIBS_INCLUDE_DIR}/tests/GuestIpmiNoOps.cpp) + target_include_directories(guest_ipmi_noops_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ipmi_noops_tests PRIVATE libSceIpmi libc) + configure_windows_unwind(guest_ipmi_noops_tests) + add_test(NAME guest_ipmi_noops COMMAND guest_ipmi_noops_tests) + set_tests_properties(guest_ipmi_noops PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_ipmi_noops PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceIpmi/PendingExports.cpp b/core/libs/prx/libSceIpmi/PendingExports.cpp index 5970f64dd6..2c915fad3a 100644 --- a/core/libs/prx/libSceIpmi/PendingExports.cpp +++ b/core/libs/prx/libSceIpmi/PendingExports.cpp @@ -14,37 +14,32 @@ int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfig20estimateMsgQueueSizeEmj(vo NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPvmS4_m(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPvmS4_m(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI6Client6Config26SyncMethodInvocationConfigC1Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPvmmS4_m(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPvmmS4_m(void* self) { + (void)self; } -int APS5_VABI _ZN4IPMI7SessionD1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI7SessionD1Ev(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI4impl11SessionImpl9tryGetMsgEjPvPmm(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPKNS_8DataInfoEjPNS_10BufferInfoEj(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPKNS_8DataInfoEjPNS_10BufferInfoEj(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfigC1Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6ServerD2Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6ServerD2Ev(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI6ServerD0Ev(void) { NotImplemented_nid_no_patch(__func__); @@ -62,9 +57,8 @@ int APS5_VABI _ZN4IPMI6Client6ConfigC2Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Server12EventHandler15onSessionKilledEPNS_7SessionE(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6Server12EventHandler15onSessionKilledEPNS_7SessionE(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI4impl11SessionImplD1Ev(void) { NotImplemented_nid_no_patch(__func__); @@ -78,13 +72,11 @@ int APS5_VABI _ZN4IPMI6Client6Config27AsyncMethodInvocationConfigC2Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPKNS_8DataInfoEj(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPKNS_8DataInfoEj(void* self) { + (void)self; } -int APS5_VABI _ZN4IPMI6ClientD2Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6ClientD2Ev(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI4impl11SessionImpl7sendMsgEjPKvmPj(void) { NotImplemented_nid_no_patch(__func__); @@ -94,9 +86,8 @@ int APS5_VABI _ZN4IPMI6ClientC2Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI7SessionD2Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI7SessionD2Ev(void* self) { + (void)self; } int APS5_VABI _ZNK4IPMI4impl11SessionImpl16isPeerPrivilegedEv(void) { NotImplemented_nid_no_patch(__func__); @@ -110,9 +101,8 @@ int APS5_VABI _ZN4IPMI4impl11SessionImplD2Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6ClientD1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6ClientD1Ev(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI6Server12EventHandlerD0Ev(void) { NotImplemented_nid_no_patch(__func__); @@ -146,9 +136,8 @@ int APS5_VABI _ZN4IPMI4impl11SessionImpl27respondToAsyncMethodRequestEjjiPKNS_8D NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Server12EventHandlerD2Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6Server12EventHandlerD2Ev(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI6ClientD0Ev(void) { NotImplemented_nid_no_patch(__func__); @@ -170,17 +159,15 @@ int APS5_VABI _ZN4IPMI7SessionD0Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6ServerD1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6ServerD1Ev(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI6Server6ConfigC1Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Server12EventHandlerD1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6Server12EventHandlerD1Ev(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI4impl11SessionImpl9getServerEv(void) { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/prx/libSceIpmi/include/Api.hpp b/core/libs/prx/libSceIpmi/include/Api.hpp index ae6dfa84ed..49bbabf6ac 100644 --- a/core/libs/prx/libSceIpmi/include/Api.hpp +++ b/core/libs/prx/libSceIpmi/include/Api.hpp @@ -11,31 +11,31 @@ int APS5_VABI sceIpmiMgrRaiseExceptionToPid(void); int APS5_VABI sceIpmiMgrStartDump(void); int APS5_VABI _ZN4IPMI4impl11SessionImplC1Ev(void); int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfig20estimateMsgQueueSizeEmj(void); -int APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPvmS4_m(void); +void APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPvmS4_m(void* self); int APS5_VABI _ZN4IPMI6Client6Config26SyncMethodInvocationConfigC1Ev(void); -int APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPvmmS4_m(void); -int APS5_VABI _ZN4IPMI7SessionD1Ev(void); +void APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPvmmS4_m(void* self); +void APS5_VABI _ZN4IPMI7SessionD1Ev(void* self); int APS5_VABI _ZN4IPMI4impl11SessionImpl9tryGetMsgEjPvPmm(void); -int APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPKNS_8DataInfoEjPNS_10BufferInfoEj(void); +void APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPKNS_8DataInfoEjPNS_10BufferInfoEj(void* self); int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfigC1Ev(void); -int APS5_VABI _ZN4IPMI6ServerD2Ev(void); +void APS5_VABI _ZN4IPMI6ServerD2Ev(void* self); int APS5_VABI _ZN4IPMI6ServerD0Ev(void); int APS5_VABI _ZN4IPMI6Client6Config24estimateClientMemorySizeEv(void); int APS5_VABI _ZN4IPMI6Server6createEPPS0_PKNS0_6ConfigEPvS6_(void); int APS5_VABI _ZN4IPMI6Client6ConfigC2Ev(void); -int APS5_VABI _ZN4IPMI6Server12EventHandler15onSessionKilledEPNS_7SessionE(void); +void APS5_VABI _ZN4IPMI6Server12EventHandler15onSessionKilledEPNS_7SessionE(void* self); int APS5_VABI _ZN4IPMI4impl11SessionImplD1Ev(void); int APS5_VABI _ZN4IPMI4impl11SessionImpl27respondToAsyncMethodRequestEjiPKvm(void); int APS5_VABI _ZN4IPMI6Client6Config27AsyncMethodInvocationConfigC2Ev(void); -int APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPKNS_8DataInfoEj(void); -int APS5_VABI _ZN4IPMI6ClientD2Ev(void); +void APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPKNS_8DataInfoEj(void* self); +void APS5_VABI _ZN4IPMI6ClientD2Ev(void* self); int APS5_VABI _ZN4IPMI4impl11SessionImpl7sendMsgEjPKvmPj(void); int APS5_VABI _ZN4IPMI6ClientC2Ev(void); -int APS5_VABI _ZN4IPMI7SessionD2Ev(void); +void APS5_VABI _ZN4IPMI7SessionD2Ev(void* self); int APS5_VABI _ZNK4IPMI4impl11SessionImpl16isPeerPrivilegedEv(void); int APS5_VABI _ZN4IPMI7SessionC2Ev(void); int APS5_VABI _ZN4IPMI4impl11SessionImplD2Ev(void); -int APS5_VABI _ZN4IPMI6ClientD1Ev(void); +void APS5_VABI _ZN4IPMI6ClientD1Ev(void* self); int APS5_VABI _ZN4IPMI6Server12EventHandlerD0Ev(void); int APS5_VABI _ZN4IPMI7Session6Config25estimateSessionMemorySizeEv(void); int APS5_VABI _ZNK4IPMI6Server6Config29estimateTempWorkingMemorySizeEv(void); @@ -44,15 +44,15 @@ int APS5_VABI _ZN4IPMI4impl11SessionImpl7destroyEv(void); int APS5_VABI _ZN4IPMI6Client6Config27AsyncMethodInvocationConfigC1Ev(void); int APS5_VABI _ZN4IPMI4impl11SessionImpl6getMsgEjPvPmmPj(void); int APS5_VABI _ZN4IPMI4impl11SessionImpl27respondToAsyncMethodRequestEjjiPKNS_8DataInfoEj(void); -int APS5_VABI _ZN4IPMI6Server12EventHandlerD2Ev(void); +void APS5_VABI _ZN4IPMI6Server12EventHandlerD2Ev(void* self); int APS5_VABI _ZN4IPMI6ClientD0Ev(void); int APS5_VABI _ZN4IPMI6Client6createEPPS0_PKNS0_6ConfigEPvS6_(void); int APS5_VABI _ZN4IPMI6ServerC2Ev(void); int APS5_VABI _ZN4IPMI4impl11SessionImplC2Ev(void); int APS5_VABI _ZN4IPMI7SessionD0Ev(void); -int APS5_VABI _ZN4IPMI6ServerD1Ev(void); +void APS5_VABI _ZN4IPMI6ServerD1Ev(void* self); int APS5_VABI _ZN4IPMI6Server6ConfigC1Ev(void); -int APS5_VABI _ZN4IPMI6Server12EventHandlerD1Ev(void); +void APS5_VABI _ZN4IPMI6Server12EventHandlerD1Ev(void* self); int APS5_VABI _ZN4IPMI4impl11SessionImpl9getServerEv(void); int APS5_VABI _ZN4IPMI6Client6ConfigC1Ev(void); int APS5_VABI _ZN4IPMI6Client6Config26SyncMethodInvocationConfigC2Ev(void); diff --git a/core/libs/tests/GuestIpmiNoOps.cpp b/core/libs/tests/GuestIpmiNoOps.cpp new file mode 100644 index 0000000000..5c61c82e7f --- /dev/null +++ b/core/libs/tests/GuestIpmiNoOps.cpp @@ -0,0 +1,47 @@ +#include "prx/libSceIpmi/include/Api.hpp" +#include +#include +#include +#include + +namespace { + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "libSceIpmi no-op handler: %s\n", message); + std::abort(); +} + +using Handler = void (APS5_VABI*)(void*); + +} + +int main() { + const Handler handlers[] = { + _ZN4IPMI7SessionD1Ev, + _ZN4IPMI7SessionD2Ev, + _ZN4IPMI6ClientD1Ev, + _ZN4IPMI6ClientD2Ev, + _ZN4IPMI6ServerD1Ev, + _ZN4IPMI6ServerD2Ev, + _ZN4IPMI6Server12EventHandlerD1Ev, + _ZN4IPMI6Server12EventHandlerD2Ev, + _ZN4IPMI6Server12EventHandler15onSessionKilledEPNS_7SessionE, + _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPvmmS4_m, + _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPKNS_8DataInfoEjPNS_10BufferInfoEj, + _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPvmS4_m, + _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPKNS_8DataInfoEj, + }; + + for (const Handler handler : handlers) { + alignas(8) unsigned char object[0x100]; + for (std::size_t index = 0; index < sizeof(object); ++index) object[index] = 0x5a; + handler(object); + for (std::size_t index = 0; index < sizeof(object); ++index) { + Require(object[index] == 0x5a, "the handler leaves the object untouched"); + } + } + + std::puts("libSceIpmi no-op handler checks passed"); + return 0; +} diff --git a/docs/dev/debt/libSceIpmi.md b/docs/dev/debt/libSceIpmi.md index fa4b20dfcd..d55d5dd23c 100644 --- a/docs/dev/debt/libSceIpmi.md +++ b/docs/dev/debt/libSceIpmi.md @@ -2,10 +2,12 @@ ### Unknown function info -- [libSceIpmi pending exports](../../../core/libs/prx/libSceIpmi/PendingExports.cpp) - 49 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceIpmi.c). Their signatures and behavior are unverified; the no-parameter declarations exist for import resolution and throw through `NotImplemented_nid_no_patch`. +- [libSceIpmi pending exports](../../../core/libs/prx/libSceIpmi/PendingExports.cpp) - 36 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceIpmi.c). Their signatures and behavior are unverified; the no-parameter declarations exist for import resolution and throw through `NotImplemented_nid_no_patch`. - [libSceIpmi pending exports](../../../core/libs/prx/libSceIpmi/Export.cpp) - 5 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceIpmi.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceIpmi no-op handlers](../../../core/libs/prx/libSceIpmi/PendingExports.cpp) - the `Session`, `Client`, `Server` and `Server::EventHandler` `D1`/`D2` destructors and the four `EventHandler` `onSessionKilled`/`onSyncMethodDispatch`/`onAsyncMethodDispatch` callbacks are implemented from the 11.00 module, where each body is a single `ret`; they take the object pointer and leave it untouched. The remaining 36 pending exports still throw. + ### Known export names; ABI prototypes unverified - 40 pending exports in [Export.cpp](../../../core/libs/prx/libSceIpmi/Export.cpp) are present in the [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs) inventory. Each is named after the mangled symbol the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) gives for its NID, and the NID of that name matches. Their prototypes and behavior are unverified. They use the no-parameter import-resolution convention and throw through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. From 6de60c35713929d05d88206a9b3b9d38acba05ad Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Thu, 5 Nov 2026 15:02:50 +0100 Subject: [PATCH 400/612] feat(libSceIpmi): implement the config estimators and empty queries --- core/libs/prx/libSceIpmi/CMakeLists.txt | 13 ++++++ core/libs/prx/libSceIpmi/Export.cpp | 15 ++++--- core/libs/prx/libSceIpmi/PendingExports.cpp | 18 ++++---- core/libs/prx/libSceIpmi/include/Api.hpp | 12 +++--- core/libs/tests/GuestIpmiConfigEstimators.cpp | 43 +++++++++++++++++++ docs/dev/debt/libSceIpmi.md | 6 ++- 6 files changed, 85 insertions(+), 22 deletions(-) create mode 100644 core/libs/tests/GuestIpmiConfigEstimators.cpp diff --git a/core/libs/prx/libSceIpmi/CMakeLists.txt b/core/libs/prx/libSceIpmi/CMakeLists.txt index cffb530135..1d085ad4db 100644 --- a/core/libs/prx/libSceIpmi/CMakeLists.txt +++ b/core/libs/prx/libSceIpmi/CMakeLists.txt @@ -14,3 +14,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_ipmi_config_estimators_tests ${LIBS_INCLUDE_DIR}/tests/GuestIpmiConfigEstimators.cpp) + target_include_directories(guest_ipmi_config_estimators_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ipmi_config_estimators_tests PRIVATE libSceIpmi libc) + configure_windows_unwind(guest_ipmi_config_estimators_tests) + add_test(NAME guest_ipmi_config_estimators COMMAND guest_ipmi_config_estimators_tests) + set_tests_properties(guest_ipmi_config_estimators PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_ipmi_config_estimators PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceIpmi/Export.cpp b/core/libs/prx/libSceIpmi/Export.cpp index 7d74d75265..10f820b7c4 100644 --- a/core/libs/prx/libSceIpmi/Export.cpp +++ b/core/libs/prx/libSceIpmi/Export.cpp @@ -46,8 +46,9 @@ int APS5_VABI _ZN4IPMI4impl10ClientImpl11getUserDataEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjPiPvPmm() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjPiPvPmm(void* self, unsigned int, int*, void*, unsigned long*, unsigned long) { + (void)self; + return 0; } int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjjPiPNS_10BufferInfoEj() { @@ -74,8 +75,9 @@ int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKNS_8DataInfoEjPjPK NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKvmPjPKNS_6Client12EventNotifeeE() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKvmPjPKNS_6Client12EventNotifeeE(void* self, unsigned int, const void*, unsigned long, unsigned int*, const void*) { + (void)self; + return 0; } int APS5_VABI _ZN4IPMI4impl10ClientImpl19terminateConnectionEv() { @@ -170,8 +172,9 @@ int APS5_VABI _ZN4IPMI4impl11SessionImpl11getUserDataEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN4IPMI4impl11SessionImpl11tryDispatchEPvm() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN4IPMI4impl11SessionImpl11tryDispatchEPvm(void* self, void*, unsigned long) { + (void)self; + return 0; } int APS5_VABI _ZN4IPMI4impl11SessionImpl12getClientPidEv() { diff --git a/core/libs/prx/libSceIpmi/PendingExports.cpp b/core/libs/prx/libSceIpmi/PendingExports.cpp index 2c915fad3a..4a12a0d86f 100644 --- a/core/libs/prx/libSceIpmi/PendingExports.cpp +++ b/core/libs/prx/libSceIpmi/PendingExports.cpp @@ -45,9 +45,9 @@ int APS5_VABI _ZN4IPMI6ServerD0Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Client6Config24estimateClientMemorySizeEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::size_t APS5_VABI _ZN4IPMI6Client6Config24estimateClientMemorySizeEv(void* self) { + (void)self; + return 0x1000; } int APS5_VABI _ZN4IPMI6Server6createEPPS0_PKNS0_6ConfigEPvS6_(void) { NotImplemented_nid_no_patch(__func__); @@ -108,13 +108,15 @@ int APS5_VABI _ZN4IPMI6Server12EventHandlerD0Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI7Session6Config25estimateSessionMemorySizeEv(void) { - NotImplemented_nid_no_patch(__func__); +std::size_t APS5_VABI _ZN4IPMI7Session6Config25estimateSessionMemorySizeEv(void* self) { + (void)self; return 0; } -int APS5_VABI _ZNK4IPMI6Server6Config29estimateTempWorkingMemorySizeEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::size_t APS5_VABI _ZNK4IPMI6Server6Config29estimateTempWorkingMemorySizeEv(const void* self) { + const auto* values = static_cast(self); + const std::uint64_t fromFirst = values[0] + 0x100; + const std::uint64_t fromSecond = values[1] + 0x100; + return fromFirst >= fromSecond ? fromFirst : fromSecond; } int APS5_VABI _ZN4IPMI4impl11SessionImplD0Ev(void) { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/prx/libSceIpmi/include/Api.hpp b/core/libs/prx/libSceIpmi/include/Api.hpp index 49bbabf6ac..55cbef0741 100644 --- a/core/libs/prx/libSceIpmi/include/Api.hpp +++ b/core/libs/prx/libSceIpmi/include/Api.hpp @@ -20,7 +20,7 @@ void APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionE int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfigC1Ev(void); void APS5_VABI _ZN4IPMI6ServerD2Ev(void* self); int APS5_VABI _ZN4IPMI6ServerD0Ev(void); -int APS5_VABI _ZN4IPMI6Client6Config24estimateClientMemorySizeEv(void); +std::size_t APS5_VABI _ZN4IPMI6Client6Config24estimateClientMemorySizeEv(void* self); int APS5_VABI _ZN4IPMI6Server6createEPPS0_PKNS0_6ConfigEPvS6_(void); int APS5_VABI _ZN4IPMI6Client6ConfigC2Ev(void); void APS5_VABI _ZN4IPMI6Server12EventHandler15onSessionKilledEPNS_7SessionE(void* self); @@ -37,8 +37,8 @@ int APS5_VABI _ZN4IPMI7SessionC2Ev(void); int APS5_VABI _ZN4IPMI4impl11SessionImplD2Ev(void); void APS5_VABI _ZN4IPMI6ClientD1Ev(void* self); int APS5_VABI _ZN4IPMI6Server12EventHandlerD0Ev(void); -int APS5_VABI _ZN4IPMI7Session6Config25estimateSessionMemorySizeEv(void); -int APS5_VABI _ZNK4IPMI6Server6Config29estimateTempWorkingMemorySizeEv(void); +std::size_t APS5_VABI _ZN4IPMI7Session6Config25estimateSessionMemorySizeEv(void* self); +std::size_t APS5_VABI _ZNK4IPMI6Server6Config29estimateTempWorkingMemorySizeEv(const void* self); int APS5_VABI _ZN4IPMI4impl11SessionImplD0Ev(void); int APS5_VABI _ZN4IPMI4impl11SessionImpl7destroyEv(void); int APS5_VABI _ZN4IPMI6Client6Config27AsyncMethodInvocationConfigC1Ev(void); @@ -62,14 +62,14 @@ int APS5_VABI _ZN4IPMI6Server6ConfigC2Ev(void); int APS5_VABI _ZN4IPMI4impl10ClientImpl10disconnectEv(); int APS5_VABI _ZN4IPMI4impl10ClientImpl10initializeEPKNS_6Client6ConfigEPv(); int APS5_VABI _ZN4IPMI4impl10ClientImpl11getUserDataEv(); -int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjPiPvPmm(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjPiPvPmm(void* self, unsigned int, int*, void*, unsigned long*, unsigned long); int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjjPiPNS_10BufferInfoEj(); int APS5_VABI _ZN4IPMI4impl10ClientImpl13pollEventFlagEjmjPm(); int APS5_VABI _ZN4IPMI4impl10ClientImpl13waitEventFlagEjmjPmPj(); int APS5_VABI _ZN4IPMI4impl10ClientImpl16invokeSyncMethodEjPKNS_8DataInfoEjPiPNS_10BufferInfoEj(); int APS5_VABI _ZN4IPMI4impl10ClientImpl16invokeSyncMethodEjPKvmPiPvPmm(); int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKNS_8DataInfoEjPjPKNS_6Client12EventNotifeeE(); -int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKvmPjPKNS_6Client12EventNotifeeE(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKvmPjPKNS_6Client12EventNotifeeE(void* self, unsigned int, const void*, unsigned long, unsigned int*, const void*); int APS5_VABI _ZN4IPMI4impl10ClientImpl19terminateConnectionEv(); int APS5_VABI _ZN4IPMI4impl10ClientImpl6getMsgEjPvPmmPj(); int APS5_VABI _ZN4IPMI4impl10ClientImpl7connectEPKvmPi(); @@ -93,7 +93,7 @@ int APS5_VABI _ZN4IPMI4impl10ServerImplD1Ev(); int APS5_VABI _ZN4IPMI4impl10ServerImplD2Ev(); int APS5_VABI _ZN4IPMI4impl11SessionImpl10trySendMsgEjPKvm(); int APS5_VABI _ZN4IPMI4impl11SessionImpl11getUserDataEv(); -int APS5_VABI _ZN4IPMI4impl11SessionImpl11tryDispatchEPvm(); +int APS5_VABI _ZN4IPMI4impl11SessionImpl11tryDispatchEPvm(void* self, void*, unsigned long); int APS5_VABI _ZN4IPMI4impl11SessionImpl12getClientPidEv(); int APS5_VABI _ZN4IPMI4impl11SessionImpl12setEventFlagEjm(); int APS5_VABI _ZN4IPMI4impl11SessionImpl13emptyMsgQueueEj(); diff --git a/core/libs/tests/GuestIpmiConfigEstimators.cpp b/core/libs/tests/GuestIpmiConfigEstimators.cpp new file mode 100644 index 0000000000..1e7f9d597b --- /dev/null +++ b/core/libs/tests/GuestIpmiConfigEstimators.cpp @@ -0,0 +1,43 @@ +#include "prx/libSceIpmi/include/Api.hpp" +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "libSceIpmi config estimator: %s\n", message); + std::abort(); +} + +} + +int main() { + alignas(8) unsigned char config[0x40]; + std::memset(config, 0, sizeof(config)); + + Require(_ZN4IPMI7Session6Config25estimateSessionMemorySizeEv(config) == 0, "Session::Config size is zero"); + Require(_ZN4IPMI6Client6Config24estimateClientMemorySizeEv(config) == 0x1000, "Client::Config size is 0x1000"); + + const std::uint64_t first[] = {0x200, 0x300}; + Require(_ZNK4IPMI6Server6Config29estimateTempWorkingMemorySizeEv(first) == 0x400, "Server::Config takes the larger first value"); + const std::uint64_t second[] = {0x500, 0x100}; + Require(_ZNK4IPMI6Server6Config29estimateTempWorkingMemorySizeEv(second) == 0x600, "Server::Config takes the larger second value"); + + alignas(8) unsigned char object[0x40]; + std::memset(object, 0x5a, sizeof(object)); + Require(_ZN4IPMI4impl10ClientImpl12tryGetResultEjPiPvPmm(object, 0, nullptr, nullptr, nullptr, 0) == 0, + "ClientImpl::tryGetResult returns zero"); + Require(_ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKvmPjPKNS_6Client12EventNotifeeE(object, 0, nullptr, 0, nullptr, nullptr) == 0, + "ClientImpl::invokeAsyncMethod returns zero"); + Require(_ZN4IPMI4impl11SessionImpl11tryDispatchEPvm(object, nullptr, 0) == 0, "SessionImpl::tryDispatch returns zero"); + for (std::size_t index = 0; index < sizeof(object); ++index) { + Require(object[index] == 0x5a, "the query leaves the object untouched"); + } + + std::puts("libSceIpmi config estimator checks passed"); + return 0; +} diff --git a/docs/dev/debt/libSceIpmi.md b/docs/dev/debt/libSceIpmi.md index d55d5dd23c..d403d7153f 100644 --- a/docs/dev/debt/libSceIpmi.md +++ b/docs/dev/debt/libSceIpmi.md @@ -2,15 +2,17 @@ ### Unknown function info -- [libSceIpmi pending exports](../../../core/libs/prx/libSceIpmi/PendingExports.cpp) - 36 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceIpmi.c). Their signatures and behavior are unverified; the no-parameter declarations exist for import resolution and throw through `NotImplemented_nid_no_patch`. +- [libSceIpmi pending exports](../../../core/libs/prx/libSceIpmi/PendingExports.cpp) - 33 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceIpmi.c). Their signatures and behavior are unverified; the no-parameter declarations exist for import resolution and throw through `NotImplemented_nid_no_patch`. - [libSceIpmi pending exports](../../../core/libs/prx/libSceIpmi/Export.cpp) - 5 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceIpmi.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [libSceIpmi no-op handlers](../../../core/libs/prx/libSceIpmi/PendingExports.cpp) - the `Session`, `Client`, `Server` and `Server::EventHandler` `D1`/`D2` destructors and the four `EventHandler` `onSessionKilled`/`onSyncMethodDispatch`/`onAsyncMethodDispatch` callbacks are implemented from the 11.00 module, where each body is a single `ret`; they take the object pointer and leave it untouched. The remaining 36 pending exports still throw. +- [libSceIpmi config estimators and empty queries](../../../core/libs/prx/libSceIpmi/PendingExports.cpp) - the three memory estimators and the three non-blocking queries are implemented from the 11.00 module: `Session::Config::estimateSessionMemorySize` returns 0, `Client::Config::estimateClientMemorySize` returns 0x1000, `Server::Config::estimateTempWorkingMemorySize` returns `max(field0 + 0x100, field1 + 0x100)`, and `ClientImpl::tryGetResult` (`PKvm` overload), `ClientImpl::invokeAsyncMethod` (`PKvm` overload) and `SessionImpl::tryDispatch` return 0 without touching the object. + ### Known export names; ABI prototypes unverified -- 40 pending exports in [Export.cpp](../../../core/libs/prx/libSceIpmi/Export.cpp) are present in the [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs) inventory. Each is named after the mangled symbol the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) gives for its NID, and the NID of that name matches. Their prototypes and behavior are unverified. They use the no-parameter import-resolution convention and throw through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. +- 37 pending exports in [Export.cpp](../../../core/libs/prx/libSceIpmi/Export.cpp) are present in the [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs) inventory. Each is named after the mangled symbol the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) gives for its NID, and the NID of that name matches. Their prototypes and behavior are unverified. They use the no-parameter import-resolution convention and throw through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. ### Additional callable exports From 13d448288475b106fbe4d39b046126b28dbe4d6b Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Fri, 6 Nov 2026 03:14:16 +1300 Subject: [PATCH 401/612] fix(libSceVideodec): read the full size fields and check them before the structures ConfigInfo::thisSize and ResourceInfo::thisSize are 64-bit, so read them as std::uint64_t in CreateDecoder and QueryResourceInfo. Decode, Flush, DeleteDecoder and Reset now read and check each thisSize before copying the whole structure in, so a short structure at the end of a mapped page gets the struct size error, not the pointer error. CreateDecoder no longer writes ctrl before it can fail; the copy-out after creation already cleans up. The debt entry now says that the console's answer for an unreadable pointer is not measured. Tests cover size fields with only their upper bits set, short structures at the end of a page, an untouched ctrl after a failed create, and read-only output blocks. Co-authored-by: fogydes <173232664+fogydes@users.noreply.github.com> --- core/libs/prx/libSceVideodec/Export.cpp | 45 ++++++++---- core/libs/tests/GuestVideodec.cpp | 95 +++++++++++++++++++++++++ docs/dev/debt/libSceVideodec.md | 2 +- 3 files changed, 127 insertions(+), 15 deletions(-) diff --git a/core/libs/prx/libSceVideodec/Export.cpp b/core/libs/prx/libSceVideodec/Export.cpp index 052fbf7f4f..f8f4fcbd88 100644 --- a/core/libs/prx/libSceVideodec/Export.cpp +++ b/core/libs/prx/libSceVideodec/Export.cpp @@ -139,9 +139,9 @@ extern "C" { int APS5_VABI sceVideodecCreateDecoder(const ConfigInfo* config, const ResourceInfo* resource, Ctrl* ctrl) { if (config == nullptr || resource == nullptr || ctrl == nullptr) return VideodecErrorArgumentPointer; - std::uint32_t configSize = 0; + std::uint64_t configSize = 0; if (!GuestCopyIn_nid_no_patch(&configSize, config, sizeof(configSize))) return VideodecErrorArgumentPointer; - std::uint32_t resourceSize = 0; + std::uint64_t resourceSize = 0; if (!GuestCopyIn_nid_no_patch(&resourceSize, resource, sizeof(resourceSize))) return VideodecErrorArgumentPointer; if (configSize < sizeof(ConfigInfo) || resourceSize < sizeof(ResourceInfo)) return VideodecErrorStructSize; ConfigInfo localConfig{}; @@ -152,7 +152,6 @@ int APS5_VABI sceVideodecCreateDecoder(const ConfigInfo* config, const ResourceI localCtrl.thisSize = sizeof(Ctrl); localCtrl.version = 1; localCtrl.handle = nullptr; - if (!GuestCopyOut_nid_no_patch(ctrl, &localCtrl, sizeof(localCtrl))) return VideodecErrorArgumentPointer; const auto decoderConfig = MapConfig(localConfig); SceVideodec2::DecoderMemoryInfo decoderMemory{}; decoderMemory.thisSize = sizeof(SceVideodec2::DecoderMemoryInfo); @@ -193,6 +192,18 @@ int APS5_VABI sceVideodecCreateDecoder(const ConfigInfo* config, const ResourceI int APS5_VABI sceVideodecDecode(Ctrl* ctrl, const SceVideodec2::InputData* input, FrameBuffer* frameBuffer, PictureInfo* picture) { if (ctrl == nullptr || input == nullptr || picture == nullptr) return VideodecErrorArgumentPointer; if (frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; + std::uint64_t ctrlSize = 0; + if (!GuestCopyIn_nid_no_patch(&ctrlSize, ctrl, sizeof(ctrlSize))) return VideodecErrorArgumentPointer; + std::uint64_t inputSize = 0; + if (!GuestCopyIn_nid_no_patch(&inputSize, input, sizeof(inputSize))) return VideodecErrorArgumentPointer; + std::uint64_t pictureSize = 0; + if (!GuestCopyIn_nid_no_patch(&pictureSize, picture, sizeof(pictureSize))) return VideodecErrorArgumentPointer; + std::uint64_t frameBufferInfoSize = 0; + if (!GuestCopyIn_nid_no_patch(&frameBufferInfoSize, frameBuffer, sizeof(frameBufferInfoSize))) return VideodecErrorFrameBufferPointer; + if (ctrlSize < sizeof(Ctrl)) return VideodecErrorStructSize; + if (inputSize < sizeof(SceVideodec2::InputData)) return VideodecErrorStructSize; + if (pictureSize < sizeof(PictureInfo)) return VideodecErrorStructSize; + if (frameBufferInfoSize < sizeof(FrameBuffer)) return VideodecErrorStructSize; Ctrl localCtrl{}; if (!GuestCopyIn_nid_no_patch(&localCtrl, ctrl, sizeof(localCtrl))) return VideodecErrorArgumentPointer; SceVideodec2::InputData localInput{}; @@ -201,10 +212,6 @@ int APS5_VABI sceVideodecDecode(Ctrl* ctrl, const SceVideodec2::InputData* input if (!GuestCopyIn_nid_no_patch(&localPicture, picture, sizeof(localPicture))) return VideodecErrorArgumentPointer; FrameBuffer localFrameBuffer{}; if (!GuestCopyIn_nid_no_patch(&localFrameBuffer, frameBuffer, sizeof(localFrameBuffer))) return VideodecErrorFrameBufferPointer; - if (localCtrl.thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; - if (localInput.thisSize < sizeof(SceVideodec2::InputData)) return VideodecErrorStructSize; - if (localPicture.thisSize < sizeof(PictureInfo)) return VideodecErrorStructSize; - if (localFrameBuffer.thisSize < sizeof(FrameBuffer)) return VideodecErrorStructSize; auto* decoder = FindDecoder(localCtrl.handle); if (decoder == nullptr) return VideodecErrorHandle; if (localInput.auData == nullptr) return VideodecErrorAuPointer; @@ -234,9 +241,11 @@ int APS5_VABI sceVideodecDecode(Ctrl* ctrl, const SceVideodec2::InputData* input int APS5_VABI sceVideodecDeleteDecoder(Ctrl* ctrl) { if (ctrl == nullptr) return VideodecErrorArgumentPointer; + std::uint64_t ctrlSize = 0; + if (!GuestCopyIn_nid_no_patch(&ctrlSize, ctrl, sizeof(ctrlSize))) return VideodecErrorArgumentPointer; + if (ctrlSize < sizeof(Ctrl)) return VideodecErrorStructSize; Ctrl localCtrl{}; if (!GuestCopyIn_nid_no_patch(&localCtrl, ctrl, sizeof(localCtrl))) return VideodecErrorArgumentPointer; - if (localCtrl.thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; Decoder* decoder = nullptr; { std::lock_guard lock(decodersMutex); @@ -259,15 +268,21 @@ int APS5_VABI sceVideodecDeleteDecoder(Ctrl* ctrl) { int APS5_VABI sceVideodecFlush(Ctrl* ctrl, FrameBuffer* frameBuffer, PictureInfo* picture) { if (ctrl == nullptr || picture == nullptr) return VideodecErrorArgumentPointer; if (frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; + std::uint64_t ctrlSize = 0; + if (!GuestCopyIn_nid_no_patch(&ctrlSize, ctrl, sizeof(ctrlSize))) return VideodecErrorArgumentPointer; + std::uint64_t pictureSize = 0; + if (!GuestCopyIn_nid_no_patch(&pictureSize, picture, sizeof(pictureSize))) return VideodecErrorArgumentPointer; + std::uint64_t frameBufferInfoSize = 0; + if (!GuestCopyIn_nid_no_patch(&frameBufferInfoSize, frameBuffer, sizeof(frameBufferInfoSize))) return VideodecErrorFrameBufferPointer; + if (ctrlSize < sizeof(Ctrl)) return VideodecErrorStructSize; + if (pictureSize < sizeof(PictureInfo)) return VideodecErrorStructSize; + if (frameBufferInfoSize < sizeof(FrameBuffer)) return VideodecErrorStructSize; Ctrl localCtrl{}; if (!GuestCopyIn_nid_no_patch(&localCtrl, ctrl, sizeof(localCtrl))) return VideodecErrorArgumentPointer; PictureInfo localPicture{}; if (!GuestCopyIn_nid_no_patch(&localPicture, picture, sizeof(localPicture))) return VideodecErrorArgumentPointer; FrameBuffer localFrameBuffer{}; if (!GuestCopyIn_nid_no_patch(&localFrameBuffer, frameBuffer, sizeof(localFrameBuffer))) return VideodecErrorFrameBufferPointer; - if (localCtrl.thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; - if (localPicture.thisSize < sizeof(PictureInfo)) return VideodecErrorStructSize; - if (localFrameBuffer.thisSize < sizeof(FrameBuffer)) return VideodecErrorStructSize; auto* decoder = FindDecoder(localCtrl.handle); if (decoder == nullptr) return VideodecErrorHandle; if (localFrameBuffer.frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; @@ -295,9 +310,11 @@ int APS5_VABI sceVideodecFlush(Ctrl* ctrl, FrameBuffer* frameBuffer, PictureInfo int APS5_VABI sceVideodecReset(Ctrl* ctrl) { if (ctrl == nullptr) return VideodecErrorArgumentPointer; + std::uint64_t ctrlSize = 0; + if (!GuestCopyIn_nid_no_patch(&ctrlSize, ctrl, sizeof(ctrlSize))) return VideodecErrorArgumentPointer; + if (ctrlSize < sizeof(Ctrl)) return VideodecErrorStructSize; Ctrl localCtrl{}; if (!GuestCopyIn_nid_no_patch(&localCtrl, ctrl, sizeof(localCtrl))) return VideodecErrorArgumentPointer; - if (localCtrl.thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; auto* decoder = FindDecoder(localCtrl.handle); if (decoder == nullptr) return VideodecErrorHandle; try { @@ -315,9 +332,9 @@ int APS5_VABI sceVideodecMapMemory(void) { int APS5_VABI sceVideodecQueryResourceInfo(const ConfigInfo* config, ResourceInfo* resource) { if (config == nullptr || resource == nullptr) return VideodecErrorArgumentPointer; - std::uint32_t configSize = 0; + std::uint64_t configSize = 0; if (!GuestCopyIn_nid_no_patch(&configSize, config, sizeof(configSize))) return VideodecErrorArgumentPointer; - std::uint32_t resourceSize = 0; + std::uint64_t resourceSize = 0; if (!GuestCopyIn_nid_no_patch(&resourceSize, resource, sizeof(resourceSize))) return VideodecErrorArgumentPointer; if (configSize < sizeof(ConfigInfo) || resourceSize < sizeof(ResourceInfo)) return VideodecErrorStructSize; ConfigInfo localConfig{}; diff --git a/core/libs/tests/GuestVideodec.cpp b/core/libs/tests/GuestVideodec.cpp index 3f18fea093..c0d3ff00f2 100644 --- a/core/libs/tests/GuestVideodec.cpp +++ b/core/libs/tests/GuestVideodec.cpp @@ -2,11 +2,18 @@ #include "prx/libSceVideodec/include/Api.hpp" #include #include +#include #include #include #include #include "prx/libc/include/General.hpp" #include "H264Fixture.hpp" +#ifdef _WIN32 +#include +#else +#include +#include +#endif using namespace SceVideodec; @@ -38,6 +45,52 @@ void check(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } +class PageTail { + void* memory = nullptr; + std::size_t page = 0; + void* data = nullptr; + +public: + PageTail(const void* source, std::size_t bytes) { +#ifdef _WIN32 + SYSTEM_INFO info; + GetSystemInfo(&info); + page = info.dwPageSize; + memory = VirtualAlloc(nullptr, page * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + check(memory != nullptr, "the guard pages could not be allocated"); + DWORD previous; + check(VirtualProtect(static_cast(memory) + page, page, PAGE_NOACCESS, &previous) != 0, "the guard page could not be protected"); +#else + page = static_cast(sysconf(_SC_PAGESIZE)); + memory = mmap(nullptr, page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + check(memory != MAP_FAILED, "the guard pages could not be allocated"); + check(mprotect(static_cast(memory) + page, page, PROT_NONE) == 0, "the guard page could not be protected"); +#endif + check(bytes <= page, "the guarded value does not fit in a page"); + data = static_cast(memory) + page - bytes; + std::memcpy(data, source, bytes); + } + PageTail(const PageTail&) = delete; + PageTail& operator=(const PageTail&) = delete; + ~PageTail() { +#ifdef _WIN32 + VirtualFree(memory, 0, MEM_RELEASE); +#else + munmap(memory, page * 2); +#endif + } + void ReadOnly() { +#ifdef _WIN32 + DWORD previous; + check(VirtualProtect(memory, page, PAGE_READONLY, &previous) != 0, "the page could not be made read-only"); +#else + check(mprotect(memory, page, PROT_READ) == 0, "the page could not be made read-only"); +#endif + } + template + T* As() const { return static_cast(data); } +}; + struct Session { Ctrl ctrl{sizeof(Ctrl)}; std::vector buffer; @@ -284,6 +337,48 @@ void testQueryResourceInfo() { check(refused.cpuMemorySize == 7 && refused.cpuGpuMemorySize == 7 && refused.maxFrameBufferSize == 7 && refused.frameBufferAlignment == 7, "a refused query wrote the resource information"); check(shortResource.cpuMemorySize == 7 && shortResource.maxFrameBufferSize == 7, "a refused query wrote the resource information"); + + auto wideConfig = config; + wideConfig.thisSize = std::uint64_t{1} << 32; + auto wideResource = resource; + wideResource.thisSize = std::uint64_t{1} << 32; + check(sceVideodecQueryResourceInfo(&wideConfig, &wideResource) == 0, "a size field with only its upper bits set was refused by query"); + Ctrl wideCtrl{std::uint64_t{1} << 32}; + check(sceVideodecCreateDecoder(&wideConfig, &resource, &wideCtrl) == 0, "a size field with only its upper bits set was refused by create"); + wideCtrl.thisSize = std::uint64_t{1} << 32; + check(sceVideodecDeleteDecoder(&wideCtrl) == 0, "a control block size with only its upper bits set was refused by delete"); + + Ctrl kept{0x5a5a5a5a5a5a5a5au, reinterpret_cast(std::uintptr_t{0x5a}), 0x5au}; + auto unsizedConfig = config; + unsizedConfig.maxFrameWidth = 0; + check(sceVideodecCreateDecoder(&unsizedConfig, &resource, &kept) == ErrorConfigInfo, "create accepted a configuration without a frame size"); + check(kept.thisSize == 0x5a5a5a5a5a5a5a5au && kept.handle == reinterpret_cast(std::uintptr_t{0x5a}) && kept.version == 0x5au, "a failed create wrote the control block"); + + const std::uint64_t shortSize = 8; + PageTail shortCtrl(&shortSize, sizeof(shortSize)); + PageTail shortFrame(&shortSize, sizeof(shortSize)); + PageTail shortPicture(&shortSize, sizeof(shortSize)); + const SceVideodec2::InputData empty{sizeof(SceVideodec2::InputData), nullptr, 0, 0, 0, 0}; + FrameBuffer frame{sizeof(FrameBuffer), nullptr, 0}; + PictureInfo picture{sizeof(PictureInfo)}; + Ctrl sized{sizeof(Ctrl)}; + check(sceVideodecDeleteDecoder(shortCtrl.As()) == ErrorStructSize, "delete gave a pointer error for a short control block at the end of a page"); + check(sceVideodecReset(shortCtrl.As()) == ErrorStructSize, "reset gave a pointer error for a short control block at the end of a page"); + check(sceVideodecFlush(shortCtrl.As(), &frame, &picture) == ErrorStructSize, "flush gave a pointer error for a short control block at the end of a page"); + check(sceVideodecFlush(&sized, shortFrame.As(), &picture) == ErrorStructSize, "flush gave a pointer error for a short frame buffer at the end of a page"); + check(sceVideodecFlush(&sized, &frame, shortPicture.As()) == ErrorStructSize, "flush gave a pointer error for a short picture at the end of a page"); + check(sceVideodecDecode(shortCtrl.As(), &empty, &frame, &picture) == ErrorStructSize, "decode gave a pointer error for a short control block at the end of a page"); + check(sceVideodecDecode(&sized, &empty, shortFrame.As(), &picture) == ErrorStructSize, "decode gave a pointer error for a short frame buffer at the end of a page"); + + PageTail readOnlyResource(&resource, sizeof(resource)); + readOnlyResource.ReadOnly(); + check(sceVideodecQueryResourceInfo(&config, readOnlyResource.As()) == ErrorArgumentPointer, "query accepted a read-only resource information block"); + const Ctrl unwritten{sizeof(Ctrl)}; + PageTail readOnlyCtrl(&unwritten, sizeof(unwritten)); + readOnlyCtrl.ReadOnly(); + check(sceVideodecCreateDecoder(&config, &resource, readOnlyCtrl.As()) == ErrorArgumentPointer, "create accepted a read-only control block"); + check(sceVideodecCreateDecoder(&config, &resource, &ctrl) == 0, "create failed after a refused read-only control block"); + check(sceVideodecDeleteDecoder(&ctrl) == 0, "the decoder created after a refused read-only control block was not deleted"); } } diff --git a/docs/dev/debt/libSceVideodec.md b/docs/dev/debt/libSceVideodec.md index 8b6fa3aa29..384315f9df 100644 --- a/docs/dev/debt/libSceVideodec.md +++ b/docs/dev/debt/libSceVideodec.md @@ -13,4 +13,4 @@ - [sceVideodecDecode and sceVideodecFlush](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - the picture information's video usability information (the tick rate, the sample aspect ratio and the colour description) stays zero, as the decoder does not report it, and the crop offsets are the decoded picture's crop halved, the unit the H.264 offsets use. The frame buffer holds the coded picture the decoder reports, so the rows the crop covers are in it and the caller crops them. A picture deeper than 8 bits, such as one of a High 10 stream, throws, as what the console writes for it is not known; when its rows do not fit the staging buffer sized for 8-bit pictures of the configured size, the decode fails with the decode error before that - [sceVideodecMapMemory](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - its name is listed in the PS5 3.20 export table and cross-checked against PS5 13.60. Its signature and behaviour are unverified; this no-parameter declaration only resolves the import and throws through NotImplemented_nid_no_patch. -- [sceVideodec pointer safety](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - `sceVideodecCreateDecoder`, `sceVideodecDecode`, `sceVideodecDeleteDecoder`, `sceVideodecFlush`, `sceVideodecReset`, and `sceVideodecQueryResourceInfo` validate caller input and output pointers using `GuestCopyIn_nid_no_patch` and `GuestCopyOut_nid_no_patch`, returning `VideodecErrorArgumentPointer` or `VideodecErrorFrameBufferPointer` on invalid or unwritable memory rather than causing host access violations. On native console firmware, memory fault handling is managed by the kernel or hardware protection; this project returns the library pointer error. +- [sceVideodec pointer safety](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - `sceVideodecCreateDecoder`, `sceVideodecDecode`, `sceVideodecDeleteDecoder`, `sceVideodecFlush`, `sceVideodecReset`, and `sceVideodecQueryResourceInfo` validate caller input and output pointers using `GuestCopyIn_nid_no_patch` and `GuestCopyOut_nid_no_patch`, returning `VideodecErrorArgumentPointer` or `VideodecErrorFrameBufferPointer` on invalid or unwritable memory rather than causing host access violations. What the console returns for an unreadable or unwritable pointer is not measured; these functions return the pointer errors instead. From 6f8651bd180978861e3cfe000edbca0220dea480 Mon Sep 17 00:00:00 2001 From: Kikoarl <7354010+Kikoarl@users.noreply.github.com> Date: Thu, 5 Nov 2026 15:22:23 +0100 Subject: [PATCH 402/612] fix(libSceNet): safely validate output pointers in sceNetGetStatisticsInfo and sceNetGetRandom --- core/libs/prx/libSceNet/Export.cpp | 23 +++++++++++++++++------ core/libs/tests/GuestSceNet.cpp | 14 +++++++++++++- docs/dev/debt/libSceNet.md | 2 ++ 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 9e14d46d5e..c3812a5426 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -9,7 +9,6 @@ #include #else #include -#include #include #include #include @@ -25,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -2808,7 +2808,11 @@ int APS5_VABI sceNetClearDnsCache(int flags) { int APS5_VABI sceNetGetStatisticsInfo(NetStatisticsInfo* info, std::uint32_t flags) { if (info == nullptr) return fail(NET_EFAULT); constexpr std::int32_t FREE_BYTES = 16 * 1024 * 1024; - *info = {FREE_BYTES, FREE_BYTES, 0, 0, FREE_BYTES, FREE_BYTES}; + const NetStatisticsInfo stats{FREE_BYTES, FREE_BYTES, 0, 0, FREE_BYTES, FREE_BYTES}; + const int savedErrno = errno; + const bool copied = GuestCopyOut_nid_no_patch(info, &stats, sizeof(stats)); + errno = savedErrno; + if (!copied) return fail(NET_EFAULT); return 0; } @@ -3003,10 +3007,17 @@ int APS5_VABI sceNetGetNameToIndex(std::uint32_t interfaceId) { int APS5_VABI sceNetGetRandom(std::uint32_t* output) { if (!g_net_inited) return fail(NET_ENOTINIT); if (output == nullptr) return fail(NET_EINVAL); - static std::mutex mutex; - static std::random_device device; - const std::lock_guard lock(mutex); - *output = static_cast(device()); + const int savedErrno = errno; + std::uint32_t value{}; + { + static std::mutex mutex; + static std::random_device device; + const std::lock_guard lock(mutex); + value = static_cast(device()); + } + const bool copied = GuestCopyOut_nid_no_patch(output, &value, sizeof(value)); + errno = savedErrno; + if (!copied) return fail(NET_EFAULT); return 0; } diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 47a78c252d..0123496c74 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -23,6 +23,7 @@ #include "tests/TemporaryFile.hpp" #include #include +#include #include #include #include @@ -331,9 +332,14 @@ static void CheckPoolErrors() { static void CheckStatisticsInfo() { Require(Failed(sceNetGetStatisticsInfo(nullptr, 0), 14)); + Require(errno == 14); + Require(Failed(sceNetGetStatisticsInfo(reinterpret_cast(std::uintptr_t{0x10}), 0), 14)); + Require(errno == 14); NetStatisticsInfo info; std::memset(&info, 0xaa, sizeof(info)); + errno = 71; Require(sceNetGetStatisticsInfo(&info, 0) == 0); + Require(errno == 71); Require(sizeof(info) == 24); Require(info.kernel_mem_free_size > 0 && info.kernel_mem_free_min > 0); Require(info.libnet_mem_free_size > 0 && info.libnet_mem_free_min > 0); @@ -342,6 +348,7 @@ static void CheckStatisticsInfo() { Require(info.packet_count == 0 && info.packet_qos_count == 0); NetStatisticsInfo again; Require(sceNetGetStatisticsInfo(&again, 0x1234) == 0); + Require(errno == 71); Require(std::memcmp(&again, &info, sizeof(info)) == 0); } @@ -484,11 +491,16 @@ static void CheckDefaultRoute() { static void CheckRandom() { Require(Failed(sceNetGetRandom(nullptr), 22)); + Require(errno == 22); std::array values{}; + errno = 71; for (auto& value : values) Require(sceNetGetRandom(&value) == 0); + Require(errno == 71); std::array guarded{0x5a5a5a5au, 0x5a5a5a5au}; - Require(sceNetGetRandom(guarded.data()) == 0 && guarded[1] == 0x5a5a5a5au && *sceNetErrnoLoc() == 22); + Require(sceNetGetRandom(guarded.data()) == 0 && guarded[1] == 0x5a5a5a5au && *sceNetErrnoLoc() == 22 && errno == 71); Require(std::any_of(values.begin() + 1, values.end(), [&](std::uint32_t value) { return value != values[0]; })); + Require(Failed(sceNetGetRandom(reinterpret_cast(std::uintptr_t{0x10})), 14)); + Require(errno == 14); } static void CheckByteOrderConversion() { diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index b160dc9b3f..3efc5777cd 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -54,6 +54,8 @@ ### Functional - [sceNetGetMacAddress](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the console's result for a non-null unwritable output is unmeasured. AnyPS5 returns `EFAULT` through a checked guest write instead of crashing the host; null output and invalid flags retain `EINVAL`. The synthetic address remains independent of the host adapter. +- [sceNetGetStatisticsInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the console's result for a non-null unwritable output is unmeasured. AnyPS5 returns `EFAULT` through a checked guest write instead of crashing the host while preserving the caller's errno; null output retains `EFAULT`. +- [sceNetGetRandom](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the console's result for a non-null unwritable output is unmeasured. AnyPS5 returns `EFAULT` through a checked guest write instead of crashing the host while preserving the caller's errno; null output retains `EINVAL`. - [IPV6_V6ONLY in sceNetSetsockopt and sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) is unverified on PS5. The IPv6 level (41), option (27), exact integer input size, nonzero enable behavior, boolean readback and rejection after binding follow FreeBSD 11.4's [`ip6_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet6/ip6_output.c). The shared libkernel option implementation applies the host's IPv6-only mode; disabling it permits IPv4-mapped traffic on a socket bound to the IPv6 wildcard address. Host network configuration controls IPv6 availability. Null buffers retain this library's `EINVAL` behavior instead of FreeBSD's fault handling. Crimson Desert (PPSA25286) disables it, passing a 4-byte 0 at both of its two calls; of the other nine titles checked, only Borderlands 4 (PPSA01494) passes an IPv6-level option as a constant, one `IPV6_JOIN_GROUP` (12). From aa14f404798c5e313e0a840bbb6a5dba71ac1e0c Mon Sep 17 00:00:00 2001 From: Evrim Cicek Date: Mon, 26 Oct 2026 16:32:26 +0300 Subject: [PATCH 403/612] test(libkernel): end a blocked receive when its socket is shut for reading --- core/libs/tests/GuestTcp.cpp | 55 ++++++++++++++++++++++++++++++++++++ core/libs/tests/GuestUdp.cpp | 47 ++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/core/libs/tests/GuestTcp.cpp b/core/libs/tests/GuestTcp.cpp index 2538d521a1..ba32a68be0 100644 --- a/core/libs/tests/GuestTcp.cpp +++ b/core/libs/tests/GuestTcp.cpp @@ -3,6 +3,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include #include #include #include @@ -275,6 +276,59 @@ static void SocketNameFaults() { for (const int descriptor : {unconnected, accepted, client, listener}) Require(close_nid_postfix(descriptor) == 0); } +static void BlockedReceiveEndsAtShutRead() { + enum Call { Receive, ReceiveFrom, ReceiveMessage, Read, ThroughDuplicate, WithTimeout, Calls }; + for (int call = Receive; call < Calls; ++call) { + const int listener = socket_nid_postfix(2, 1, 0); + Require(listener >= 0); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(listener, address.data(), address.size()) == 0 && listen_nid_postfix(listener, 4) == 0); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(listener, address.data(), &size) == 0); + const int client = socket_nid_postfix(2, 1, 0); + Require(client >= 0 && connect_nid_postfix(client, address.data(), address.size()) == 0); + const int accepted = accept_nid_postfix(listener, nullptr, nullptr); + Require(accepted >= 0); + const int watched = call == ThroughDuplicate ? dup_nid_postfix(accepted) : accepted; + Require(watched >= 0); + if (call == WithTimeout) { + const KernelTimeval timeout{3, 0}; + Require(setsockopt_nid_postfix(accepted, 0xffff, 0x1006, &timeout, sizeof(timeout)) == 0); + } + char buffer[8]; + std::array sender{}; + std::uint32_t senderSize = sender.size(); + KernelIovec vector{buffer, sizeof(buffer)}; + GuestMsghdr message{sender.data(), static_cast(sender.size()), &vector, 1, nullptr, 0, -1}; + std::atomic entered{false}; + std::atomic returned{false}; + std::int64_t received = -2; + std::chrono::steady_clock::duration waited{}; + std::thread receiver([&] { + const auto start = std::chrono::steady_clock::now(); + entered = true; + if (call == ReceiveFrom) received = recvfrom_nid_postfix(watched, buffer, sizeof(buffer), 0, sender.data(), &senderSize); + else if (call == ReceiveMessage) received = recvmsg_nid_postfix(watched, &message, 0); + else if (call == Read) received = read_nid_postfix(watched, buffer, sizeof(buffer)); + else received = recv_nid_postfix(watched, buffer, sizeof(buffer), 0); + waited = std::chrono::steady_clock::now() - start; + returned = true; + }); + while (!entered) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + Require(!returned); + Require(shutdown_nid_postfix(accepted, 0) == 0); + for (int poll = 0; poll < 100 && !returned; ++poll) std::this_thread::sleep_for(std::chrono::milliseconds(20)); + if (!returned) Require(shutdown_nid_postfix(client, 2) == 0); + receiver.join(); + Require(received == 0 && waited >= std::chrono::milliseconds(40) && waited < std::chrono::seconds(2)); + if (call == ReceiveFrom) Require(senderSize == 0); + if (call == ReceiveMessage) Require(message.nameLength == 0 && message.flags == 0); + if (call == ThroughDuplicate) Require(close_nid_postfix(watched) == 0); + for (const int descriptor : {client, accepted, listener}) Require(close_nid_postfix(descriptor) == 0); + } +} + int main() { StreamReceiveReportsNoSender(); SocketNameFaults(); @@ -282,6 +336,7 @@ int main() { AcceptedSocketsFollowListenerBlocking(); WaitAllReceivesTheWholeRequest(); ReceiveAnsweredWithoutTheHost(); + BlockedReceiveEndsAtShutRead(); UnboundSocketNameIsZero(); const int listener = socket_nid_postfix(2, 1, 6); Require(listener >= 0); diff --git a/core/libs/tests/GuestUdp.cpp b/core/libs/tests/GuestUdp.cpp index e177213445..99b6c7a997 100644 --- a/core/libs/tests/GuestUdp.cpp +++ b/core/libs/tests/GuestUdp.cpp @@ -4,6 +4,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include #include #include #include @@ -221,6 +222,49 @@ static void CheckShutReadUnconnected() { Require(close_nid_postfix(receiver) == 0 && close_nid_postfix(sender) == 0); } +#ifndef __APPLE__ +static void CheckShutReadEndsBlockedReceive() { + for (const bool stamped : {false, true}) { + const int receiver = socket_nid_postfix(2, 2, 0); + const int sender = socket_nid_postfix(2, 2, 0); + Require(receiver >= 0 && sender >= 0); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(receiver, address.data(), address.size()) == 0); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(receiver, address.data(), &size) == 0); + const int enabled = 1; + if (stamped) Require(setsockopt_nid_postfix(receiver, 0xffff, 0x400, &enabled, sizeof(enabled)) == 0); + std::atomic entered{false}; + std::atomic returned{false}; + std::int64_t received = -2; + std::array control{}; + std::uint32_t controlLength = 0; + std::chrono::steady_clock::duration waited{}; + std::thread waiter([&] { + char buffer[4]; + KernelIovec vector{buffer, sizeof(buffer)}; + GuestMsghdr message{nullptr, 0, &vector, 1, control.data(), static_cast(control.size()), -1}; + const auto start = std::chrono::steady_clock::now(); + entered = true; + received = stamped ? recvmsg_nid_postfix(receiver, &message, 0) : recv_nid_postfix(receiver, buffer, sizeof(buffer), 0); + waited = std::chrono::steady_clock::now() - start; + controlLength = message.controlLength; + returned = true; + }); + while (!entered) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + Require(!returned); + Require(shutdown_nid_postfix(receiver, 0) == -1 && *__error_nid_postfix() == 57); + for (int poll = 0; poll < 100 && !returned; ++poll) std::this_thread::sleep_for(std::chrono::milliseconds(20)); + if (!returned) Require(sendto_nid_postfix(sender, "x", 1, 0, address.data(), address.size()) == 1); + waiter.join(); + Require(received == 0 && waited >= std::chrono::milliseconds(40) && waited < std::chrono::seconds(2)); + if (stamped) Require(controlLength == 0); + Require(close_nid_postfix(receiver) == 0 && close_nid_postfix(sender) == 0); + } +} +#endif + static void CheckDontWait() { const int receiver = socket_nid_postfix(2, 2, 0); const int sender = socket_nid_postfix(2, 2, 0); @@ -501,6 +545,9 @@ int main() { CheckDontWait(); CheckSendToPortZero(); CheckShutReadUnconnected(); +#ifndef __APPLE__ + CheckShutReadEndsBlockedReceive(); +#endif const int receiver = socket_nid_postfix(2, 2, 0); const int sender = socket_nid_postfix(2, 2, 17); Require(receiver >= 0 && sender >= 0 && receiver != sender); From 11f2ffcc4083b9acada3d22c014ee232363859d2 Mon Sep 17 00:00:00 2001 From: Evrim Cicek Date: Mon, 26 Oct 2026 16:32:26 +0300 Subject: [PATCH 404/612] fix(libkernel): end a blocked receive on Windows when its socket is shut for reading --- core/libs/prx/libkernel/Socket/src/Udp.cpp | 68 ++++++++++++++++------ docs/dev/debt/libkernel.md | 3 +- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index 11d3088b20..8e2ed3ec73 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -944,7 +944,15 @@ int SocketSendFlags(Socket& socket, int flags) { std::lock_guard lock(socket.modeMutex); return NativeSendFlags(socket.options.noSignal ? flags | GuestNoSignal : flags); } -int WaitForTransfer(Socket& socket, bool send, int flags) { +#ifdef _WIN32 +bool ReceiveWouldBlock(const Socket& socket) { + if (socket.type == 1) return PeerConnected(socket); + sockaddr_storage name{}; + socklen_t size = sizeof(name); + return ::getsockname(socket.value, reinterpret_cast(&name), &size) == 0; +} +#endif +int WaitForTransfer(Socket& socket, bool send, int flags, int receiver = -1) { if ((flags & GuestDontWait) != 0) { #ifdef _WIN32 NativePollDescriptor entry{}; @@ -961,22 +969,38 @@ int WaitForTransfer(Socket& socket, bool send, int flags) { if (socket.options.nonblocking) return 0; timeout = send ? socket.options.sendTimeout : socket.options.receiveTimeout; } - if (timeout == 0) return 0; +#ifdef _WIN32 + const bool watched = receiver >= 0 && ReceiveWouldBlock(socket); +#else + static_cast(receiver); + constexpr bool watched = false; +#endif + if (timeout == 0 && !watched) return 0; + const ReadWatch watch(watched ? std::vector{receiver} : std::vector{}); const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(timeout >> 32) + std::chrono::nanoseconds((static_cast(static_cast(timeout)) * 1000000000) >> 32); for (;;) { - const auto left = deadline - std::chrono::steady_clock::now(); - if (left <= std::chrono::steady_clock::duration::zero()) return 35; - const int milliseconds = static_cast(std::min(std::chrono::ceil(left).count(), INT_MAX)); - NativePollDescriptor entry{}; - entry.fd = socket.value; + if (watched && ReceiveShut(socket)) return 0; + int milliseconds = -1; + if (timeout != 0) { + const auto left = deadline - std::chrono::steady_clock::now(); + if (left <= std::chrono::steady_clock::duration::zero()) return 35; + milliseconds = static_cast(std::min(std::chrono::ceil(left).count(), INT_MAX)); + } + NativePollDescriptor entries[2]{}; + entries[0].fd = socket.value; #ifdef _WIN32 - entry.events = static_cast(send ? POLLWRNORM : POLLRDNORM); - const int ready = WSAPoll(&entry, 1, milliseconds); - if (ready != 0) return 0; + entries[0].events = static_cast(send ? POLLWRNORM : POLLRDNORM); + if (watched) { + entries[1].fd = ThreadWaker().value; + entries[1].events = POLLRDNORM; + } + const int ready = WSAPoll(entries, watched ? 2 : 1, milliseconds); + if (ready < 0 || entries[0].revents != 0) return 0; + if (entries[1].revents != 0) DrainWaker(entries[1].fd); #else - entry.events = static_cast(send ? POLLOUT : POLLIN); - const int ready = ::poll(&entry, 1, milliseconds); + entries[0].events = static_cast(send ? POLLOUT : POLLIN); + const int ready = ::poll(entries, 1, milliseconds); if (ready > 0 || (ready < 0 && errno != EINTR)) return 0; #endif } @@ -1621,7 +1645,8 @@ std::int64_t APS5_VABI recv_nid_postfix(int descriptor, void* buffer, std::uint6 const GuestArena::HostWrite destination(length == 0 ? nullptr : buffer, static_cast(length)); if (!destination.Open()) return Fail(14); if (const int answered = ReceiveAnsweredHere(*socket, length); answered >= 0) return answered == 0 ? 0 : Fail(answered); - if (const int waited = WaitForTransfer(*socket, false, flags)) return Fail(waited); + if (const int waited = WaitForTransfer(*socket, false, flags, descriptor)) return Fail(waited); + if (ReceiveShut(*socket)) return 0; const auto result = ::recv(socket->value, static_cast(buffer), static_cast(length), NativeReceiveFlags(flags, socket->type)); #ifdef _WIN32 @@ -1678,9 +1703,9 @@ int APS5_VABI shutdown_nid_postfix(int descriptor, int how) { sockaddr_storage peer{}; socklen_t size = sizeof(peer); const bool unconnected = socket->type == 2 && ::getpeername(socket->value, reinterpret_cast(&peer), &size) != 0; - if (::shutdown(socket->value, how) && !unconnected && !AlreadyShut(socket->value, how)) return Fail(NativeError()); { std::lock_guard lock(socket->modeMutex); + if (::shutdown(socket->value, how) && !unconnected && !AlreadyShut(socket->value, how)) return Fail(NativeError()); if (how != 1) socket->receiveShut = true; if (how != 0) socket->sendShut = true; } @@ -1861,7 +1886,11 @@ std::int64_t APS5_VABI recvfrom_nid_postfix(int descriptor, void* buffer, std::u } sockaddr_storage native{}; socklen_t size = sizeof(native); - if (const int waited = WaitForTransfer(*socket, false, flags)) return Fail(waited); + if (const int waited = WaitForTransfer(*socket, false, flags, descriptor)) return Fail(waited); + if (ReceiveShut(*socket)) { + if (addressLength && !ReportAddress(address, addressLength, nullptr, address ? 0 : capacity)) return Fail(14); + return 0; + } std::uint64_t limit = length; #ifdef _WIN32 if (length != 0 && (limit = GuestPrefix(buffer, length, WritableProtection)) < length) { @@ -2187,11 +2216,12 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, ReceivedRights rights; bool controlTruncated = false; #endif - const bool shut = ReceiveShut(*socket); + bool shut = ReceiveShut(*socket); std::vector stamps; if (!shut && (total != 0 || socket->type == 1 || ReadableNow(*socket))) { size = sizeof(native); - if (const int waited = WaitForTransfer(*socket, false, flags)) return Fail(waited); + if (const int waited = WaitForTransfer(*socket, false, flags, descriptor)) return Fail(waited); + shut = ReceiveShut(*socket); #ifndef _WIN32 if (passing) { std::vector control(CMSG_SPACE(MostPassedDescriptors * sizeof(int))); @@ -2227,7 +2257,7 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, if (const int error = rights.Install((flags & GuestControlCloseOnExec) != 0)) return Fail(error); } else #endif - received = ::recvfrom(socket->value, buffer.data(), static_cast(buffer.size()), + if (!shut) received = ::recvfrom(socket->value, buffer.data(), static_cast(buffer.size()), NativeReceiveFlags(flags, socket->type), reinterpret_cast(&native), &size); if (received < 0) { #ifdef _WIN32 @@ -2237,7 +2267,7 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, return Fail(NativeError()); #endif } - if (header.control) { + if (header.control && !(received == 0 && ReceiveShut(*socket))) { int keptFlags = 0; { std::lock_guard lock(socket->modeMutex); diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 08a150589a..ebbd688c3c 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -281,10 +281,11 @@ - Guest sockets on macOS ([the equeue wait, poll, kevent, fstat and ioctl](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp), [gethostbyname](../../../core/libs/prx/libScePosixForWebKit/Resolver.cpp)): macOS has no `ppoll`, so the wait uses `poll` with the timeout rounded up to a millisecond; it has no `POLLRDHUP`, so a stream that the peer closed is found by peeking one byte when it is readable; XNU's `poll` reports `POLLHUP`, with `POLLPRI` and without `POLLOUT`, once either direction of a stream has ended, so a guest stream socket it reports hung up is asked about each direction alone and keeps `POLLHUP` only once both have ended, as FreeBSD does; a call that asks for no event gets nothing, where FreeBSD reports `POLLHUP`; `FIONREAD` on a datagram socket, and the byte count that `fstat` and `kevent` report for it, is `SO_NREAD`, the size of the next datagram, because macOS adds the source address to `FIONREAD`; and `gethostbyname` replaces `gethostbyname_r`, which macOS lacks. - [recvmsg](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) loses received stream data on an output copy fault: outputs are checked after the host receive. FreeBSD 11.4 leaves unread stream data queued, though it also drops a datagram on a copy fault ([`soreceive_dgram`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L2308-L2312), [`soreceive_generic`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1758-L1774)). A Linux `process_vm_readv` or `process_vm_writev` failure other than `EFAULT` throws - [MSG_DONTWAIT](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) in `send`, `sendto`, `sendmsg`, `recv`, `recvfrom` and `recvmsg` makes that one call non-blocking through the host's `MSG_DONTWAIT`, as FreeBSD 11.4's `soreceive_generic` returns `EWOULDBLOCK` for it (`sys/kern/uipc_socket.c`). Winsock has no per-call non-blocking flag, so on Windows the call first asks `WSAPoll` with a zero timeout whether the socket is ready and fails with `EAGAIN` when it is not. The check and the transfer are two host calls: a second thread that takes the data in between can leave a `recv` blocked, and a `send` of more bytes than the socket buffer holds can still block until it has sent them, where FreeBSD's `sosend_generic` returns a partial count -- [MSG_WAITALL](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) in `recv`, `recvfrom` and `recvmsg` on a stream socket is passed to the host, which returns when the buffer is full, the peer closes or an error occurs, as FreeBSD 11.4's [`soreceive_generic`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1855-L1860) does; a datagram socket ignores it. It is not measured on a console, a nonblocking socket and a call that also passes `MSG_PEEK` follow the host's rules, and `SO_RCVTIMEO` bounds only the wait for the first data. Any other flag still fails with `EOPNOTSUPP`. +- [MSG_WAITALL](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) in `recv`, `recvfrom` and `recvmsg` on a stream socket is passed to the host, which returns when the buffer is full, the peer closes or an error occurs, as FreeBSD 11.4's [`soreceive_generic`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1855-L1860) does; a datagram socket ignores it. It is not measured on a console, a nonblocking socket and a call that also passes `MSG_PEEK` follow the host's rules, and `SO_RCVTIMEO` bounds only the wait for the first data. On Windows, `shutdown(SHUT_RD)` from another thread does not end a wait that already holds part of the data, where that loop stops at `SBS_CANTRCVMORE` and returns it. Any other flag still fails with `EOPNOTSUPP`. - [recv and recvfrom](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) answer some cases without the host. A zero-length receive on a stream socket returns 0, or `ENOTCONN` when it is not connected ([L1566-L1578](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1566-L1578)), as on a PS5 (firmware 13.60). On an empty datagram socket it returns 0 at once; the console was measured only with `MSG_DONTWAIT`, and the blocking case follows FreeBSD 11.4's `soreceive_dgram`, which returns 0 before waiting. `recvmsg` answers the empty datagram socket the same way but asks the host in the stream cases. A datagram another thread takes after the check makes a zero-length receive wait - [`shutdown(SHUT_RD)`](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) on a Windows TCP socket resets the connection when data is unread or arrives later, so the peer's next send fails with `EPIPE`, where FreeBSD's [`sorflush`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L2387-L2432) discards the data and the peer's sends succeed. The reset also makes `poll` report `POLLHUP` and `POLLERR` beside `POLLIN` for the shut socket, where FreeBSD's [`sopoll_generic`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L3060-L3103) reports only `POLLIN` and `POLLRDNORM`. On a Unix stream socket FreeBSD keeps appending the peer's later data ([`uipc_send`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_usrreq.c#L936-L1001) does not check the receiver's `SBS_CANTRCVMORE`) and receives return it; here receives return 0 and the peer's send fails with `EPIPE` on Linux. - [sendmsg and recvmsg](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) pass descriptors in `SCM_RIGHTS` messages on Unix-domain sockets through the host's `SCM_RIGHTS` (Linux only), in FreeBSD's guest layout: a 12-byte `cmsghdr`, data at offset 16, `CMSG_SPACE` records. Several `SCM_RIGHTS` messages in one `sendmsg` arrive as one record because the host merges them (FreeBSD keeps one per message), so a buffer sized for the first can't receive it alone. A received socket gets default emulated socket options. Sending a guest kqueue returns `EOPNOTSUPP`, as FreeBSD's `unp_internalize` does; a random device, directory or JIT shared memory descriptor (libkernel's state) throws, as does a zero-byte stream send with descriptors (Linux carries no record without data). Windows throws (socket pairs are loopback TCP), as do other control message types and control data on IP sockets. `MSG_PEEK` with a control buffer reads only the data and leaves passed descriptors queued. +- [Blocking receives](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - `shutdown(SHUT_RD)` from another thread does not end every `recv`, `recvfrom` or `recvmsg` blocked on the socket, where FreeBSD's [`sorflush`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L2403-L2409) wakes them all. On Windows it misses a receive whose data a third thread took between its `WSAPoll` wait and the host receive, and one whose descriptor was closed or replaced while it waited when the shutdown goes through another descriptor of the socket. On macOS it misses a receive on an unconnected datagram socket: XNU's [`soshutdown`](https://github.com/apple-oss-distributions/xnu/blob/xnu-12377.121.6/bsd/kern/uipc_socket.c#L4351-L4356) fails with `ENOTCONN` without flushing. A `recvmsg` that takes a zero-length datagram as `SHUT_RD` lands returns it without its `SO_TIMESTAMP` or `SO_BINTIME` record. - [send, sendto](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - send and sendto without an address on an unconnected datagram socket map Windows `WSAENOTCONN` to FreeBSD's `EDESTADDRREQ` (39). - [bind, connect, sendto](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - a connected stream socket's `sendto` ignores the address once its length passes, as FreeBSD 11.4's `tcp_usr_send` does, which was not measured. Otherwise they check a socket address as a payload process on a PS5 (firmware 13.60) did on datagram and stream sockets: a length below 2 fails with `EINVAL` and above 255 with `ENAMETOOLONG`; then an `AF_INET` socket needs exactly 16 bytes and an `AF_INET6` socket 28, or `EINVAL`, before the family is looked at (an `AF_INET6` address of 28 or 10 bytes on an `AF_INET` socket fails with `EINVAL`); then another family, `AF_UNSPEC` with `INADDR_ANY` included, fails with `EAFNOSUPPORT`; and `connect` on a listening stream socket fails with `EOPNOTSUPP` - [bind, connect](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) on an `AF_INET6` datagram socket with `IPV6_V6ONLY` cleared accept an IPv4-mapped address longer than 28 bytes and use its first 28, as FreeBSD 11.4's `udp6_bind` and `udp6_connect` hand it to `in_pcbbind` and `in_pcbconnect` as a `sockaddr_in` without the length test of `in6_pcbbind`; `sendto` still fails it with `EINVAL`, as `udp6_send` tests the length first. The console was measured only with `::1` and `IPV6_V6ONLY` at its default, so this case is not measured From 1a288102ea92504f3fe91e053c002d6163417387 Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Fri, 6 Nov 2026 03:52:55 +1300 Subject: [PATCH 405/612] docs(libSceJxr): add jxrlib's licence notice to Export.cpp The WMPAlloc, WMPFree and memory stream ports, like PKFree, readIS and writeIS, follow jxrlib's strcodec.c; the notice is lines 1-27 of that file at f7521879862b9085318e814c6157490dd9dbbdb4. --- core/libs/prx/libSceJxr/Export.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/core/libs/prx/libSceJxr/Export.cpp b/core/libs/prx/libSceJxr/Export.cpp index 5a1681b389..6500ef0770 100644 --- a/core/libs/prx/libSceJxr/Export.cpp +++ b/core/libs/prx/libSceJxr/Export.cpp @@ -1,3 +1,31 @@ +//*@@@+++@@@@****************************************************************** +// +// Copyright © Microsoft Corp. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// • Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// • Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +//*@@@---@@@@****************************************************************** + #include "prx/libc/include/Api.hpp" #include "prx/libSceJxr/include/Api.hpp" #include From 302d5a32fcd8508561f119923d332b2fb26b4cf4 Mon Sep 17 00:00:00 2001 From: Evan Klein Date: Thu, 5 Nov 2026 09:55:32 -0500 Subject: [PATCH 406/612] fix(libkernel): compare a wait address's range end as uintptr_t on macOS Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp index fa88568295..b8fbe7bf5d 100644 --- a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp +++ b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp @@ -213,7 +213,7 @@ std::optional TryLoadAddressValue(TValue* address) { const auto rangeEnd = range->address + range->bytes; if (rangeEnd <= cursor) return std::nullopt; pins[pinCount++] = std::move(range); - cursor = std::min(end, rangeEnd); + cursor = std::min(end, rangeEnd); } if (hasUnregistered) { TValue value; From c5fba8dc423e4b1c3a007989d3b1ee4b73164ff6 Mon Sep 17 00:00:00 2001 From: Milos-Opletal <87488948+Milos-Opletal@users.noreply.github.com> Date: Thu, 5 Nov 2026 15:42:16 +0100 Subject: [PATCH 407/612] feat(libSceVrTracker2): implement sceVrTracker2GetRawCameraMeshSize --- core/libs/prx/libSceVrTracker2/Export.cpp | 3 +-- .../libSceVrTracker2/tests/UninitializedNamedAnswers.cpp | 3 +++ core/libs/tests/GuestVrTracker2.cpp | 7 +++++++ docs/dev/debt/libSceVrTracker2.md | 4 ++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceVrTracker2/Export.cpp b/core/libs/prx/libSceVrTracker2/Export.cpp index 7a58f2152c..86264794fc 100644 --- a/core/libs/prx/libSceVrTracker2/Export.cpp +++ b/core/libs/prx/libSceVrTracker2/Export.cpp @@ -653,8 +653,7 @@ std::int32_t APS5_VABI VrTracker2Unknown01(VrTracker2CalibrationResult* result, } std::int32_t APS5_VABI sceVrTracker2GetRawCameraMeshSize(VrTracker2MeshRequirement* requirement, std::uint32_t format) { - NotImplemented_nid_no_patch(__func__); - return 0; + return VR_TRACKER2_ERROR_NOT_INITIALIZED; } APS5_EXPORT("9R6W33Au4B4", VrTracker2Unknown03); diff --git a/core/libs/prx/libSceVrTracker2/tests/UninitializedNamedAnswers.cpp b/core/libs/prx/libSceVrTracker2/tests/UninitializedNamedAnswers.cpp index 552b1c4c02..02c553af23 100644 --- a/core/libs/prx/libSceVrTracker2/tests/UninitializedNamedAnswers.cpp +++ b/core/libs/prx/libSceVrTracker2/tests/UninitializedNamedAnswers.cpp @@ -63,5 +63,8 @@ int main() { Require(sceVrTracker2UnregisterDevice(2) == NotInitialized, "unregister device before initialization"); Require(sceVrTracker2SetCoordinateSystem(1) == NotInitialized, "set coordinate system before initialization"); Require(sceVrTracker2ResetLocalCoordinate(2) == NotInitialized, "reset local coordinate before initialization"); + Require(sceVrTracker2GetRawCameraMeshSize(reinterpret_cast(output.bytes), 0) == NotInitialized, + "get raw camera mesh size before initialization"); + Require(output.Untouched(), "get raw camera mesh size wrote nothing"); return 0; } diff --git a/core/libs/tests/GuestVrTracker2.cpp b/core/libs/tests/GuestVrTracker2.cpp index 1a590ba742..0fa78e98c9 100644 --- a/core/libs/tests/GuestVrTracker2.cpp +++ b/core/libs/tests/GuestVrTracker2.cpp @@ -257,6 +257,13 @@ int main() { parameter[0] = 0x21; Require(sceVrTracker2QueryMemory(reinterpret_cast(parameter.data()), reinterpret_cast(requirement.data())) == InvalidArgument, "query size"); + auto rawMeshReq = Filled(); + Require(sceVrTracker2GetRawCameraMeshSize(nullptr, 0) == NotInitialized, "raw camera mesh size null"); + Require(sceVrTracker2GetRawCameraMeshSize(reinterpret_cast(rawMeshReq.data()), 0) == NotInitialized, "raw camera mesh size return"); + Require(AllEqual(rawMeshReq.data(), rawMeshReq.data() + rawMeshReq.size(), 0xAA), "raw camera mesh size untouched"); + Require(sceVrTracker2GetRawCameraMeshSize(reinterpret_cast(rawMeshReq.data()), 1) == NotInitialized, "raw camera mesh size format"); + Require(AllEqual(rawMeshReq.data(), rawMeshReq.data() + sizeof(VrTracker2MeshRequirement), 0xAA), "raw camera mesh size preserves struct"); + std::vector memory(TrackerMemory + WorkMemoryTail); CheckInitializeValidation(memory); const auto previous = std::filesystem::current_path(); diff --git a/docs/dev/debt/libSceVrTracker2.md b/docs/dev/debt/libSceVrTracker2.md index 330978dc78..239bb695c8 100644 --- a/docs/dev/debt/libSceVrTracker2.md +++ b/docs/dev/debt/libSceVrTracker2.md @@ -7,7 +7,7 @@ ### Unknown function info - [sceVrTracker2Initialize](../../../core/libs/prx/libSceVrTracker2/Export.cpp) - never initializes: after validation it returns the first non-zero libSceHmd2 tracking or LED-detector open/start result and resets to uninitialized, and throws if all four succeed or for modes 1000/1001, whose application category the host lacks. Unverified: the mode 0 category query is assumed to succeed, the `sceKernelGetAppInfo2` attribute checks are skipped (the host has no such record; a zeroed one passes), the daemon IPC client and work-memory heap are not created, rollback only undoes what the call opened, and the meanings of the options at 0x30, 0x38 and 0x39 remain unknown (0x39 is only range-checked). The behaviour is not measured on a console; the -1 a no-headset host gets relies on libSceHmd2's measured pre-initialization answers. PPSA05666 calls it with no headset and, with #7463, #7690 and #7658 applied, stopped on the NotImplemented. -- [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp) - the anonymous throwing exports have their consumed argument slots restored. Calibration output (`jRmuXjsknsI`) uses a 32-bit capacity with 608/1216-byte variants; its fields remain unresolved. Mesh requirements (identified as sceVrTracker2GetRawCameraMeshSize, `NBhWy+Gl0tY`) contain four 32-bit counts/sizes. The mode/buffer/capacity/size export (`9R6W33Au4B4`) uses 64-bit sizes. The 32/48-byte result parameters are recovered; result output and pose parameters still need full layouts; the telemetry input (`oZDm1bynCaA`) spans 7024 bytes. Their bodies remain unimplemented. +- [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp) - the anonymous throwing exports have their consumed argument slots restored. Calibration output (`jRmuXjsknsI`) uses a 32-bit capacity with 608/1216-byte variants; its fields remain unresolved. The mode/buffer/capacity/size export (`9R6W33Au4B4`) uses 64-bit sizes. The 32/48-byte result parameters are recovered; result output and pose parameters still need full layouts; the telemetry input (`oZDm1bynCaA`) spans 7024 bytes. Their bodies remain unimplemented. - [Anonymous tracking queries](../../../core/libs/prx/libSceVrTracker2/Export.cpp) still lack initialized behavior. The paired option setter/getter consume 28/40-byte records with three integer options, a reset byte and one/two float vectors; option meanings remain unresolved. The scalar query uses an 8-byte size/float record. Diagnostic output spans 4096 bytes, extended output 0xD02E8 bytes, and tracking configuration input 1024 bytes; their fields remain unresolved. The relative-pose query uses two coordinate-system selectors and a 64-byte output containing a pose and 32 cleared bytes; the counter query uses a 64-bit output and a 32-bit selector whose meaning remains unresolved. Remaining aliases still need individual signatures. @@ -23,6 +23,6 @@ - [Extended result and boundary fitting](../../../core/libs/prx/libSceVrTracker2/Export.cpp) retain uninitialized responses. The extended result spans 816 bytes; its coordinate-specific parameter prefixes the 48-byte query with a coordinate selector and four-byte padding. Boundary fitting takes the recovered point list, optionally returns two 80-byte bounds, and writes four byte flags plus four floats; flag and dimension semantics remain unresolved. The device-center output is a 16-byte size/three-float record. -- [Mesh and image-pose utilities](../../../core/libs/prx/libSceVrTracker2/Export.cpp) retain uninitialized responses. Mesh generation takes vertex and 16-bit index outputs plus a 32-bit format. Timed-pose queries take an in/out result, timestamp and user tag. Image submission consumes a record with format at byte 16 and a data pointer at byte 80; image-pose retrieval uses an in/out record with a buffer pointer and capacity. Record fields and initialized algorithms remain incomplete. Scalar queries use float outputs; the map-scalar query also consumes map data. +- [Mesh and image-pose utilities](../../../core/libs/prx/libSceVrTracker2/Export.cpp) retain uninitialized responses. Mesh generation takes vertex and 16-bit index outputs plus a 32-bit format; raw camera mesh size queries return 0x8A740001 without writing to the four 32-bit requirement fields. Timed-pose queries take an in/out result, timestamp and user tag. Image submission consumes a record with format at byte 16 and a data pointer at byte 80; image-pose retrieval uses an in/out record with a buffer pointer and capacity. Record fields and initialized algorithms remain incomplete. Scalar queries use float outputs; the map-scalar query also consumes map data. - [Legacy unsupported operations](../../../core/libs/prx/libSceVrTracker2/Export.cpp) have separate input-pointer signatures. Two consume only the size prefix of 12/20-byte records, leaving their other fields unrecoverable from these bodies. The coordinate operation reads a selector at byte 4 but always returns unsupported after initialization; the remaining record layout is unresolved. From 5d423bfb7b104879e8def77cc468e60468d22920 Mon Sep 17 00:00:00 2001 From: LeandroLP23 Date: Thu, 5 Nov 2026 14:57:32 +0100 Subject: [PATCH 408/612] feat(tools): queue a per-title controller timeline in title runs A title's pad entry in titles.json holds a pad_timeline.py timeline; each run points APS5_PAD_INPUT_FILE at the work directory and queues it after_s seconds after start, so titles that wait on their start screen reach gameplay. Celeste Classic (PPSA59322) starts a game and moves. Co-Authored-By: Claude Opus 5.5 --- docs/dev/TRACING.md | 2 +- tools/tests/test_title_run.py | 65 +++++++++++++++++++++++++++++++++++ tools/title_run.py | 38 +++++++++++++++++--- tools/titles.json | 1 + 4 files changed, 100 insertions(+), 6 deletions(-) diff --git a/docs/dev/TRACING.md b/docs/dev/TRACING.md index da065304b5..edf2ac9111 100644 --- a/docs/dev/TRACING.md +++ b/docs/dev/TRACING.md @@ -79,4 +79,4 @@ See [Build](BUILD.md#performance-trace) for the trace format, summary fields and ## Running titles repeatably -`python tools/title_run.py --games [ID ...]` relinks and runs extracted title packages found under `--games` (a directory holding `eboot.bin` and `sce_sys/param.json`) and writes `summary.json` and a fixed-format `summary.md` under `--out` (default `build/title-runs/-