From f1f3ba8ed4de29ad11c57dd0420c46ea1af7c560 Mon Sep 17 00:00:00 2001 From: msbutt1 Date: Fri, 30 Oct 2026 04:18:07 -0600 Subject: [PATCH 01/27] feat(libSceLibreSsl3): implement RSA_up_ref --- core/libs/prx/libSceLibreSsl3/Export.cpp | 5 ---- core/libs/prx/libSceLibreSsl3/include/Api.hpp | 2 +- core/libs/prx/libSceLibreSsl3/include/Key.hpp | 25 +++++++++++++++++++ core/libs/prx/libSceLibreSsl3/src/Key.cpp | 6 +++++ .../libs/tests/GuestLibreSsl3KeyReference.cpp | 22 ++++++++++++++++ docs/dev/debt/libSceLibreSsl3.md | 4 +-- 6 files changed, 56 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl3/Export.cpp b/core/libs/prx/libSceLibreSsl3/Export.cpp index 0f7cb0e894..a299a4c7a7 100644 --- a/core/libs/prx/libSceLibreSsl3/Export.cpp +++ b/core/libs/prx/libSceLibreSsl3/Export.cpp @@ -266,11 +266,6 @@ int APS5_VABI RSA_padding_add_PKCS1_PSS_mgf1_nid_postfix(void) { return 0; } -int APS5_VABI RSA_up_ref_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_add0_chain_cert_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 b34694dba0..d696c7b462 100644 --- a/core/libs/prx/libSceLibreSsl3/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl3/include/Api.hpp @@ -59,7 +59,7 @@ int APS5_VABI PKCS8_PRIV_KEY_INFO_free_nid_postfix(void); int APS5_VABI RSA_check_key_nid_postfix(void); int APS5_VABI RSA_get0_key_nid_postfix(void); int APS5_VABI RSA_padding_add_PKCS1_PSS_mgf1_nid_postfix(void); -int APS5_VABI RSA_up_ref_nid_postfix(void); +int APS5_VABI RSA_up_ref_nid_postfix(LibreSsl3Rsa* rsa); int APS5_VABI SSL_CTX_add0_chain_cert_nid_postfix(void); int APS5_VABI SSL_CTX_clear_chain_certs_nid_postfix(void); int APS5_VABI SSL_CTX_set_max_proto_version_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl3/include/Key.hpp b/core/libs/prx/libSceLibreSsl3/include/Key.hpp index bc0a301686..4dac85fd57 100644 --- a/core/libs/prx/libSceLibreSsl3/include/Key.hpp +++ b/core/libs/prx/libSceLibreSsl3/include/Key.hpp @@ -24,7 +24,32 @@ struct LibreSsl3EcKey { void* extraData; }; +struct LibreSsl3Rsa { + std::int32_t pad; + std::int64_t version; + const void* method; + void* n; + void* e; + void* d; + void* p; + void* q; + void* dmp1; + void* dmq1; + void* iqmp; + void* pss; + void* extraData; + std::int32_t references; + std::int32_t flags; + void* methodModN; + void* methodModP; + void* methodModQ; + void* blinding; + void* mtBlinding; +}; + static_assert(sizeof(LibreSsl3EvpPkey) == 32); static_assert(offsetof(LibreSsl3EvpPkey, references) == 4); static_assert(sizeof(LibreSsl3EcKey) == 64); static_assert(offsetof(LibreSsl3EcKey, references) == 48); +static_assert(sizeof(LibreSsl3Rsa) == 152); +static_assert(offsetof(LibreSsl3Rsa, references) == 104); diff --git a/core/libs/prx/libSceLibreSsl3/src/Key.cpp b/core/libs/prx/libSceLibreSsl3/src/Key.cpp index d5c4b7a856..dfca8410ad 100644 --- a/core/libs/prx/libSceLibreSsl3/src/Key.cpp +++ b/core/libs/prx/libSceLibreSsl3/src/Key.cpp @@ -35,4 +35,10 @@ int APS5_VABI EC_KEY_up_ref_nid_postfix(LibreSsl3EcKey* key) { return Retain_nid_no_patch(key->references); } +int APS5_VABI RSA_up_ref_nid_postfix(LibreSsl3Rsa* rsa) { + if (!rsa) + throw std::invalid_argument("RSA_up_ref requires a key"); + return Retain_nid_no_patch(rsa->references); +} + } diff --git a/core/libs/tests/GuestLibreSsl3KeyReference.cpp b/core/libs/tests/GuestLibreSsl3KeyReference.cpp index ca69aceb4e..1f814a8d68 100644 --- a/core/libs/tests/GuestLibreSsl3KeyReference.cpp +++ b/core/libs/tests/GuestLibreSsl3KeyReference.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include static void Require(bool condition, const char* message) { @@ -33,6 +34,25 @@ static void CheckKey(TRetain retain, std::size_t referenceOffset) { Require(retain(&guarded.key) == (next > 1), "retain return value"); Require(std::memcmp(&guarded, expected.data(), sizeof(guarded)) == 0, "retain changed bytes outside reference count"); } + std::memset(&guarded, 0x3C, sizeof(guarded)); + guarded.key.references = std::numeric_limits::max(); + std::array unchanged; + std::memcpy(unchanged.data(), &guarded, sizeof(guarded)); + bool overflowThrown = false; + try { + retain(&guarded.key); + } catch (const std::overflow_error&) { + overflowThrown = true; + } + Require(overflowThrown, "retain at maximum count did not throw"); + Require(std::memcmp(&guarded, unchanged.data(), sizeof(guarded)) == 0, "overflowing retain changed bytes"); + bool nullThrown = false; + try { + retain(nullptr); + } catch (const std::invalid_argument&) { + nullThrown = true; + } + Require(nullThrown, "retain of null did not throw"); std::memset(&guarded, 0x5A, sizeof(guarded)); guarded.key.references = 1; std::array expected; @@ -64,5 +84,7 @@ int main(int argc, char** argv) { CheckKey(EVP_PKEY_up_ref_nid_postfix, 4); if (argc == 1 || std::strcmp(argv[1], "ec") == 0) CheckKey(EC_KEY_up_ref_nid_postfix, 48); + if (argc == 1 || std::strcmp(argv[1], "rsa") == 0) + CheckKey(RSA_up_ref_nid_postfix, 104); return 0; } diff --git a/docs/dev/debt/libSceLibreSsl3.md b/docs/dev/debt/libSceLibreSsl3.md index 49a9963156..47310c5e30 100644 --- a/docs/dev/debt/libSceLibreSsl3.md +++ b/docs/dev/debt/libSceLibreSsl3.md @@ -2,14 +2,14 @@ ### Unknown function info -- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 81 named callable exports, excluding `CRYPTO_memcmp`, `MD5`, `SHA1`, `d2i_ASN1_INTEGER`, `EVP_PKEY_up_ref` and `EC_KEY_up_ref`, 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) - 80 named callable exports, excluding `CRYPTO_memcmp`, `MD5`, `SHA1`, `d2i_ASN1_INTEGER`, `EVP_PKEY_up_ref`, `EC_KEY_up_ref` and `RSA_up_ref`, 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 - [ASN.1 integer decoding](../../../core/libs/prx/libSceLibreSsl3/src/IntegerDecoder.cpp) throws on rejected encodings/allocation failures instead of matching native NULL/error handling. Redundant-sign/empty-content rejection retains old output that the module frees and clears. The decoder does not populate the legacy queue; remaining LibreSSL3 ERR exports throw. [13.42 native-PC evidence](https://github.com/boykopovar/AnyPS5/pull/10948#issuecomment-6412137728), using a recording allocator, confirms tested replacement/free order, allocation cleanup and negative-length NULL returns. Nonnegative null input faults natively and throws here. Console/application-heap ABI and untested error-precedence/overflow combinations remain unverified. Contracts follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/asn1). Closes when: error/replacement semantics and console/allocator interoperability are verified. - [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 and EC_KEY_up_ref](../../../core/libs/prx/libSceLibreSsl3/src/Key.cpp) - console layouts and retain/release interoperability are unverified. The 32-byte EVP and 64-byte EC layouts, with counters at offsets 4 and 48, 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` and `ec/ec_key.c`. 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. +- [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. - [Parent export forwarding](../../../core/libs/prx/libSceLibreSsl3/ParentExports.cpp) exposes 395 parent-library exports on Windows through IAT tail jumps without changing their ABI. Non-Windows hosts use their existing dependency lookup. Static COFF inspection verifies names, parent imports and tail jumps; Windows loading and runtime behavior remain unverified. Parent functions retain their existing implementation limitations. From 18cab7e579c64e3f6c373d42ab56415ccff1ef6d Mon Sep 17 00:00:00 2001 From: Filip Michalski Date: Fri, 30 Oct 2026 12:47:45 +0100 Subject: [PATCH 02/27] perf(shader): omit repeated zero selection for volatile buffer loads --- .../tests/GuardedMemoryLoads.cpp | 21 +++++++++++++++++-- .../execution/BufferDescriptorRuntime.cpp | 12 ++++++++--- .../SpirvBackend/src/SpirvRuntimeBuffer.cpp | 3 ++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/GuardedMemoryLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/GuardedMemoryLoads.cpp index 9cd66b426c..5a1996ddf4 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuardedMemoryLoads.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuardedMemoryLoads.cpp @@ -32,7 +32,7 @@ struct LoadCase { std::uint32_t components = 1; }; -std::vector emitLoad(const LoadCase& test, bool active, std::uint32_t offset, std::uint32_t records, bool robust, bool exact = false, bool valid = true) { +std::vector emitLoad(const LoadCase& test, bool active, std::uint32_t offset, std::uint32_t records, bool robust, bool exact = false, bool valid = true, bool coherent = true) { IrProgram program; program.SetWaveSize(32); program.Resources().stage = IrShaderStage::Compute; @@ -70,7 +70,7 @@ std::vector emitLoad(const LoadCase& test, bool active, std::uint auto& handle = emit(IrOpcode::GetScratchResource, IrType::AddressResource, {}); emit(test.opcode, test.type, {&handle, &address, &zero, &enabled}); } else if (test.kind == ResourceKind::Buffer) { - program.Resources().memoryInfo.front().coherent = true; + program.Resources().memoryInfo.front().coherent = coherent; program.Info().buffers.emplace_back().read = true; program.Metadata().bindings.descriptors.push_back({DescriptorBindingKind::Buffers, {0}}); program.Metadata().bindings.pushDataStartDword = 0; @@ -386,6 +386,23 @@ int main() { const auto plain = emitLoad(test, true, 8, 64, false, true); require(AccessTrace(plain, 0).Run() == std::vector({2}), "exact request without robustBufferAccess2 changed the plain variant"); }); + run("volatile robust buffer omits repeated zero selection", [] { + const LoadCase test{ResourceKind::Buffer, IrOpcode::LoadBufferU32, IrType::U32}; + const auto selections = [](const std::vector& module) { + unsigned count = 0; + for (std::size_t offset = 5; offset < module.size(); offset += module[offset] >> 16u) { + count += (module[offset] & 0xffffu) == spv::OpSelect; + } + return count; + }; + for (const bool robust : {false, true}) { + for (const bool exact : {false, true}) { + const auto plain = emitLoad(test, true, 8, 64, robust, exact, true, false); + const auto coherent = emitLoad(test, true, 8, 64, robust, exact); + require(selections(plain) == selections(coherent) + (robust && !exact ? 1u : 0u), "only volatile guarded robust loads may omit the repeated zero selection"); + } + } + }); run("private scratch selection", [] { const LoadCase test{ResourceKind::Scratch, IrOpcode::LoadAddressU32, IrType::U32}; for (const bool robust : {false, true}) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/BufferDescriptorRuntime.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/BufferDescriptorRuntime.cpp index 15aa7c77a2..e847bc1c62 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/BufferDescriptorRuntime.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/BufferDescriptorRuntime.cpp @@ -57,13 +57,17 @@ ShaderRecompiler::RecompileRequest Request(std::span userDa }; } -void Run(AgcDriver::VulkanDevice& device, bool registrationModules) { +void Run(AgcDriver::VulkanDevice& device, bool registrationModules, bool coherent = false) { + alignas(256) auto code = Code; + if (coherent) code[1] |= 0x4000u; const std::string mode = registrationModules ? "registration modules" : "specialized modules"; - const std::array memory{{{reinterpret_cast(Code.data()), std::as_bytes(std::span(Code))}}}; + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(std::span(code))}}}; std::array userData{}; auto target = device.ComputeTarget(Lanes); target.registrationModules = registrationModules; - const auto request = Request(userData, target, memory); + auto request = Request(userData, target, memory); + request.shader.code = code; + request.shader.codeAddress = reinterpret_cast(code.data()); const auto handle = ShaderRecompiler::PrepareShader(request); const std::array cases{{ {"aligned", 0, 128, true}, @@ -258,6 +262,8 @@ int main() { for (std::uint32_t index = 0; index < InputBytes; ++index) Input[index] = static_cast(index * 41u + 3u + (index >> 3u)); Run(*device, false); Run(*device, true); + Run(*device, false, true); + Run(*device, true, true); ScalarReads(*device); DisabledDescriptors(*device); std::puts("buffer descriptor runtime tests passed"); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp index 65cef1375d..1f8522303e 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp @@ -282,7 +282,8 @@ std::uint32_t readBuffer(SpirvValueEmitContext& context, const IrValue& instruct const auto highIndex = Select(state, TypeU32(state), both(state, inBounds, unaligned), next, lowIndex); const auto low = Binary(state, spv::OpShiftRightLogical, TypeU32(state), load(lowIndex), shift); const auto high = EmitValueOrZero(state, unaligned, Binary(state, spv::OpShiftLeftLogical, TypeU32(state), load(highIndex), highShift)); - return EmitValueOrZero(state, inBounds, combine(low, high)); + const auto value = combine(low, high); + return (access.memoryAccess & spv::MemoryAccessVolatileMask) != 0u ? value : EmitValueOrZero(state, inBounds, value); } std::uint32_t byteBuffers(SpirvEmitterState& state) { From affc2a3e653f15a39e93b8ceda6ad6eaccb31c32 Mon Sep 17 00:00:00 2001 From: dom Date: Fri, 30 Oct 2026 07:47:47 -0400 Subject: [PATCH 03/27] fix(tools): wrap EIP-relative annotation addresses --- tools/annotate_module.py | 21 ++++++++++++++------ tools/tests/test_annotate_module.py | 30 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/tools/annotate_module.py b/tools/annotate_module.py index 1ed6c4c37b..416a9cde81 100644 --- a/tools/annotate_module.py +++ b/tools/annotate_module.py @@ -35,7 +35,7 @@ RELA_SIZE = 24 R_X86_64_NONE = 0 LINE_ADDRESS = re.compile(r"^\s*([0-9a-f]+):") -STUB = re.compile(r"^\s*([0-9a-f]+):\s+(?:\w+\s+)?jmpq?\s+\*(?:-?0x[0-9a-f]+)?\(%rip\)\s+# 0x([0-9a-f]+)") +STUB = re.compile(r"^\s*([0-9a-f]+):\s+(?:\w+\s+)?jmpq?\s+\*(?:-?0x[0-9a-f]+)?\(%(?:rip|eip)\)\s+# 0x([0-9a-f]+)") BRANCH = re.compile(r"\b(?:call|jmp)q?\s+0x([0-9a-f]+)") REFERENCE = re.compile(r"# 0x([0-9a-f]+)") @@ -243,12 +243,21 @@ def disassemble(code, vaddr): return result.stdout.splitlines() +def referenced_address(line): + reference = REFERENCE.search(line) + if reference is None: + return None + address = int(reference.group(1), 16) + return address & 0xFFFFFFFF if "(%eip)" in line else address + + def find_stubs(listing, slots, namer): stubs = {} for line in listing: match = STUB.match(line) - if match and int(match.group(2), 16) in slots: - stubs[int(match.group(1), 16)] = namer.resolve(slots[int(match.group(2), 16)]) + target = referenced_address(line) + if match and target in slots: + stubs[int(match.group(1), 16)] = namer.resolve(slots[target]) return stubs @@ -257,9 +266,9 @@ def line_note(line, slots, stubs, namer): branch = BRANCH.search(line) if branch and int(branch.group(1), 16) in stubs: note = " ; -> " + stubs[int(branch.group(1), 16)] - reference = REFERENCE.search(line) - if reference and int(reference.group(1), 16) in slots: - note = " ; GOT " + namer.resolve(slots[int(reference.group(1), 16)]) + reference = referenced_address(line) + if reference in slots: + note = " ; GOT " + namer.resolve(slots[reference]) return note diff --git a/tools/tests/test_annotate_module.py b/tools/tests/test_annotate_module.py index 4ae356bdb9..2b14d02912 100644 --- a/tools/tests/test_annotate_module.py +++ b/tools/tests/test_annotate_module.py @@ -261,6 +261,36 @@ class AnnotateModuleTests(unittest.TestCase): self.annotate(build_module(code=code_bytes(b"\xf2"))) self.assertTrue(instruction(self.read(".asm").splitlines(), "call").endswith("; -> " + IMPORT_NAME)) + def test_eip_relative_stubs_and_calls_use_wrapped_got_addresses(self): + for address in (CODE_ADDRESS, 0xFFFFF000, 0x100001000): + for prefix in (b"\x67", b"\xf2\x67", b"\x3e\x67"): + with self.subTest(address=address, prefix=prefix): + length = len(prefix) + 6 + code = prefix + b"\xff\x25" + struct.pack(" " + IMPORT_NAME)) + + def test_eip_references_do_not_name_the_unwrapped_address(self): + address = 0x100001000 + for opcode in (b"\xff\x15", b"\x48\x8b\x05"): + with self.subTest(opcode=opcode): + length = 1 + len(opcode) + 4 + code = b"\x67" + opcode + struct.pack(" Date: Fri, 30 Oct 2026 12:26:42 +0100 Subject: [PATCH 04/27] ci(sanitizers): skip the strict precise_wait bound in the hosted nightly Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/sanitizers.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 914ad341bf..94c7c2631c 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -94,6 +94,7 @@ jobs: env: ASAN_OPTIONS: detect_leaks=0 UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1 + APS5_SKIP_TIMING_TESTS: 1 run: ctest --output-on-failure --timeout 300 --parallel 4 --output-log ctest.log --output-junit ctest.xml || true - name: Summarize the sanitizer findings if: ${{ !cancelled() && steps.configure.outcome == 'success' }} From 0e20f51dc74a8ab2d26179a96bb79d3c92519114 Mon Sep 17 00:00:00 2001 From: StoneDC <19905433+StoneDC@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:38:47 +0800 Subject: [PATCH 05/27] feat(tools): check configure seeds without writing --- CMakeLists.txt | 2 + tools/check_configure_seed.py | 35 ++++++++++++++++ tools/tests/test_check_configure_seed.py | 51 ++++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 tools/check_configure_seed.py create mode 100644 tools/tests/test_check_configure_seed.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c85757df6..235a15a9c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -472,6 +472,8 @@ if(BUILD_TESTING) set_tests_properties(check_compile_${test_case} PROPERTIES TIMEOUT 30) endif() endforeach() + add_test(NAME tools_seed_check COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_check_configure_seed.py) + set_tests_properties(tools_seed_check PROPERTIES TIMEOUT 20) add_test(NAME performance_compare COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_performance_compare.py) set_tests_properties(performance_compare PROPERTIES TIMEOUT 20) diff --git a/tools/check_configure_seed.py b/tools/check_configure_seed.py new file mode 100644 index 0000000000..c2175732d6 --- /dev/null +++ b/tools/check_configure_seed.py @@ -0,0 +1,35 @@ +import argparse +import sys +from pathlib import Path + +from configure_seed import checks, quote + + +def main(argv=None): + for stream in (sys.stdout, sys.stderr): + if hasattr(stream, "reconfigure"): + stream.reconfigure(encoding="utf-8", errors="backslashreplace") + parser = argparse.ArgumentParser(description="Check an unfiltered configure seed against its source CMake cache without writing") + parser.add_argument("cache", type=Path) + parser.add_argument("seed", type=Path) + args = parser.parse_args(argv) + try: + found = checks(args.cache) + expected = "".join(f"set({quote(name)} {quote(value)} CACHE INTERNAL {quote(doc)})\n" + for name, (value, doc) in sorted(found.items())).encode("utf-8") + try: + actual = args.seed.read_bytes() + except FileNotFoundError: + print(f"missing seed: {args.seed}") + return 1 + except (OSError, UnicodeError, ValueError) as error: + parser.error(str(error)) + if actual != expected: + print(f"stale seed: {args.seed}") + return 1 + print(f"{len(found)} check results match") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/tests/test_check_configure_seed.py b/tools/tests/test_check_configure_seed.py new file mode 100644 index 0000000000..84a4a87449 --- /dev/null +++ b/tools/tests/test_check_configure_seed.py @@ -0,0 +1,51 @@ +import json +import os +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +SCRIPT = 'check_configure_seed.py' +TOOLS = Path(__file__).resolve().parents[1] + + +class Tests(unittest.TestCase): + def setUp(self): + self.directory = tempfile.TemporaryDirectory() + self.addCleanup(self.directory.cleanup) + self.root = Path(self.directory.name) + + def run_tool(self, *args): + return subprocess.run([sys.executable, str(TOOLS / SCRIPT), *map(str, args)], capture_output=True, + encoding="utf-8", timeout=10, env={**os.environ, "PYTHONIOENCODING": "ascii"}) + + def test_exact_stale_and_missing_are_read_only(self): + cache, seed = self.root / "缓存.txt", self.root / "seed.cmake" + cache.write_text("//Have include stdint.h\nHAVE_STDINT_H:INTERNAL=1\n//User option\nOPT:BOOL=ON\n", encoding="utf-8-sig") + expected = b'set("HAVE_STDINT_H" "1" CACHE INTERNAL "Have include stdint.h")\n' + seed.write_bytes(expected) + before = seed.stat().st_mtime_ns + result = self.run_tool(cache, seed) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(seed.stat().st_mtime_ns, before) + seed.write_bytes(expected.replace(b'"1"', b'"0"')) + before = seed.read_bytes() + self.assertEqual(self.run_tool(cache, seed).returncode, 1) + self.assertEqual(seed.read_bytes(), before) + missing = self.root / "absent" / "seed.cmake" + self.assertEqual(self.run_tool(cache, missing).returncode, 1) + self.assertFalse(missing.parent.exists()) + + def test_invalid_cache_and_empty_seed(self): + cache, seed = self.root / "cache", self.root / "seed" + cache.write_bytes(b"\xff") + self.assertEqual(self.run_tool(cache, seed).returncode, 2) + self.assertFalse(seed.exists()) + cache.write_text("OPT:BOOL=ON\n", encoding="utf-8") + seed.write_bytes(b"") + self.assertEqual(self.run_tool(cache, seed).returncode, 0) + + +if __name__ == "__main__": + unittest.main() From ab1efd11f8ecb739a43cb3965ee8e96a8efdcf1c Mon Sep 17 00:00:00 2001 From: StoneDC <19905433+StoneDC@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:38:47 +0800 Subject: [PATCH 06/27] feat(tools): inspect SELF header metadata without conversion --- CMakeLists.txt | 2 ++ tools/inspect_self.py | 36 +++++++++++++++++++ tools/tests/test_inspect_self.py | 61 ++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 tools/inspect_self.py create mode 100644 tools/tests/test_inspect_self.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c85757df6..13842887b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -288,6 +288,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_sanitizer_report.py) add_test(NAME select_tests COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_select_tests.py) + add_test(NAME tools_self_inspect COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_inspect_self.py) + set_tests_properties(tools_self_inspect PROPERTIES TIMEOUT 20) add_test(NAME check_main_sync COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_main_sync.py CheckMainSyncTests) set_tests_properties(check_main_sync PROPERTIES TIMEOUT 30) diff --git a/tools/inspect_self.py b/tools/inspect_self.py new file mode 100644 index 0000000000..667040951d --- /dev/null +++ b/tools/inspect_self.py @@ -0,0 +1,36 @@ +import argparse +import json +import struct +import sys +from pathlib import Path + +from unwrap_self import FIRST_REGION, SELF_MAGIC, SELF_MAGIC_KERNEL, Refused, parse_elf, read_records + + +def main(argv=None): + for stream in (sys.stdout, sys.stderr): + if hasattr(stream, "reconfigure"): + stream.reconfigure(encoding="utf-8", errors="backslashreplace") + parser = argparse.ArgumentParser(description="Inspect SELF record and original inner ELF header metadata as JSON; this does not validate conversion or decrypt segments") + parser.add_argument("input", type=Path) + args = parser.parse_args(argv) + try: + data = args.input.read_bytes() + if len(data) < FIRST_REGION: + raise Refused("file is smaller than the SELF header area") + magic = struct.unpack_from(" Date: Fri, 30 Oct 2026 07:59:37 -0400 Subject: [PATCH 07/27] fix(tools): exclude segment-based import annotations --- tools/annotate_module.py | 4 +++- tools/tests/test_annotate_module.py | 13 +++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/tools/annotate_module.py b/tools/annotate_module.py index 416a9cde81..a473c38cc6 100644 --- a/tools/annotate_module.py +++ b/tools/annotate_module.py @@ -35,7 +35,7 @@ RELA_SIZE = 24 R_X86_64_NONE = 0 LINE_ADDRESS = re.compile(r"^\s*([0-9a-f]+):") -STUB = re.compile(r"^\s*([0-9a-f]+):\s+(?:\w+\s+)?jmpq?\s+\*(?:-?0x[0-9a-f]+)?\(%(?:rip|eip)\)\s+# 0x([0-9a-f]+)") +STUB = re.compile(r"^\s*([0-9a-f]+):\s+(?:\w+\s+)*jmpq?\s+\*(?:-?0x[0-9a-f]+)?\(%(?:rip|eip)\)\s+# 0x([0-9a-f]+)") BRANCH = re.compile(r"\b(?:call|jmp)q?\s+0x([0-9a-f]+)") REFERENCE = re.compile(r"# 0x([0-9a-f]+)") @@ -244,6 +244,8 @@ def disassemble(code, vaddr): def referenced_address(line): + if "%fs:" in line or "%gs:" in line: + return None reference = REFERENCE.search(line) if reference is None: return None diff --git a/tools/tests/test_annotate_module.py b/tools/tests/test_annotate_module.py index 2b14d02912..a1cc8092ba 100644 --- a/tools/tests/test_annotate_module.py +++ b/tools/tests/test_annotate_module.py @@ -93,7 +93,7 @@ def build_module(tags=DEFAULT_TAGS, values=None, symbols=DEFAULT_SYMBOLS, reloca def instruction(listing, mnemonic): - pattern = re.compile(r"^\s*[0-9a-f]+:\s+(?:\w+\s+)?%s\b" % mnemonic) + pattern = re.compile(r"^\s*[0-9a-f]+:\s+(?:\w+\s+)*%s\b" % mnemonic) return next(line for line in listing if pattern.search(line)) @@ -263,7 +263,7 @@ class AnnotateModuleTests(unittest.TestCase): def test_eip_relative_stubs_and_calls_use_wrapped_got_addresses(self): for address in (CODE_ADDRESS, 0xFFFFF000, 0x100001000): - for prefix in (b"\x67", b"\xf2\x67", b"\x3e\x67"): + for prefix in (b"\x67", b"\xf2\x67", b"\x3e\x67", b"\xf2\x3e\x67"): with self.subTest(address=address, prefix=prefix): length = len(prefix) + 6 code = prefix + b"\xff\x25" + struct.pack(" ", self.read(".asm")) + def test_none_relocation_does_not_annotate_a_stub_or_call(self): module = with_relocation(build_module(), 0x2280, 0, SLOT, 1, 0) summary = self.annotate(module) From 23f92f6519ad52a5bf0f5f8e34f9e8752a1fa0ce Mon Sep 17 00:00:00 2001 From: dom Date: Fri, 30 Oct 2026 08:00:17 -0400 Subject: [PATCH 08/27] fix(tools): skip empty executable segments in annotations --- tools/annotate_module.py | 4 ++-- tools/tests/test_annotate_module.py | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/tools/annotate_module.py b/tools/annotate_module.py index 1ed6c4c37b..7bc8fc6099 100644 --- a/tools/annotate_module.py +++ b/tools/annotate_module.py @@ -195,12 +195,12 @@ class Module: def code_segments(self): segments = [] for vaddr, offset, size, flags in sorted(self.loads): - if flags & PF_X: + if flags & PF_X and size: if offset + size > len(self.data): raise ModuleError("the executable PT_LOAD segment at %#x extends past the end of the file" % vaddr) segments.append((vaddr, self.data[offset:offset + size])) if not segments: - raise ModuleError("no executable PT_LOAD segment") + raise ModuleError("no executable PT_LOAD segment with file-backed data") return segments diff --git a/tools/tests/test_annotate_module.py b/tools/tests/test_annotate_module.py index 4ae356bdb9..12c7c81f3b 100644 --- a/tools/tests/test_annotate_module.py +++ b/tools/tests/test_annotate_module.py @@ -341,6 +341,31 @@ class AnnotateModuleTests(unittest.TestCase): self.assertLess(first, second) self.assertTrue(any(re.match(r"^\s*3000:\s+ret\b", line) for line in listing[second:])) + def test_empty_executable_segments_do_not_abort_other_disassembly(self): + for memory_size in (0, 0x1000): + for address in (0, 0x3000): + with self.subTest(memory_size=memory_size, address=address): + module = bytearray(build_module(extra_code=b"")) + struct.pack_into(" " + IMPORT_NAME, listing) + self.assertIn("; ===== export " + EXPORT_NAME, listing) + + def test_only_empty_executable_segments_fail_before_writing_reports(self): + self.assert_fails(build_module(code=b"", extra_code=b""), "no executable PT_LOAD segment with file-backed data") + for suffix in (".asm", ".imports.txt", ".exports.txt"): + self.assertFalse((self.root / ("out" + suffix)).exists()) + + def test_nonempty_executable_segment_past_end_of_file_is_still_rejected(self): + module = bytearray(build_module(extra_code=b"\xc3")) + struct.pack_into(" Date: Fri, 30 Oct 2026 08:01:03 -0400 Subject: [PATCH 09/27] test(tools): keep empty-segment ELF fixtures ordered --- tools/tests/test_annotate_module.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/tests/test_annotate_module.py b/tools/tests/test_annotate_module.py index 12c7c81f3b..cada4ef5b3 100644 --- a/tools/tests/test_annotate_module.py +++ b/tools/tests/test_annotate_module.py @@ -348,6 +348,8 @@ class AnnotateModuleTests(unittest.TestCase): module = bytearray(build_module(extra_code=b"")) struct.pack_into(" Date: Fri, 30 Oct 2026 15:47:10 +0330 Subject: [PATCH 10/27] fix(tools): decode relinker audit output as UTF-8 --- tools/audit_dump.py | 2 +- tools/tests/test_audit_dump.py | 42 ++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/tools/audit_dump.py b/tools/audit_dump.py index 0d2414e620..edd5ed83fe 100644 --- a/tools/audit_dump.py +++ b/tools/audit_dump.py @@ -72,7 +72,7 @@ def convert(relinker, source, output, extra, windows): for relative in extra: command += ["--module-dir", relative] command += [str(source / "eboot.elf"), str(output)] - return subprocess.run(command, capture_output=True, text=True) + return subprocess.run(command, capture_output=True, text=True, encoding="utf-8", errors="replace") def comparison_path(path, links=frozenset()): diff --git a/tools/tests/test_audit_dump.py b/tools/tests/test_audit_dump.py index df52080316..21f462663f 100644 --- a/tools/tests/test_audit_dump.py +++ b/tools/tests/test_audit_dump.py @@ -1,5 +1,6 @@ import contextlib import io +import locale import os import subprocess import sys @@ -83,11 +84,48 @@ class AuditDumpTests(unittest.TestCase): code = audit_dump.main([str(argument) for argument in arguments]) return code, errors.getvalue() + def test_conversion_decodes_both_streams_as_utf8_under_a_legacy_locale(self): + message = "module-\u754c-\U0001f3ae.prx" + payload = message.encode("utf-8") + child = f"import os; os.write(1, {payload!r}); os.write(2, {payload!r})" + run = subprocess.run + + def relink(command, **options): + return run([sys.executable, "-c", child], timeout=5, **options) + + with mock.patch.object(locale, "getpreferredencoding", return_value="cp1252"), \ + mock.patch.object(locale, "getencoding", return_value="cp1252", create=True), \ + mock.patch.object(subprocess, "run", side_effect=relink): + result = audit_dump.convert(self.relinker, self.root / "source", self.root / "app.elf", [], False) + self.assertEqual(result.returncode, 0) + self.assertEqual(result.stdout, message) + self.assertEqual(result.stderr, message) + + def test_non_utf8_diagnostic_bytes_preserve_the_conversion_failure(self): + payload = b"FAIL: native diagnostic \xff\n" + run = subprocess.run + for stream in (1, 2): + with self.subTest(stream=stream): + child = f"import os, sys; os.write({stream}, {payload!r}); sys.exit(3)" + + def relink(command, **options): + return run([sys.executable, "-c", child], timeout=5, **options) + + with mock.patch.object(locale, "getpreferredencoding", return_value="utf-8"), \ + mock.patch.object(locale, "getencoding", return_value="utf-8", create=True), \ + mock.patch.object(subprocess, "run", side_effect=relink), \ + mock.patch.object(audit_dump.import_audit, "main") as audit: + code, errors = self.run_main(self.dump, "--relinker", self.relinker, "--libs", "libs") + self.assertEqual(code, 2) + self.assertIn("relinker exited with 3", errors) + self.assertIn("FAIL: native diagnostic \ufffd", errors) + audit.assert_not_called() + def test_the_audit_gets_every_registry_library_and_module_directory(self): self.module("sce_module/libc.prx") work = self.root / "work" - def relink(command, capture_output, text): + def relink(command, **options): output = Path(command[-1]).parent (output / "app.registry.json").write_text("[]") (output / "app.libc.prx.guest.prx.registry.json").write_text("[]") @@ -110,7 +148,7 @@ class AuditDumpTests(unittest.TestCase): (work / "source" / "sce_module").mkdir(parents=True) (work / "source" / "sce_module" / "gone.prx").write_bytes(ELF) - def relink(command, capture_output, text): + def relink(command, **options): (Path(command[-1]).parent / "app.registry.json").write_text("[]") return subprocess.CompletedProcess(command, 0, "", "") From 6f402d7a9e32823c4ee6986add04e48690748d70 Mon Sep 17 00:00:00 2001 From: dr-hoseyn <95236112+dr-hoseyn@users.noreply.github.com> Date: Fri, 30 Oct 2026 15:51:35 +0330 Subject: [PATCH 11/27] fix(tools): report audit output directory creation errors --- tools/audit_dump.py | 6 +++++- tools/tests/test_audit_dump.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/tools/audit_dump.py b/tools/audit_dump.py index 0d2414e620..caef673254 100644 --- a/tools/audit_dump.py +++ b/tools/audit_dump.py @@ -155,7 +155,11 @@ def main(argv=None): if generated.exists() or generated.is_symlink(): print(f"FAIL: cannot empty {generated}: it still exists", file=sys.stderr) return 2 - output.mkdir(parents=True, exist_ok=True) + try: + output.mkdir(parents=True, exist_ok=True) + except OSError as error: + print(f"FAIL: cannot create {output}: {error}", file=sys.stderr) + return 2 try: module_directories, extra = prepare(dump, source) except (unwrap_self.Refused, OSError) as error: diff --git a/tools/tests/test_audit_dump.py b/tools/tests/test_audit_dump.py index df52080316..df196cc682 100644 --- a/tools/tests/test_audit_dump.py +++ b/tools/tests/test_audit_dump.py @@ -143,6 +143,27 @@ class AuditDumpTests(unittest.TestCase): audit.assert_not_called() self.assertTrue(stale.exists()) + def test_output_directory_creation_failure_stops_before_conversion(self): + work = self.root / "work" + original_mkdir = Path.mkdir + + def mkdir(path, *args, **kwargs): + if path == work / "output": + raise PermissionError("permission denied") + return original_mkdir(path, *args, **kwargs) + + with mock.patch.object(Path, "mkdir", mkdir), mock.patch.object(audit_dump, "prepare") as prepare, \ + mock.patch.object(audit_dump, "convert") as convert, mock.patch.object(audit_dump.import_audit, "main") as audit: + code, errors = self.run_main(self.dump, "--relinker", self.relinker, "--libs", "libs", "--work", work) + self.assertEqual(code, 2) + self.assertIn("cannot create", errors) + self.assertIn(str(work / "output"), errors) + self.assertIn("permission denied", errors) + prepare.assert_not_called() + convert.assert_not_called() + audit.assert_not_called() + self.assertEqual((self.dump / "eboot.bin").read_bytes(), SELF) + @unittest.skipIf(os.name == "nt" or (hasattr(os, "geteuid") and os.geteuid() == 0), "needs POSIX permissions and a non-root user") def test_a_non_writable_stale_directory_stops_before_conversion(self): work = self.root / "work" From e08ae9fe51da34893256f8398e344987665c3602 Mon Sep 17 00:00:00 2001 From: Filip Michalski Date: Fri, 30 Oct 2026 13:56:10 +0100 Subject: [PATCH 12/27] fix(tools): bound sampler export ordinals --- tools/symbolize_samples.py | 4 +++- tools/tests/test_symbolize_samples.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tools/symbolize_samples.py b/tools/symbolize_samples.py index 23a9784fd4..ad3f154953 100644 --- a/tools/symbolize_samples.py +++ b/tools/symbolize_samples.py @@ -111,7 +111,7 @@ class PeModule: def export_symbols(self, data, rva, size): directory = self.offset(rva) - _, names, function_rva, name_rva, ordinal_rva = struct.unpack_from("= functions: + raise ValueError("PE export ordinal is outside the export address table") address = struct.unpack_from(" Date: Fri, 30 Oct 2026 13:58:36 +0100 Subject: [PATCH 13/27] refactor(relinker): share fixed-base argument parsing --- core/relinker/cli/src/CliArgs.cpp | 9 +-------- core/relinker/cli/tests/CliArgsTests.cpp | 2 ++ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/core/relinker/cli/src/CliArgs.cpp b/core/relinker/cli/src/CliArgs.cpp index 697a5de1aa..8a6f7b2aef 100644 --- a/core/relinker/cli/src/CliArgs.cpp +++ b/core/relinker/cli/src/CliArgs.cpp @@ -110,14 +110,7 @@ Args ParseArgs(int argc, char* argv[]) { if (args.windowsFixedBase) throw std::runtime_error("--windows-fixed-base cannot be repeated"); if (i + 1 >= argc || std::string(argv[i + 1]).empty() || std::string(argv[i + 1]).starts_with("--")) throw std::runtime_error("--windows-fixed-base requires an address"); - std::string_view value(argv[++i]); - const bool hexadecimal = value.starts_with("0x") || value.starts_with("0X"); - if (hexadecimal) value.remove_prefix(2); - std::uint64_t address = 0; - const auto parsed = std::from_chars(value.data(), value.data() + value.size(), address, hexadecimal ? 16 : 10); - if (parsed.ec != std::errc{} || parsed.ptr != value.data() + value.size() || address < 0x20000 || address > 0x7fffffff0000 || (address & 0xffff) != 0) - throw std::runtime_error("--windows-fixed-base requires a 64 KiB-aligned address from 0x20000 through 0x7fffffff0000"); - args.windowsFixedBase = address; + args.windowsFixedBase = ParseFixedBase(argv[++i], 0x20000, "--windows-fixed-base requires a 64 KiB-aligned address from 0x20000 through 0x7fffffff0000"); } else if (arg == "--linux-fixed-base") { if (args.linuxFixedBase) throw std::runtime_error("--linux-fixed-base cannot be repeated"); if (i + 1 >= argc || std::string_view(argv[i + 1]).empty() || std::string_view(argv[i + 1]).starts_with("--")) diff --git a/core/relinker/cli/tests/CliArgsTests.cpp b/core/relinker/cli/tests/CliArgsTests.cpp index a56f25be56..b7e0a7d218 100644 --- a/core/relinker/cli/tests/CliArgsTests.cpp +++ b/core/relinker/cli/tests/CliArgsTests.cpp @@ -223,6 +223,8 @@ void windowsFixedBase() { const auto args = parse({"--windows-fixed-base", value, "--windows", "input.elf", "output.exe"}); require(args.windowsFixedBase == 0x400000, "fixed base was parsed incorrectly"); } + require(parse({"--windows", "--windows-fixed-base", "0x20000", "input.elf", "output.exe"}).windowsFixedBase == 0x20000, "lowest fixed base was rejected"); + require(parse({"--windows", "--windows-fixed-base", "0x7fffffff0000", "input.elf", "output.exe"}).windowsFixedBase == 0x7fffffff0000, "highest fixed base was rejected"); require(parse({"--windows", "--windows-fixed-base", "0x400000000", "input.elf", "output.exe"}).windowsFixedBase == 0x400000000, "fixed base was truncated to 32 bits"); for (const auto& value : {"0", "0x10000", "0x400001", "0x", "-4194304", "+4194304", " 4194304", "0x400000junk", "18446744073709551616", "0x800000000000"}) expectThrows({"--windows", "--windows-fixed-base", value, "input.elf", "output.exe"}, "--windows-fixed-base requires a 64 KiB-aligned address from 0x20000 through 0x7fffffff0000"); From 04d8b1a0a6b933989d476eb4f49c1fd8618a61e1 Mon Sep 17 00:00:00 2001 From: Filip Michalski Date: Fri, 30 Oct 2026 14:00:39 +0100 Subject: [PATCH 14/27] fix(tools): validate annotation symbol entry size --- tools/annotate_module.py | 3 +++ tools/tests/test_annotate_module.py | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/tools/annotate_module.py b/tools/annotate_module.py index 1ed6c4c37b..57826e6600 100644 --- a/tools/annotate_module.py +++ b/tools/annotate_module.py @@ -25,6 +25,7 @@ DT_JMPREL = 23 DT_PLTRELSZ = 2 DT_PLTREL = 20 DT_STRSZ = 10 +DT_SYMENT = 11 DT_RELAENT = 9 DT_REL = 17 DT_RELA_KIND = 7 @@ -57,6 +58,8 @@ class Module: if self.strtab + self.strsz > len(self.data): raise ModuleError("the string table of DT_STRSZ %#x extends past the end of the file" % self.strsz) self.address(self.tags[DT_STRTAB], self.strsz) + if self.require_tag(DT_SYMENT, "DT_SYMENT") != SYMBOL_SIZE: + raise ModuleError("DT_SYMENT is not %d" % SYMBOL_SIZE) self.symbol_count = self.count_symbols() self.address(self.tags[DT_SYMTAB], self.symbol_count * SYMBOL_SIZE) self.check_relocation_tags() diff --git a/tools/tests/test_annotate_module.py b/tools/tests/test_annotate_module.py index 4ae356bdb9..b634b7b70f 100644 --- a/tools/tests/test_annotate_module.py +++ b/tools/tests/test_annotate_module.py @@ -104,6 +104,14 @@ def with_relocation(module, table_offset, index, slot, symbol_index, kind): class ImportRelocationTests(unittest.TestCase): + def test_symbol_entry_size_must_match_elf64(self): + for size in (None, 0, 16, 32): + with self.subTest(size=size): + tags = tuple(tag for tag in DEFAULT_TAGS if tag != 11 or size is not None) + data = build_module(tags=tags, values={} if size is None else {11: size}) + with self.assertRaisesRegex(annotate_module.ModuleError, "DT_SYMENT"): + annotate_module.Module(data) + def test_tables_cannot_read_file_padding_past_the_load_segment(self): for tag in (5, 6, 23): with self.subTest(tag=tag): From b959fb319f4f3e0a5dbcc95f245cbe2572241282 Mon Sep 17 00:00:00 2001 From: Malek Date: Fri, 30 Oct 2026 14:04:39 +0100 Subject: [PATCH 15/27] fix(tools): reject duplicate performance summary fields --- tools/performance_compare.py | 11 ++++++++++- tools/tests/test_performance_compare.py | 17 +++++++++++++++++ tools/tests/test_performance_csv.py | 13 +++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/tools/performance_compare.py b/tools/performance_compare.py index 99a92b64f5..3cb10483a7 100644 --- a/tools/performance_compare.py +++ b/tools/performance_compare.py @@ -75,6 +75,15 @@ def validate(summary): return summary +def unique_json_object(pairs): + result = {} + for name, value in pairs: + if name in result: + raise ValueError(f"duplicate JSON field: {name!r}") + result[name] = value + return result + + def load_summary(path, trace=None): records = [] with Path(path).open(encoding="utf-8-sig") as source: @@ -82,7 +91,7 @@ def load_summary(path, trace=None): if not line.strip(): continue try: - record = json.loads(line) + record = json.loads(line, object_pairs_hook=unique_json_object) if not isinstance(record, dict): raise ValueError("summary must be an object") except (ValueError, RecursionError) as error: diff --git a/tools/tests/test_performance_compare.py b/tools/tests/test_performance_compare.py index c5e6fcae05..b0b2a5c61d 100644 --- a/tools/tests/test_performance_compare.py +++ b/tools/tests/test_performance_compare.py @@ -245,6 +245,23 @@ class PerformanceCompareTests(unittest.TestCase): self.assertEqual(result.stdout, "") self.assertNotIn("Traceback", result.stderr) + def test_duplicate_json_fields_are_rejected_at_every_object_level(self): + record = json.dumps(self.baseline) + duplicates = ( + (record.replace('"commit": "abc123"', '"commit": "wrong", "commit": "abc123"', 1), "commit"), + (record.replace('"spec": "frames 600:4"', '"spec": "seconds 0:1", "spec": "frames 600:4"', 1), "spec"), + (record.replace('"Driver.Packet": 12.0', '"Driver.Packet": 1.0, "Driver.Packet": 12.0', 1), "Driver.Packet"), + ) + for content, field in duplicates: + with self.subTest(field=field): + self.before.write_text(content + "\n", encoding="utf-8") + original = self.before.read_bytes() + with self.assertRaises(ValueError) as raised: + performance_compare.load_summary(self.before) + self.assertIn(f"before.jsonl:1: duplicate JSON field: {field!r}", str(raised.exception)) + self.assertIsInstance(raised.exception.__cause__, ValueError) + self.assertEqual(self.before.read_bytes(), original) + def test_percentage_overflow_is_an_error(self): self.baseline["gpu_busy_ms_per_present"] = 1e-308 self.candidate["gpu_busy_ms_per_present"] = 1e308 diff --git a/tools/tests/test_performance_csv.py b/tools/tests/test_performance_csv.py index b15a8c25af..b32f38f693 100644 --- a/tools/tests/test_performance_csv.py +++ b/tools/tests/test_performance_csv.py @@ -93,6 +93,19 @@ class PerformanceCsvTests(unittest.TestCase): self.assertEqual(result.stdout, b"") self.assertNotIn(b"Traceback", result.stderr) + def test_duplicate_json_field_does_not_emit_an_ambiguous_csv(self): + baseline = json.dumps(self.before).replace( + '"commit": "abc123"', '"commit": "wrong", "commit": "abc123"', 1) + self.baseline.write_text(baseline + "\n", encoding="utf-8") + self.candidate.write_text(json.dumps(self.after) + "\n", encoding="utf-8") + result = subprocess.run( + [sys.executable, str(Path(performance_csv.__file__)), str(self.baseline), str(self.candidate)], + capture_output=True, timeout=5) + self.assertEqual(result.returncode, 2) + self.assertEqual(result.stdout, b"") + self.assertIn(b"duplicate JSON field: 'commit'", result.stderr) + self.assertNotIn(b"Traceback", result.stderr) + def test_trace_selection_uses_the_existing_jsonl_selection_contract(self): self.baseline.write_text(json.dumps(self.before) + "\n" + json.dumps(self.after) + "\n", encoding="utf-8") self.candidate.write_bytes(self.baseline.read_bytes()) From b6f5abaf9f54034c54b5d1c6d02286e4672eeea6 Mon Sep 17 00:00:00 2001 From: Nciibi Date: Fri, 30 Oct 2026 00:19:15 +0100 Subject: [PATCH 16/27] fix(relinker): split 32-bit absolute wide stores at the displacement boundary --- core/relinker/codegen/src/x86/WideStoreOperands.cpp | 9 ++++++--- core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp | 8 ++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/core/relinker/codegen/src/x86/WideStoreOperands.cpp b/core/relinker/codegen/src/x86/WideStoreOperands.cpp index 478c4108ed..c48feebe3e 100644 --- a/core/relinker/codegen/src/x86/WideStoreOperands.cpp +++ b/core/relinker/codegen/src/x86/WideStoreOperands.cpp @@ -37,8 +37,11 @@ void _appendU32(std::vector& bytes, const std::uint32_t value) { std::optional DecodeVexWideStore(const std::uint8_t* data, const std::size_t length) { using namespace X64OpcodeConstants; std::size_t pos = 0; - while (pos < length && _legacyPrefixAllowed(data[pos])) + bool addressSize32 = false; + while (pos < length && _legacyPrefixAllowed(data[pos])) { + addressSize32 = addressSize32 || data[pos] == PrefixAddressSize; ++pos; + } const auto vex = pos; if (vex + 2 > length) return std::nullopt; @@ -110,7 +113,7 @@ std::optional DecodeVexWideStore(const std::uint8_t* data, co highDisplacementSize = highMod == ModRmModDisp8 ? Disp8Size : Disp32Size; } } - if (!ripRelative && highDisplacement > std::numeric_limits::max()) + if (!ripRelative && !addressSize32 && highDisplacement > std::numeric_limits::max()) throw CodegenException("256-bit store displacement leaves no room for its upper half"); high.push_back(static_cast((highMod << ModRmModShift) | reg | rm)); if (sib) @@ -121,7 +124,7 @@ std::optional DecodeVexWideStore(const std::uint8_t* data, co else if (ripRelative) _appendU32(high, 0); else if (highDisplacementSize == Disp32Size) - _appendU32(high, static_cast(static_cast(highDisplacement))); + _appendU32(high, static_cast(highDisplacement)); high.push_back(kUpperHalf); if (ripRelative) { operands.High.RipDisplacementOffset = highDisplacementOffset; diff --git a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp index d5823779fc..290c9842ce 100644 --- a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp +++ b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp @@ -839,6 +839,14 @@ void wideStoreSplit() { require(farRipSplit && farRipSplit->High.RipTarget == std::int64_t{8} + 0x7FFFFFF8 + 0x10, "A RIP-relative 256-bit store near the end of its displacement range was not split"); const Bytes farBase = {0xC5, 0xFC, 0x11, 0x80, 0xF8, 0xFF, 0xFF, 0x7F}; requireFailure([&] { (void)Codegen::DecodeVexWideStore(farBase.data(), farBase.size()); }, "A 256-bit store whose upper half has no 32-bit displacement was kept"); + const Bytes absolute32 = {0x67, 0xC5, 0xFC, 0x11, 0x04, 0x25, 0xF8, 0xFF, 0xFF, 0x7F}; + const auto absolute32Split = Codegen::DecodeVexWideStore(absolute32.data(), absolute32.size()); + require(absolute32Split && absolute32Split->Low.Bytes == Bytes{0x67, 0xC5, 0xF8, 0x11, 0x04, 0x25, 0xF8, 0xFF, 0xFF, 0x7F} && absolute32Split->High.Bytes == Bytes{0x67, 0xC4, 0xE3, 0x7D, 0x19, 0x04, 0x25, 0x08, 0x00, 0x00, 0x80, 0x01}, "A 32-bit absolute 256-bit store was not split across the signed displacement boundary"); + const Bytes disp32 = {0x67, 0xC5, 0xFC, 0x11, 0x80, 0xF8, 0xFF, 0xFF, 0x7F}; + const auto disp32Split = Codegen::DecodeVexWideStore(disp32.data(), disp32.size()); + require(disp32Split && disp32Split->Low.Bytes == Bytes{0x67, 0xC5, 0xF8, 0x11, 0x80, 0xF8, 0xFF, 0xFF, 0x7F} && disp32Split->High.Bytes == Bytes{0x67, 0xC4, 0xE3, 0x7D, 0x19, 0x80, 0x08, 0x00, 0x00, 0x80, 0x01}, "A 32-bit base-relative 256-bit store was not split across the signed displacement boundary"); + const Bytes absolute64 = {0xC5, 0xFC, 0x11, 0x04, 0x25, 0xF8, 0xFF, 0xFF, 0x7F}; + requireFailure([&] { (void)Codegen::DecodeVexWideStore(absolute64.data(), absolute64.size()); }, "A 64-bit absolute 256-bit store whose upper half has no signed displacement was kept"); requireFailure([] { Codegen::StubBodyBuilder body; body.RipRelative(Bytes{0x90}, 0, 0); }, "A RIP-relative displacement outside its instruction was relocated"); const auto rosetta = Codegen::MakeAmd64OnlyInstructionMatcher(Codegen::Amd64OnlyTarget::Rosetta); const Bytes store = {0xC4, 0xC1, 0x7C, 0x11, 0x47, 0x68}; From 5fc33449559fb47e5c2ecce600447c4cc4121a09 Mon Sep 17 00:00:00 2001 From: Nciibi Date: Fri, 30 Oct 2026 12:49:14 +0100 Subject: [PATCH 17/27] fix(relinker): restrict 32-bit wide-store splits to absolute operands --- .../include/codegen/x86/X64OpcodeConstants.hpp | 2 ++ core/relinker/codegen/src/x86/WideStoreOperands.cpp | 13 +++++++++++-- .../codegen/tests/Amd64OnlyConverterTests.cpp | 7 +++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp b/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp index 951a99b18e..08363b0d1e 100644 --- a/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp +++ b/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp @@ -215,6 +215,8 @@ inline constexpr std::uint8_t ModRmRmRipRelative = 0x05; inline constexpr std::uint8_t Grp3RegTestMax = 0x01; inline constexpr std::uint8_t SibBaseMask = 0x07; inline constexpr std::uint8_t SibBaseDisp32 = 0x05; +inline constexpr std::uint8_t SibIndexMask = 0x38; +inline constexpr std::uint8_t SibIndexNone = 0x20; inline constexpr std::size_t Disp8Size = 1; inline constexpr std::size_t Disp32Size = 4; diff --git a/core/relinker/codegen/src/x86/WideStoreOperands.cpp b/core/relinker/codegen/src/x86/WideStoreOperands.cpp index c48feebe3e..df22eb8456 100644 --- a/core/relinker/codegen/src/x86/WideStoreOperands.cpp +++ b/core/relinker/codegen/src/x86/WideStoreOperands.cpp @@ -79,6 +79,7 @@ std::optional DecodeVexWideStore(const std::uint8_t* data, co } const bool ripRelative = mod == ModRmModIndirect && rm == ModRmRmRipRelative; const bool absolute = mod == ModRmModIndirect && sib.has_value() && (*sib & SibBaseMask) == SibBaseDisp32; + const bool absoluteNoIndex = absolute && (*sib & SibIndexMask) == SibIndexNone; const std::size_t displacementSize = mod == ModRmModDisp8 ? Disp8Size : (mod == ModRmModDisp32 || ripRelative || absolute) ? Disp32Size : 0; if (cursor + displacementSize != length) return std::nullopt; @@ -113,8 +114,16 @@ std::optional DecodeVexWideStore(const std::uint8_t* data, co highDisplacementSize = highMod == ModRmModDisp8 ? Disp8Size : Disp32Size; } } - if (!ripRelative && !addressSize32 && highDisplacement > std::numeric_limits::max()) - throw CodegenException("256-bit store displacement leaves no room for its upper half"); + const bool address32Absolute = addressSize32 && absoluteNoIndex; + const auto unsignedHighDisplacement = static_cast(static_cast(displacement)) + kHalfBytes; + if (!ripRelative) { + if (address32Absolute) { + if (unsignedHighDisplacement > std::numeric_limits::max()) + throw CodegenException("256-bit store displacement leaves no room for its upper half"); + } else if (highDisplacement > std::numeric_limits::max()) { + throw CodegenException("256-bit store displacement leaves no room for its upper half"); + } + } high.push_back(static_cast((highMod << ModRmModShift) | reg | rm)); if (sib) high.push_back(*sib); diff --git a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp index 290c9842ce..27cf7b5fff 100644 --- a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp +++ b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp @@ -843,8 +843,11 @@ void wideStoreSplit() { const auto absolute32Split = Codegen::DecodeVexWideStore(absolute32.data(), absolute32.size()); require(absolute32Split && absolute32Split->Low.Bytes == Bytes{0x67, 0xC5, 0xF8, 0x11, 0x04, 0x25, 0xF8, 0xFF, 0xFF, 0x7F} && absolute32Split->High.Bytes == Bytes{0x67, 0xC4, 0xE3, 0x7D, 0x19, 0x04, 0x25, 0x08, 0x00, 0x00, 0x80, 0x01}, "A 32-bit absolute 256-bit store was not split across the signed displacement boundary"); const Bytes disp32 = {0x67, 0xC5, 0xFC, 0x11, 0x80, 0xF8, 0xFF, 0xFF, 0x7F}; - const auto disp32Split = Codegen::DecodeVexWideStore(disp32.data(), disp32.size()); - require(disp32Split && disp32Split->Low.Bytes == Bytes{0x67, 0xC5, 0xF8, 0x11, 0x80, 0xF8, 0xFF, 0xFF, 0x7F} && disp32Split->High.Bytes == Bytes{0x67, 0xC4, 0xE3, 0x7D, 0x19, 0x80, 0x08, 0x00, 0x00, 0x80, 0x01}, "A 32-bit base-relative 256-bit store was not split across the signed displacement boundary"); + requireFailure([&] { (void)Codegen::DecodeVexWideStore(disp32.data(), disp32.size()); }, "A 32-bit base-relative 256-bit store whose upper half can wrap was split"); + const Bytes indexed32 = {0x67, 0xC5, 0xFC, 0x11, 0x04, 0x0D, 0xF8, 0xFF, 0xFF, 0x7F}; + requireFailure([&] { (void)Codegen::DecodeVexWideStore(indexed32.data(), indexed32.size()); }, "A 32-bit indexed 256-bit store whose upper half can wrap was split"); + const Bytes absolute32Overflow = {0x67, 0xC5, 0xFC, 0x11, 0x04, 0x25, 0xF8, 0xFF, 0xFF, 0xFF}; + requireFailure([&] { (void)Codegen::DecodeVexWideStore(absolute32Overflow.data(), absolute32Overflow.size()); }, "A 32-bit absolute 256-bit store whose upper half crosses the address boundary was split"); const Bytes absolute64 = {0xC5, 0xFC, 0x11, 0x04, 0x25, 0xF8, 0xFF, 0xFF, 0x7F}; requireFailure([&] { (void)Codegen::DecodeVexWideStore(absolute64.data(), absolute64.size()); }, "A 64-bit absolute 256-bit store whose upper half has no signed displacement was kept"); requireFailure([] { Codegen::StubBodyBuilder body; body.RipRelative(Bytes{0x90}, 0, 0); }, "A RIP-relative displacement outside its instruction was relocated"); From f0aac7e014682aa69ef6ce106617fd8746684712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0?= Date: Fri, 30 Oct 2026 14:16:42 +0100 Subject: [PATCH 18/27] fix(relinker): treat VEX.X as an index when splitting 32-bit absolute wide stores Co-Authored-By: Claude Sonnet 5.5 --- core/relinker/codegen/src/x86/WideStoreOperands.cpp | 2 +- core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/relinker/codegen/src/x86/WideStoreOperands.cpp b/core/relinker/codegen/src/x86/WideStoreOperands.cpp index df22eb8456..529d39596b 100644 --- a/core/relinker/codegen/src/x86/WideStoreOperands.cpp +++ b/core/relinker/codegen/src/x86/WideStoreOperands.cpp @@ -79,7 +79,7 @@ std::optional DecodeVexWideStore(const std::uint8_t* data, co } const bool ripRelative = mod == ModRmModIndirect && rm == ModRmRmRipRelative; const bool absolute = mod == ModRmModIndirect && sib.has_value() && (*sib & SibBaseMask) == SibBaseDisp32; - const bool absoluteNoIndex = absolute && (*sib & SibIndexMask) == SibIndexNone; + const bool absoluteNoIndex = absolute && (*sib & SibIndexMask) == SibIndexNone && (inverted & 0x40) != 0; const std::size_t displacementSize = mod == ModRmModDisp8 ? Disp8Size : (mod == ModRmModDisp32 || ripRelative || absolute) ? Disp32Size : 0; if (cursor + displacementSize != length) return std::nullopt; diff --git a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp index 27cf7b5fff..fca262f771 100644 --- a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp +++ b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp @@ -846,6 +846,8 @@ void wideStoreSplit() { requireFailure([&] { (void)Codegen::DecodeVexWideStore(disp32.data(), disp32.size()); }, "A 32-bit base-relative 256-bit store whose upper half can wrap was split"); const Bytes indexed32 = {0x67, 0xC5, 0xFC, 0x11, 0x04, 0x0D, 0xF8, 0xFF, 0xFF, 0x7F}; requireFailure([&] { (void)Codegen::DecodeVexWideStore(indexed32.data(), indexed32.size()); }, "A 32-bit indexed 256-bit store whose upper half can wrap was split"); + const Bytes extendedIndex32 = {0x67, 0xC4, 0xA1, 0x7C, 0x11, 0x04, 0x25, 0xF8, 0xFF, 0xFF, 0x7F}; + requireFailure([&] { (void)Codegen::DecodeVexWideStore(extendedIndex32.data(), extendedIndex32.size()); }, "A 32-bit store indexed by an extended register through VEX.X was split as absolute"); const Bytes absolute32Overflow = {0x67, 0xC5, 0xFC, 0x11, 0x04, 0x25, 0xF8, 0xFF, 0xFF, 0xFF}; requireFailure([&] { (void)Codegen::DecodeVexWideStore(absolute32Overflow.data(), absolute32Overflow.size()); }, "A 32-bit absolute 256-bit store whose upper half crosses the address boundary was split"); const Bytes absolute64 = {0xC5, 0xFC, 0x11, 0x04, 0x25, 0xF8, 0xFF, 0xFF, 0x7F}; From 5df1bc7f20178fe179caca9ad0b796122f593f33 Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Thu, 29 Oct 2026 18:31:43 +0100 Subject: [PATCH 19/27] feat(libSceNpCppWebApi): bring up TitleCloudStorage V1 offline --- .../libs/prx/libSceNpCppWebApi/CMakeLists.txt | 9 + core/libs/prx/libSceNpCppWebApi/Export.cpp | 166 ++++++++++++++++++ .../prx/libSceNpCppWebApi/include/Api.hpp | 35 ++++ .../GuestNpCppWebApiTitleCloudStorage.cpp | 124 +++++++++++++ 4 files changed, 334 insertions(+) create mode 100644 core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp diff --git a/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt index d3ffa1ac9e..a58e22ad78 100644 --- a/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt +++ b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt @@ -14,6 +14,15 @@ if(BUILD_TESTING) target_link_libraries(guest_np_cpp_web_api_string_tests PRIVATE libSceNpCppWebApi libc) configure_windows_unwind(guest_np_cpp_web_api_string_tests) add_test(NAME guest_np_cpp_web_api_string COMMAND guest_np_cpp_web_api_string_tests) + add_test_executable(guest_np_cpp_web_api_title_cloud_storage_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpCppWebApiTitleCloudStorage.cpp) + target_include_directories(guest_np_cpp_web_api_title_cloud_storage_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_cpp_web_api_title_cloud_storage_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_title_cloud_storage_tests) + add_test(NAME guest_np_cpp_web_api_title_cloud_storage COMMAND guest_np_cpp_web_api_title_cloud_storage_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_title_cloud_storage + PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() add_test(NAME guest_np_cpp_web_api_alias_append COMMAND guest_np_cpp_web_api_string_tests alias-append) add_test(NAME guest_np_cpp_web_api_alias_string_vector COMMAND guest_np_cpp_web_api_string_tests alias-vector) add_test(NAME guest_np_cpp_web_api_alias_intrusive_vector COMMAND guest_np_cpp_web_api_intrusive_ptr_tests alias-vector) diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp index 5351d8f8c0..360fa43e5c 100644 --- a/core/libs/prx/libSceNpCppWebApi/Export.cpp +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -279,6 +279,172 @@ int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDow return 0; } +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V129SetDataInfoRequestBodyFactory6createEPNS1_6Common10LibContextEPvmPNS5_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE(void* self) { + (void)self; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi10uploadDataEiRKNS4_21ParameterToUploadDataERNS1_6Common19UpStreamTransactionINS8_12IntrusivePtrINS3_22UploadDataResponseBodyEEENSA_INS4_25UploadDataResponseHeadersEEEEE(void* self) { + (void)self; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi10uploadDataEiRKNS4_42ParameterWithBinaryRequestBodyToUploadDataERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_22UploadDataResponseBodyEEENSA_INS4_25UploadDataResponseHeadersEEEEE(void* self) { + (void)self; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi11setDataInfoEiRKNS4_22ParameterToSetDataInfoERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS4_26SetDataInfoResponseHeadersEEEEE(void* self) { + (void)self; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi12downloadDataEiRKNS4_23ParameterToDownloadDataERNS1_6Common11TransactionINS8_12IntrusivePtrINS8_6BinaryEEENSA_INS4_27DownloadDataResponseHeadersEEEEE(void* self) { + (void)self; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData10initializeEPNS1_6Common10LibContextEPKci(void* self, const std::uint64_t* context) { + return InitializeParameter(self, context, true); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData21setxPsnNpServiceLabelEj(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData22setxPsnAtomicOperationENS5_19XPsnAtomicOperationE(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData9terminateEv(void* self) { + return TerminateParameter(self); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadDataC1Ev(void* self) { + ConstructParameter(self); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadDataD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21deleteMultiDataByUserEiRKNS4_32ParameterToDeleteMultiDataByUserERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self) { + (void)self; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo10initializeEPNS1_6Common10LibContextEPKciNS6_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE(void* self, const std::uint64_t* context) { + return InitializeParameter(self, context, true); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo22setxPsnAtomicOperationENS5_19XPsnAtomicOperationE(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo24setxPsnAtomicOperationIdEPKc(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo9terminateEv(void* self) { + return TerminateParameter(self); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfoC1Ev(void* self) { + ConstructParameter(self); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfoD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData17setnpServiceLabelEj(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser10initializeEPNS1_6Common10LibContextEPKcSA_(void* self, const std::uint64_t* context) { + return InitializeParameter(self, context, true); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser17setnpServiceLabelEj(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser9terminateEv(void* self) { + return TerminateParameter(self); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUserC1Ev(void* self) { + ConstructParameter(self); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUserD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi37ParameterToGetMultiDataStatusesByUser17setnpServiceLabelEj(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData10initializeEPNS1_6Common10LibContextEPKci(void* self, const std::uint64_t* context) { + return InitializeParameter(self, context, true); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData14setRequestBodyEPKhm(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData9terminateEv(void* self) { + return TerminateParameter(self); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadDataC1Ev(void* self) { + ConstructParameter(self); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadDataD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V110DataStatus22getLastUpdatedDateTimeEv(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V110DataStatus7getInfoEv(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi25UploadDataResponseHeaders24getXPsnAtomicOperationIdEv(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi25UploadDataResponseHeaders24hasXPsnAtomicOperationIdEv(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI _ZN3sce2Np9CppWebApi6Common10InitParamsC1Ev(void* self) { (void)self; return 0; diff --git a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp index 9750c96de1..5f2dc45b3f 100644 --- a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp +++ b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp @@ -376,6 +376,41 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V110DataStatus9getSlotIdE void* APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V132GetMultiDataStatusesResponseBody17getDataStatusListEv(void* self); std::uint32_t APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V132GetMultiDataStatusesResponseBody23getTotalDataStatusCountEv(const void* self); std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V15Owner12getAccountIdEv(const void* self); + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V129SetDataInfoRequestBodyFactory6createEPNS1_6Common10LibContextEPvmPNS5_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi10uploadDataEiRKNS4_21ParameterToUploadDataERNS1_6Common19UpStreamTransactionINS8_12IntrusivePtrINS3_22UploadDataResponseBodyEEENSA_INS4_25UploadDataResponseHeadersEEEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi10uploadDataEiRKNS4_42ParameterWithBinaryRequestBodyToUploadDataERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_22UploadDataResponseBodyEEENSA_INS4_25UploadDataResponseHeadersEEEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi11setDataInfoEiRKNS4_22ParameterToSetDataInfoERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS4_26SetDataInfoResponseHeadersEEEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi12downloadDataEiRKNS4_23ParameterToDownloadDataERNS1_6Common11TransactionINS8_12IntrusivePtrINS8_6BinaryEEENSA_INS4_27DownloadDataResponseHeadersEEEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData10initializeEPNS1_6Common10LibContextEPKci(void* self, const std::uint64_t* context); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData21setxPsnNpServiceLabelEj(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData22setxPsnAtomicOperationENS5_19XPsnAtomicOperationE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData9terminateEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadDataC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadDataD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21deleteMultiDataByUserEiRKNS4_32ParameterToDeleteMultiDataByUserERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo10initializeEPNS1_6Common10LibContextEPKciNS6_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE(void* self, const std::uint64_t* context); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo22setxPsnAtomicOperationENS5_19XPsnAtomicOperationE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo24setxPsnAtomicOperationIdEPKc(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo9terminateEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfoC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfoD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData17setnpServiceLabelEj(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser10initializeEPNS1_6Common10LibContextEPKcSA_(void* self, const std::uint64_t* context); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser17setnpServiceLabelEj(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser9terminateEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUserC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUserD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi37ParameterToGetMultiDataStatusesByUser17setnpServiceLabelEj(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData10initializeEPNS1_6Common10LibContextEPKci(void* self, const std::uint64_t* context); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData14setRequestBodyEPKhm(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData9terminateEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadDataC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadDataD1Ev(void* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V110DataStatus22getLastUpdatedDateTimeEv(void* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V110DataStatus7getInfoEv(void* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi25UploadDataResponseHeaders24getXPsnAtomicOperationIdEv(void* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi25UploadDataResponseHeaders24hasXPsnAtomicOperationIdEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi10getFriendsEiRKNS4_21ParameterToGetFriendsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_18GetFriendsResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends10initializeEPNS1_6Common10LibContextEPKc(void* self, const std::uint64_t* context, const char* accountId); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends9terminateEv(void* self); diff --git a/core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp b/core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp new file mode 100644 index 0000000000..1f869d2786 --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp @@ -0,0 +1,124 @@ +#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 int kNotSignedIn = static_cast(0x80552907); +constexpr int kAlreadyInitialized = static_cast(0x8055350f); +constexpr int kNotInitialized = static_cast(0x80553510); +constexpr std::size_t kParameterSize = 0x10; +constexpr std::size_t kGuardSize = 0x10; +constexpr unsigned char kFill = 0xA5; + +struct Storage { + alignas(8) unsigned char bytes[kParameterSize + kGuardSize]; +}; + +bool GuardIntact(const Storage& storage) { + for (std::size_t index = kParameterSize; index < sizeof(storage.bytes); ++index) { + if (storage.bytes[index] != kFill) return false; + } + return true; +} + +using SimpleFn = int (APS5_VABI*)(void*); +using InitFn = int (APS5_VABI*)(void*, const std::uint64_t*); + +const SimpleFn kSignedOut[] = { + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V129SetDataInfoRequestBodyFactory6createEPNS1_6Common10LibContextEPvmPNS5_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi10uploadDataEiRKNS4_21ParameterToUploadDataERNS1_6Common19UpStreamTransactionINS8_12IntrusivePtrINS3_22UploadDataResponseBodyEEENSA_INS4_25UploadDataResponseHeadersEEEEE, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi10uploadDataEiRKNS4_42ParameterWithBinaryRequestBodyToUploadDataERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_22UploadDataResponseBodyEEENSA_INS4_25UploadDataResponseHeadersEEEEE, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi11setDataInfoEiRKNS4_22ParameterToSetDataInfoERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS4_26SetDataInfoResponseHeadersEEEEE, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi12downloadDataEiRKNS4_23ParameterToDownloadDataERNS1_6Common11TransactionINS8_12IntrusivePtrINS8_6BinaryEEENSA_INS4_27DownloadDataResponseHeadersEEEEE, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21deleteMultiDataByUserEiRKNS4_32ParameterToDeleteMultiDataByUserERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE, +}; +const SimpleFn kAccessors[] = { + _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V110DataStatus22getLastUpdatedDateTimeEv, + _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V110DataStatus7getInfoEv, + _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi25UploadDataResponseHeaders24getXPsnAtomicOperationIdEv, + _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi25UploadDataResponseHeaders24hasXPsnAtomicOperationIdEv, +}; +const SimpleFn kSetters[] = {}; +const SimpleFn kConstructors[] = { + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadDataC1Ev, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfoC1Ev, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUserC1Ev, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadDataC1Ev, +}; +const SimpleFn kDestructors[] = { + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadDataD1Ev, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfoD1Ev, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUserD1Ev, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadDataD1Ev, +}; +const SimpleFn kTerminators[] = { + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData9terminateEv, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo9terminateEv, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser9terminateEv, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData9terminateEv, +}; +const InitFn kInitializers[] = { + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData10initializeEPNS1_6Common10LibContextEPKci, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo10initializeEPNS1_6Common10LibContextEPKciNS6_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser10initializeEPNS1_6Common10LibContextEPKcSA_, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData10initializeEPNS1_6Common10LibContextEPKci, +}; + +template +void RequireThrows(TCall call) { + bool threw = false; + try { call(); } catch (const std::runtime_error&) { threw = true; } + Require(threw); +} + +} + +int main() { + for (const auto fn : kSignedOut) Require(fn(nullptr) == kNotSignedIn); + for (const auto fn : kAccessors) RequireThrows([&] { (void)fn(nullptr); }); + for (const auto fn : kSetters) Require(fn(nullptr) == 0); + + for (const auto fn : kConstructors) { + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + Require(fn(storage.bytes) == 0); + Require(GuardIntact(storage)); + } + for (const auto fn : kDestructors) { + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + Require(fn(storage.bytes) == 0); + Require(GuardIntact(storage)); + } + for (const auto fn : kTerminators) { + Storage storage; + std::memset(storage.bytes, 0, sizeof(storage.bytes)); + Require(fn(storage.bytes) == kNotInitialized); + } + for (const auto fn : kInitializers) { + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + std::memset(storage.bytes, 0, kParameterSize); + std::uint64_t context = 1; + Require(fn(storage.bytes, &context) == 0); + Require(fn(storage.bytes, &context) == kAlreadyInitialized); + Require(kTerminators[0](storage.bytes) == 0); + Require(kTerminators[0](storage.bytes) == kNotInitialized); + Require(GuardIntact(storage)); + } + return 0; +} From 6277e4b76da0f37e3dafda3617edd74311d1def4 Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Fri, 30 Oct 2026 09:37:24 +0100 Subject: [PATCH 20/27] fix(libSceNpCppWebApi): address TitleCloudStorage review comments Declare and validate the initialize arguments, give the text and binary setters their parameter handling, list the eight setters in the test, stop pinning the unread response accessors to a throw, and extend the debt entry with the four TitleCloudStorage parameter classes. --- core/libs/prx/libSceNpCppWebApi/Export.cpp | 29 ++-- .../prx/libSceNpCppWebApi/include/Api.hpp | 12 +- .../GuestNpCppWebApiTitleCloudStorage.cpp | 155 +++++++++++++----- docs/dev/debt/libSceNpCppWebApi.md | 2 +- 4 files changed, 134 insertions(+), 64 deletions(-) diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp index 360fa43e5c..cc47ffd628 100644 --- a/core/libs/prx/libSceNpCppWebApi/Export.cpp +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -304,8 +304,9 @@ int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi12downloadDataEi return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } -int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData10initializeEPNS1_6Common10LibContextEPKci(void* self, const std::uint64_t* context) { - return InitializeParameter(self, context, true); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData10initializeEPNS1_6Common10LibContextEPKci(void* self, const std::uint64_t* context, const char* text, int value) { + (void)value; + return InitializeParameter(self, context, text != nullptr); } int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData21setxPsnNpServiceLabelEj(void* self) { @@ -337,8 +338,9 @@ int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21deleteMultiDat return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } -int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo10initializeEPNS1_6Common10LibContextEPKciNS6_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE(void* self, const std::uint64_t* context) { - return InitializeParameter(self, context, true); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo10initializeEPNS1_6Common10LibContextEPKciNS6_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE(void* self, const std::uint64_t* context, const char* text, int value, const IntrusivePtr* body) { + (void)value; + return InitializeParameterWithBody(self, context, text != nullptr, body); } int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo22setxPsnAtomicOperationENS5_19XPsnAtomicOperationE(void* self) { @@ -346,9 +348,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSet return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo24setxPsnAtomicOperationIdEPKc(void* self) { - (void)self; - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo24setxPsnAtomicOperationIdEPKc(void* self, const char* text) { + return SetParameterText(self, text); } int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo9terminateEv(void* self) { @@ -370,8 +371,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDow return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser10initializeEPNS1_6Common10LibContextEPKcSA_(void* self, const std::uint64_t* context) { - return InitializeParameter(self, context, true); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser10initializeEPNS1_6Common10LibContextEPKcSA_(void* self, const std::uint64_t* context, const char* first, const char* second) { + return InitializeParameter(self, context, first != nullptr && second != nullptr); } int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser17setnpServiceLabelEj(void* self) { @@ -398,12 +399,14 @@ int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi37ParameterToGet return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData10initializeEPNS1_6Common10LibContextEPKci(void* self, const std::uint64_t* context) { - return InitializeParameter(self, context, true); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData10initializeEPNS1_6Common10LibContextEPKci(void* self, const std::uint64_t* context, const char* text, int value) { + (void)value; + return InitializeParameter(self, context, text != nullptr); } -int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData14setRequestBodyEPKhm(void* self) { - (void)self; +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData14setRequestBodyEPKhm(void* self, const void* data, std::size_t size) { + if (static_cast(self)->implementation == nullptr) return ParameterNotInitialized; + if (data == nullptr && size != 0) return ParameterInvalidArgument; return 0; } diff --git a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp index 5f2dc45b3f..e25f8b4495 100644 --- a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp +++ b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp @@ -382,28 +382,28 @@ int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi10uploadDataEiRK int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi10uploadDataEiRKNS4_42ParameterWithBinaryRequestBodyToUploadDataERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_22UploadDataResponseBodyEEENSA_INS4_25UploadDataResponseHeadersEEEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi11setDataInfoEiRKNS4_22ParameterToSetDataInfoERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS4_26SetDataInfoResponseHeadersEEEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi12downloadDataEiRKNS4_23ParameterToDownloadDataERNS1_6Common11TransactionINS8_12IntrusivePtrINS8_6BinaryEEENSA_INS4_27DownloadDataResponseHeadersEEEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData10initializeEPNS1_6Common10LibContextEPKci(void* self, const std::uint64_t* context); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData10initializeEPNS1_6Common10LibContextEPKci(void* self, const std::uint64_t* context, const char* text, int value); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData21setxPsnNpServiceLabelEj(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData22setxPsnAtomicOperationENS5_19XPsnAtomicOperationE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData9terminateEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadDataC1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadDataD1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21deleteMultiDataByUserEiRKNS4_32ParameterToDeleteMultiDataByUserERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo10initializeEPNS1_6Common10LibContextEPKciNS6_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE(void* self, const std::uint64_t* context); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo10initializeEPNS1_6Common10LibContextEPKciNS6_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE(void* self, const std::uint64_t* context, const char* text, int value, const IntrusivePtr* body); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo22setxPsnAtomicOperationENS5_19XPsnAtomicOperationE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo24setxPsnAtomicOperationIdEPKc(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo24setxPsnAtomicOperationIdEPKc(void* self, const char* text); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo9terminateEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfoC1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfoD1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData17setnpServiceLabelEj(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser10initializeEPNS1_6Common10LibContextEPKcSA_(void* self, const std::uint64_t* context); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser10initializeEPNS1_6Common10LibContextEPKcSA_(void* self, const std::uint64_t* context, const char* first, const char* second); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser17setnpServiceLabelEj(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser9terminateEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUserC1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUserD1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi37ParameterToGetMultiDataStatusesByUser17setnpServiceLabelEj(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData10initializeEPNS1_6Common10LibContextEPKci(void* self, const std::uint64_t* context); -int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData14setRequestBodyEPKhm(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData10initializeEPNS1_6Common10LibContextEPKci(void* self, const std::uint64_t* context, const char* text, int value); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData14setRequestBodyEPKhm(void* self, const void* data, std::size_t size); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData9terminateEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadDataC1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadDataD1Ev(void* self); diff --git a/core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp b/core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp index 1f869d2786..da9a621cb3 100644 --- a/core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp +++ b/core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp @@ -6,7 +6,6 @@ #include #include #include -#include namespace { @@ -18,8 +17,11 @@ void Require(bool value, std::source_location at = std::source_location::current } constexpr int kNotSignedIn = static_cast(0x80552907); +constexpr int kParameterNotInitialized = static_cast(0x80553502); +constexpr int kInvalidArgument = static_cast(0x80553503); constexpr int kAlreadyInitialized = static_cast(0x8055350f); -constexpr int kNotInitialized = static_cast(0x80553510); +constexpr int kTerminatedUninitialized = static_cast(0x80553510); +constexpr int kBodyMissing = static_cast(0x80553512); constexpr std::size_t kParameterSize = 0x10; constexpr std::size_t kGuardSize = 0x10; constexpr unsigned char kFill = 0xA5; @@ -35,8 +37,11 @@ bool GuardIntact(const Storage& storage) { return true; } +void APS5_VABI NoopDelete(void*) {} + using SimpleFn = int (APS5_VABI*)(void*); -using InitFn = int (APS5_VABI*)(void*, const std::uint64_t*); +using TextFn = int (APS5_VABI*)(void*, const char*); +using DataFn = int (APS5_VABI*)(void*, const void*, std::size_t); const SimpleFn kSignedOut[] = { _ZN3sce2Np9CppWebApi17TitleCloudStorage2V129SetDataInfoRequestBodyFactory6createEPNS1_6Common10LibContextEPvmPNS5_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE, @@ -46,13 +51,20 @@ const SimpleFn kSignedOut[] = { _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi12downloadDataEiRKNS4_23ParameterToDownloadDataERNS1_6Common11TransactionINS8_12IntrusivePtrINS8_6BinaryEEENSA_INS4_27DownloadDataResponseHeadersEEEEE, _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21deleteMultiDataByUserEiRKNS4_32ParameterToDeleteMultiDataByUserERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE, }; -const SimpleFn kAccessors[] = { - _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V110DataStatus22getLastUpdatedDateTimeEv, - _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V110DataStatus7getInfoEv, - _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi25UploadDataResponseHeaders24getXPsnAtomicOperationIdEv, - _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi25UploadDataResponseHeaders24hasXPsnAtomicOperationIdEv, +const SimpleFn kNumericSetters[] = { + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData21setxPsnNpServiceLabelEj, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData22setxPsnAtomicOperationENS5_19XPsnAtomicOperationE, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo22setxPsnAtomicOperationENS5_19XPsnAtomicOperationE, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData17setnpServiceLabelEj, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser17setnpServiceLabelEj, + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi37ParameterToGetMultiDataStatusesByUser17setnpServiceLabelEj, +}; +const TextFn kTextSetters[] = { + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo24setxPsnAtomicOperationIdEPKc, +}; +const DataFn kDataSetters[] = { + _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData14setRequestBodyEPKhm, }; -const SimpleFn kSetters[] = {}; const SimpleFn kConstructors[] = { _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadDataC1Ev, _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfoC1Ev, @@ -71,54 +83,109 @@ const SimpleFn kTerminators[] = { _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser9terminateEv, _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData9terminateEv, }; -const InitFn kInitializers[] = { - _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData10initializeEPNS1_6Common10LibContextEPKci, - _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo10initializeEPNS1_6Common10LibContextEPKciNS6_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE, - _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser10initializeEPNS1_6Common10LibContextEPKcSA_, - _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData10initializeEPNS1_6Common10LibContextEPKci, -}; -template -void RequireThrows(TCall call) { - bool threw = false; - try { call(); } catch (const std::runtime_error&) { threw = true; } - Require(threw); +bool Constructed(SimpleFn constructor) { + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + if (constructor(storage.bytes) != 0) return false; + if (!GuardIntact(storage)) return false; + for (std::size_t index = 0; index < kParameterSize; ++index) { + if (storage.bytes[index] != 0) return false; + } + return true; +} + +void TestInitializers() { + const std::uint64_t uninitialized = 0; + std::uint64_t context = 1; + + { + Storage parameter; + std::memset(parameter.bytes, kFill, sizeof(parameter.bytes)); + Require(kConstructors[0](parameter.bytes) == 0 && GuardIntact(parameter)); + Require(_ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData10initializeEPNS1_6Common10LibContextEPKci(parameter.bytes, nullptr, "label", 0) == kInvalidArgument); + Require(_ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData10initializeEPNS1_6Common10LibContextEPKci(parameter.bytes, &uninitialized, "label", 0) == kParameterNotInitialized); + Require(_ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData10initializeEPNS1_6Common10LibContextEPKci(parameter.bytes, &context, nullptr, 0) == kInvalidArgument); + Require(_ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData10initializeEPNS1_6Common10LibContextEPKci(parameter.bytes, &context, "label", 0) == 0); + Require(_ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi21ParameterToUploadData10initializeEPNS1_6Common10LibContextEPKci(parameter.bytes, &context, "label", 0) == kAlreadyInitialized); + Require(kTerminators[0](parameter.bytes) == 0); + Require(kTerminators[0](parameter.bytes) == kTerminatedUninitialized); + Require(kDestructors[0](parameter.bytes) == 0 && GuardIntact(parameter)); + } + { + Storage parameter; + std::memset(parameter.bytes, kFill, sizeof(parameter.bytes)); + Require(kConstructors[1](parameter.bytes) == 0 && GuardIntact(parameter)); + IntrusivePtr missing{}; + IntrusivePtr body{parameter.bytes, NoopDelete, nullptr}; + Require(_ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo10initializeEPNS1_6Common10LibContextEPKciNS6_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE(parameter.bytes, &context, "label", 0, &missing) == kBodyMissing); + Require(_ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo10initializeEPNS1_6Common10LibContextEPKciNS6_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE(parameter.bytes, &context, "label", 0, &body) == 0); + Require(kTerminators[1](parameter.bytes) == 0); + Require(kDestructors[1](parameter.bytes) == 0 && GuardIntact(parameter)); + } + { + Storage parameter; + std::memset(parameter.bytes, kFill, sizeof(parameter.bytes)); + Require(kConstructors[2](parameter.bytes) == 0 && GuardIntact(parameter)); + Require(_ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser10initializeEPNS1_6Common10LibContextEPKcSA_(parameter.bytes, &context, "slot", nullptr) == kInvalidArgument); + Require(_ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi32ParameterToDeleteMultiDataByUser10initializeEPNS1_6Common10LibContextEPKcSA_(parameter.bytes, &context, "slot", "me") == 0); + Require(kTerminators[2](parameter.bytes) == 0); + Require(kDestructors[2](parameter.bytes) == 0 && GuardIntact(parameter)); + } + { + Storage parameter; + std::memset(parameter.bytes, kFill, sizeof(parameter.bytes)); + Require(kConstructors[3](parameter.bytes) == 0 && GuardIntact(parameter)); + Require(_ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData10initializeEPNS1_6Common10LibContextEPKci(parameter.bytes, &context, nullptr, 0) == kInvalidArgument); + Require(_ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData10initializeEPNS1_6Common10LibContextEPKci(parameter.bytes, &context, "slot", 0) == 0); + Require(kTerminators[3](parameter.bytes) == 0); + Require(kDestructors[3](parameter.bytes) == 0 && GuardIntact(parameter)); + } +} + +void TestSetters() { + for (const auto setter : kNumericSetters) Require(setter(nullptr) == 0); + Storage parameter; + std::memset(parameter.bytes, kFill, sizeof(parameter.bytes)); + Require(kConstructors[1](parameter.bytes) == 0); + for (const auto setter : kTextSetters) Require(setter(parameter.bytes, "id") == kParameterNotInitialized); + std::uint64_t context = 1; + IntrusivePtr missing{}; + Require(_ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo10initializeEPNS1_6Common10LibContextEPKciNS6_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE(parameter.bytes, &context, "label", 0, &missing) == kBodyMissing); + IntrusivePtr body{parameter.bytes, NoopDelete, nullptr}; + Require(_ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi22ParameterToSetDataInfo10initializeEPNS1_6Common10LibContextEPKciNS6_12IntrusivePtrINS3_22SetDataInfoRequestBodyEEE(parameter.bytes, &context, "label", 0, &body) == 0); + Require(kTextSetters[0](parameter.bytes, nullptr) == kInvalidArgument); + Require(kTextSetters[0](parameter.bytes, "id") == 0); + Require(kTerminators[1](parameter.bytes) == 0); + + Storage binary; + std::memset(binary.bytes, kFill, sizeof(binary.bytes)); + Require(kConstructors[3](binary.bytes) == 0); + for (const auto setter : kDataSetters) Require(setter(binary.bytes, nullptr, 0) == kParameterNotInitialized); + Require(_ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi42ParameterWithBinaryRequestBodyToUploadData10initializeEPNS1_6Common10LibContextEPKci(binary.bytes, &context, "slot", 0) == 0); + Require(kDataSetters[0](binary.bytes, nullptr, 1) == kInvalidArgument); + const unsigned char data[] = {1, 2, 3}; + Require(kDataSetters[0](binary.bytes, data, sizeof(data)) == 0); + Require(kTerminators[3](binary.bytes) == 0); } } int main() { for (const auto fn : kSignedOut) Require(fn(nullptr) == kNotSignedIn); - for (const auto fn : kAccessors) RequireThrows([&] { (void)fn(nullptr); }); - for (const auto fn : kSetters) Require(fn(nullptr) == 0); - - for (const auto fn : kConstructors) { + for (const auto constructor : kConstructors) Require(Constructed(constructor)); + for (const auto destructor : kDestructors) { Storage storage; std::memset(storage.bytes, kFill, sizeof(storage.bytes)); - Require(fn(storage.bytes) == 0); + Require(destructor(storage.bytes) == 0); Require(GuardIntact(storage)); } - for (const auto fn : kDestructors) { - Storage storage; - std::memset(storage.bytes, kFill, sizeof(storage.bytes)); - Require(fn(storage.bytes) == 0); - Require(GuardIntact(storage)); - } - for (const auto fn : kTerminators) { + for (const auto terminator : kTerminators) { Storage storage; std::memset(storage.bytes, 0, sizeof(storage.bytes)); - Require(fn(storage.bytes) == kNotInitialized); - } - for (const auto fn : kInitializers) { - Storage storage; - std::memset(storage.bytes, kFill, sizeof(storage.bytes)); - std::memset(storage.bytes, 0, kParameterSize); - std::uint64_t context = 1; - Require(fn(storage.bytes, &context) == 0); - Require(fn(storage.bytes, &context) == kAlreadyInitialized); - Require(kTerminators[0](storage.bytes) == 0); - Require(kTerminators[0](storage.bytes) == kNotInitialized); - Require(GuardIntact(storage)); + Require(terminator(storage.bytes) == kTerminatedUninitialized); } + TestInitializers(); + TestSetters(); return 0; } diff --git a/docs/dev/debt/libSceNpCppWebApi.md b/docs/dev/debt/libSceNpCppWebApi.md index b53a0809d7..6df06a32f0 100644 --- a/docs/dev/debt/libSceNpCppWebApi.md +++ b/docs/dev/debt/libSceNpCppWebApi.md @@ -5,7 +5,7 @@ - [sce::Np::CppWebApi::Common](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the `finish` overrides of `DownStreamTransaction<...>` (2 instantiations) and `Vector>::pushBack` return success without doing work; a failed request never produces a transaction to finish. `Transaction<...>::setOnFinishedCallback` and `TransactionBase<...>::setResponseInformationOption` (5 instantiations each, the `UserProfile::V1`, `ProfanityFilter::V2` and `CommunicationRestrictionStatus::V3` responses) return success without storing the callback or the option: `start` fails, so neither is ever used. The constructors and destructors of the same five `Transaction<...>` instantiations succeed without keeping state - [sce::Np::CppWebApi::Leaderboards::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - request-body bookkeeping succeeds without storing state: `GetRankingRequestBody::set{StartSerialRank,Group,Limit,Users,Offset}`, `RecordScoreRequestBody::set{Comment,SmallData,NeedsTmpRank,ComparedDateTime,PcId}` and `User::{setAccountId,setPcId}`. `BoardsApi::ParameterToGetBoardDefinition`, `RecordApi::ParameterToRecordLargeData`, `RecordApi::ParameterToRecordScore`, `ViewApi::ParameterToGetLargeDataByObjectId` and `ViewApi::ParameterToGetRanking` keep only whether they are initialized, as the SessionManager::V1 parameters do: `initialize` and `terminate` return the codes of the module bundled with PPSA21564, 0x80553512 for an empty `RecordScoreRequestBody` included. They store none of the arguments, and `setxPsnAtomicOperation` and `setgetRankingRequestBody` store nothing either - [sce::Np::CppWebApi::SessionManager::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - `RequestPlayerSession::set{MaxSpectators,InvitableUserType,JoinableUserType,ExclusiveLeaderPrivileges}` and `RequestPlayerSessionPlayer::setPushContexts` succeed without storing state. The `PlayerSessionsApi` parameters `ParameterToCreatePlayerSessions`, `ParameterToGetPlayerSessionInvitations`, `ParameterToJoinPlayerSessionAsPlayer` and `ParameterToLeavePlayerSession` keep only whether they are initialized, as the TitleCloudStorage ones do: `initialize`, `terminate` and `setinvitationInvalidFilter` return the codes of the module bundled with PPSA21564, plus 0x80553512 when the request body of `ParameterToCreatePlayerSessions` or `ParameterToJoinPlayerSessionAsPlayer` is empty. They store none of the strings or bodies, and `setfields` stores nothing either: only a request would read them -- [sce::Np::CppWebApi::TitleCloudStorage::V1 and InGameCatalog::V5](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - `DataApi::ParameterToDownloadData`, `DataApi::ParameterToGetMultiDataStatusesByUser` and `ContainerApi::ParameterToGetContainer` keep only whether they are initialized. `initialize`, `terminate` and the string setters return the codes of the module bundled with PPSA21564: 0x80553503 for a null `LibContext` or string, 0x80553502 for a `LibContext` that `Common::initialize` did not set or a parameter not yet initialized, 0x8055350f for a second `initialize` and 0x80553510 for `terminate` without one. They store none of the values, and `ParameterToGetContainer::set{sort,offset,limit,serviceLabel}` store nothing either: only a request would read them +- [sce::Np::CppWebApi::TitleCloudStorage::V1 and InGameCatalog::V5](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - `DataApi::ParameterToDownloadData`, `DataApi::ParameterToGetMultiDataStatusesByUser` and `ContainerApi::ParameterToGetContainer` keep only whether they are initialized. `initialize`, `terminate` and the string setters return the codes of the module bundled with PPSA21564: 0x80553503 for a null `LibContext` or string, 0x80553502 for a `LibContext` that `Common::initialize` did not set or a parameter not yet initialized, 0x8055350f for a second `initialize` and 0x80553510 for `terminate` without one; an empty `SetDataInfo` body returns 0x80553512. `ParameterToUploadData`, `ParameterToSetDataInfo`, `ParameterToDeleteMultiDataByUser` and `ParameterWithBinaryRequestBodyToUploadData` add state-less setters and keep only the initialized flag. None store values; only a request would read them. The console behaviour is not measured. - [sce::Np::CppWebApi::UserProfile::V1, ProfanityFilter::V2, CommunicationRestrictionStatus::V3](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - `FriendsApi::ParameterToGetFriends`, `PresenceApi::ParameterToGetBasicPresences`, `BlocksApi::ParameterToGetBlockingUsers`, `ProfanityApi::ParameterToFilterProfanity` and `CommunicationRestrictionStatusApi::ParameterToGetCommunicationRestrictionStatus` keep only whether they are initialized, as the SessionManager::V1 parameters do: `initialize` and `terminate` return the codes of the module bundled with PPSA21564, 0x80553512 for an empty `WebApiFilterRequest` included. They store none of the arguments: only a request would read them ### Unknown function info From bb0900fea0113b04ba9ed41d81b81cd89f34838b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0?= Date: Fri, 30 Oct 2026 14:20:21 +0100 Subject: [PATCH 21/27] docs(libSceNpCppWebApi): record the throwing TitleCloudStorage accessors in debt --- 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 6df06a32f0..1385523226 100644 --- a/docs/dev/debt/libSceNpCppWebApi.md +++ b/docs/dev/debt/libSceNpCppWebApi.md @@ -5,7 +5,7 @@ - [sce::Np::CppWebApi::Common](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the `finish` overrides of `DownStreamTransaction<...>` (2 instantiations) and `Vector>::pushBack` return success without doing work; a failed request never produces a transaction to finish. `Transaction<...>::setOnFinishedCallback` and `TransactionBase<...>::setResponseInformationOption` (5 instantiations each, the `UserProfile::V1`, `ProfanityFilter::V2` and `CommunicationRestrictionStatus::V3` responses) return success without storing the callback or the option: `start` fails, so neither is ever used. The constructors and destructors of the same five `Transaction<...>` instantiations succeed without keeping state - [sce::Np::CppWebApi::Leaderboards::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - request-body bookkeeping succeeds without storing state: `GetRankingRequestBody::set{StartSerialRank,Group,Limit,Users,Offset}`, `RecordScoreRequestBody::set{Comment,SmallData,NeedsTmpRank,ComparedDateTime,PcId}` and `User::{setAccountId,setPcId}`. `BoardsApi::ParameterToGetBoardDefinition`, `RecordApi::ParameterToRecordLargeData`, `RecordApi::ParameterToRecordScore`, `ViewApi::ParameterToGetLargeDataByObjectId` and `ViewApi::ParameterToGetRanking` keep only whether they are initialized, as the SessionManager::V1 parameters do: `initialize` and `terminate` return the codes of the module bundled with PPSA21564, 0x80553512 for an empty `RecordScoreRequestBody` included. They store none of the arguments, and `setxPsnAtomicOperation` and `setgetRankingRequestBody` store nothing either - [sce::Np::CppWebApi::SessionManager::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - `RequestPlayerSession::set{MaxSpectators,InvitableUserType,JoinableUserType,ExclusiveLeaderPrivileges}` and `RequestPlayerSessionPlayer::setPushContexts` succeed without storing state. The `PlayerSessionsApi` parameters `ParameterToCreatePlayerSessions`, `ParameterToGetPlayerSessionInvitations`, `ParameterToJoinPlayerSessionAsPlayer` and `ParameterToLeavePlayerSession` keep only whether they are initialized, as the TitleCloudStorage ones do: `initialize`, `terminate` and `setinvitationInvalidFilter` return the codes of the module bundled with PPSA21564, plus 0x80553512 when the request body of `ParameterToCreatePlayerSessions` or `ParameterToJoinPlayerSessionAsPlayer` is empty. They store none of the strings or bodies, and `setfields` stores nothing either: only a request would read them -- [sce::Np::CppWebApi::TitleCloudStorage::V1 and InGameCatalog::V5](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - `DataApi::ParameterToDownloadData`, `DataApi::ParameterToGetMultiDataStatusesByUser` and `ContainerApi::ParameterToGetContainer` keep only whether they are initialized. `initialize`, `terminate` and the string setters return the codes of the module bundled with PPSA21564: 0x80553503 for a null `LibContext` or string, 0x80553502 for a `LibContext` that `Common::initialize` did not set or a parameter not yet initialized, 0x8055350f for a second `initialize` and 0x80553510 for `terminate` without one; an empty `SetDataInfo` body returns 0x80553512. `ParameterToUploadData`, `ParameterToSetDataInfo`, `ParameterToDeleteMultiDataByUser` and `ParameterWithBinaryRequestBodyToUploadData` add state-less setters and keep only the initialized flag. None store values; only a request would read them. The console behaviour is not measured. +- [sce::Np::CppWebApi::TitleCloudStorage::V1 and InGameCatalog::V5](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - `DataApi::ParameterToDownloadData`, `DataApi::ParameterToGetMultiDataStatusesByUser` and `ContainerApi::ParameterToGetContainer` keep only whether they are initialized. `initialize`, `terminate` and the string setters return the codes of the module bundled with PPSA21564: 0x80553503 for a null `LibContext` or string, 0x80553502 for a `LibContext` that `Common::initialize` did not set or a parameter not yet initialized, 0x8055350f for a second `initialize` and 0x80553510 for `terminate` without one; an empty `SetDataInfo` body returns 0x80553512. `ParameterToUploadData`, `ParameterToSetDataInfo`, `ParameterToDeleteMultiDataByUser` and `ParameterWithBinaryRequestBodyToUploadData` add state-less setters and keep only the initialized flag. None store values; only a request would read them. `DataStatus::{getLastUpdatedDateTime,getInfo}` and `UploadDataResponseHeaders::{getXPsnAtomicOperationId,hasXPsnAtomicOperationId}` throw, as a failed request never produces those objects. The console behaviour is not measured. - [sce::Np::CppWebApi::UserProfile::V1, ProfanityFilter::V2, CommunicationRestrictionStatus::V3](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - `FriendsApi::ParameterToGetFriends`, `PresenceApi::ParameterToGetBasicPresences`, `BlocksApi::ParameterToGetBlockingUsers`, `ProfanityApi::ParameterToFilterProfanity` and `CommunicationRestrictionStatusApi::ParameterToGetCommunicationRestrictionStatus` keep only whether they are initialized, as the SessionManager::V1 parameters do: `initialize` and `terminate` return the codes of the module bundled with PPSA21564, 0x80553512 for an empty `WebApiFilterRequest` included. They store none of the arguments: only a request would read them ### Unknown function info From 126a40816fb2f1e9dd43b7e0e191dca9cc8cc6ae Mon Sep 17 00:00:00 2001 From: BunnyFl <166477956+BunnyFl@users.noreply.github.com> Date: Thu, 22 Oct 2026 06:28:04 +0200 Subject: [PATCH 22/27] feat(libSceSystemService): answer the codec activation calls as firmware 13.42 does Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceSystemService/CMakeLists.txt | 9 ++ .../libSceSystemService/PendingExports.cpp | 102 ++++++++++-------- .../include/SystemService.hpp | 2 +- .../tests/GuestSystemServiceActivation.cpp | 83 ++++++++++++++ docs/dev/debt/libSceSystemService.md | 7 +- 5 files changed, 153 insertions(+), 50 deletions(-) create mode 100644 core/libs/tests/GuestSystemServiceActivation.cpp diff --git a/core/libs/prx/libSceSystemService/CMakeLists.txt b/core/libs/prx/libSceSystemService/CMakeLists.txt index 2831f4653a..140da9cc88 100644 --- a/core/libs/prx/libSceSystemService/CMakeLists.txt +++ b/core/libs/prx/libSceSystemService/CMakeLists.txt @@ -131,6 +131,15 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$;PATH=path_list_prepend:$") endif() endforeach() + add_test_executable(guest_system_service_activation_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemServiceActivation.cpp) + target_include_directories(guest_system_service_activation_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_system_service_activation_tests PRIVATE libSceSystemService libc) + configure_windows_unwind(guest_system_service_activation_tests) + add_test(NAME guest_system_service_activation COMMAND guest_system_service_activation_tests) + if(WIN32) + set_tests_properties(guest_system_service_activation PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_system_kill_app_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemKillApp.cpp) target_include_directories(guest_system_kill_app_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_system_kill_app_tests PRIVATE libSceSystemService libc) diff --git a/core/libs/prx/libSceSystemService/PendingExports.cpp b/core/libs/prx/libSceSystemService/PendingExports.cpp index 21179c6118..e7a82e1f5a 100644 --- a/core/libs/prx/libSceSystemService/PendingExports.cpp +++ b/core/libs/prx/libSceSystemService/PendingExports.cpp @@ -2,10 +2,22 @@ #include "prx/libSceSystemService/SystemService.hpp" #include "prx/libc/include/General.hpp" +#include + namespace { constexpr int SystemServiceUnavailable = static_cast(0x80A10002); +constexpr int SystemServiceInvalidArgument = static_cast(0x80A10003); constexpr int SystemServiceRejected = static_cast(0x80A10005); +constexpr int ShellCoreInvalidArgument = static_cast(0x80E30005); + +constexpr std::int32_t ActivationTypeMpeg2 = 0; +constexpr std::int32_t ActivationTypeHevc = 3; +constexpr std::int32_t ActivationTypeHevcSoft = 4; +constexpr std::int32_t ActivationStatus = 2; +constexpr std::intptr_t ActivationHandleStep = 4; + +std::atomic nextActivationHandle{0x1000}; } @@ -542,17 +554,21 @@ int APS5_VABI sceShellCoreUtilActivateAbort(std::intptr_t handle) { } int APS5_VABI sceShellCoreUtilActivateGetStatus(std::intptr_t handle, std::int32_t* status, std::int32_t* result) { - NotImplemented_nid_no_patch(__func__); + if (status == nullptr || result == nullptr) return ShellCoreInvalidArgument; + *status = ActivationStatus; + *result = 0; return 0; } int APS5_VABI sceShellCoreUtilActivateInit(std::int32_t type, std::intptr_t* handle) { - NotImplemented_nid_no_patch(__func__); + if (handle == nullptr) return ShellCoreInvalidArgument; + *handle = nextActivationHandle.fetch_add(ActivationHandleStep); return 0; } -int APS5_VABI sceShellCoreUtilActivateIsActivated(std::intptr_t handle, bool* activated) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceShellCoreUtilActivateIsActivated(std::int32_t type, bool* activated) { + if (activated == nullptr) return ShellCoreInvalidArgument; + *activated = true; return 0; } @@ -1567,103 +1583,101 @@ int APS5_VABI sceSystemServiceAcquireFb0() { } int APS5_VABI sceSystemServiceActivateHevc() { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceShellCoreUtilActivateRecordActivation(); } int APS5_VABI sceSystemServiceActivateHevcAbort(std::uintptr_t handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle == 0) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateAbort(static_cast(handle)); } int APS5_VABI sceSystemServiceActivateHevcGetStatus(std::uintptr_t handle, std::uint32_t* status, std::int32_t* result) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle == 0 || status == nullptr || result == nullptr) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateGetStatus(static_cast(handle), reinterpret_cast(status), result); } int APS5_VABI sceSystemServiceActivateHevcInit(std::uintptr_t* handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle == nullptr) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateInit(ActivationTypeHevc, reinterpret_cast(handle)); } int APS5_VABI sceSystemServiceActivateHevcIsActivated(std::uint8_t* activated) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (activated == nullptr) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateIsActivated(ActivationTypeHevc, reinterpret_cast(activated)); } int APS5_VABI sceSystemServiceActivateHevcSoft() { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceShellCoreUtilActivateRecordActivation(); } int APS5_VABI sceSystemServiceActivateHevcSoftAbort(std::uintptr_t handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle == 0) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateAbort(static_cast(handle)); } int APS5_VABI sceSystemServiceActivateHevcSoftGetStatus(std::uintptr_t handle, std::uint32_t* status, std::int32_t* result) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle == 0 || status == nullptr || result == nullptr) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateGetStatus(static_cast(handle), reinterpret_cast(status), result); } int APS5_VABI sceSystemServiceActivateHevcSoftInit(std::uintptr_t* handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle == nullptr) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateInit(ActivationTypeHevcSoft, reinterpret_cast(handle)); } int APS5_VABI sceSystemServiceActivateHevcSoftIsActivated(std::uint8_t* activated) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (activated == nullptr) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateIsActivated(ActivationTypeHevcSoft, reinterpret_cast(activated)); } int APS5_VABI sceSystemServiceActivateHevcSoftStart(std::uintptr_t handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle == 0) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateStartAsync(static_cast(handle)); } int APS5_VABI sceSystemServiceActivateHevcSoftTerm(std::uintptr_t handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle == 0) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateTerm(static_cast(handle)); } int APS5_VABI sceSystemServiceActivateHevcStart(std::uintptr_t handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle == 0) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateStartAsync(static_cast(handle)); } int APS5_VABI sceSystemServiceActivateHevcTerm(std::uintptr_t handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle == 0) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateTerm(static_cast(handle)); } int APS5_VABI sceSystemServiceActivateMpeg2Abort(std::uintptr_t handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle == 0) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateAbort(static_cast(handle)); } int APS5_VABI sceSystemServiceActivateMpeg2GetStatus(std::uintptr_t handle, std::uint32_t* status, std::int32_t* result) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle == 0 || status == nullptr || result == nullptr) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateGetStatus(static_cast(handle), reinterpret_cast(status), result); } int APS5_VABI sceSystemServiceActivateMpeg2Init(std::uintptr_t* handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle == nullptr) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateInit(ActivationTypeMpeg2, reinterpret_cast(handle)); } int APS5_VABI sceSystemServiceActivateMpeg2IsActivated(std::uint8_t* activated) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (activated == nullptr) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateIsActivated(ActivationTypeMpeg2, reinterpret_cast(activated)); } int APS5_VABI sceSystemServiceActivateMpeg2Start(std::uintptr_t handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle == 0) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateStartAsync(static_cast(handle)); } int APS5_VABI sceSystemServiceActivateMpeg2Term(std::uintptr_t handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle == 0) return SystemServiceInvalidArgument; + return sceShellCoreUtilActivateTerm(static_cast(handle)); } int APS5_VABI sceSystemServiceAddLocalProcess(std::int32_t appId, const char* path, const char* const* argv, const SystemServiceLocalProcessParam* param) { diff --git a/core/libs/prx/libSceSystemService/include/SystemService.hpp b/core/libs/prx/libSceSystemService/include/SystemService.hpp index 6c0120e66b..a62f21fcc8 100644 --- a/core/libs/prx/libSceSystemService/include/SystemService.hpp +++ b/core/libs/prx/libSceSystemService/include/SystemService.hpp @@ -353,7 +353,7 @@ int APS5_VABI sceShellCoreUtilAcquireSharePlayCpuBudget(); int APS5_VABI sceShellCoreUtilActivateAbort(std::intptr_t handle); int APS5_VABI sceShellCoreUtilActivateGetStatus(std::intptr_t handle, std::int32_t* status, std::int32_t* result); int APS5_VABI sceShellCoreUtilActivateInit(std::int32_t type, std::intptr_t* handle); -int APS5_VABI sceShellCoreUtilActivateIsActivated(std::intptr_t handle, bool* activated); +int APS5_VABI sceShellCoreUtilActivateIsActivated(std::int32_t type, bool* activated); int APS5_VABI sceShellCoreUtilActivateRecordActivation(); int APS5_VABI sceShellCoreUtilActivateStart(std::intptr_t handle); int APS5_VABI sceShellCoreUtilActivateStartAsync(std::intptr_t handle); diff --git a/core/libs/tests/GuestSystemServiceActivation.cpp b/core/libs/tests/GuestSystemServiceActivation.cpp new file mode 100644 index 0000000000..3fc4f9762c --- /dev/null +++ b/core/libs/tests/GuestSystemServiceActivation.cpp @@ -0,0 +1,83 @@ +#include "prx/libSceSystemService/include/SystemService.hpp" +#include "SceTypes.hpp" +#include +#include +#include + +namespace { + +constexpr int invalidArgument = static_cast(0x80A10003u); +constexpr int shellInvalidArgument = static_cast(0x80E30005u); +constexpr std::uintptr_t untouchedHandle = 0x5A5A5A5A5A5A5A5Au; +constexpr std::uint32_t untouchedStatus = 0x5A5A5A5Au; +constexpr std::int32_t untouchedResult = 0x5A5A5A5A; +constexpr std::uint8_t untouchedFlag = 0x5A; + +void Require(bool value) { if (!value) std::abort(); } + +struct Codec { + int (APS5_VABI* init)(std::uintptr_t*); + int (APS5_VABI* isActivated)(std::uint8_t*); + int (APS5_VABI* start)(std::uintptr_t); + int (APS5_VABI* getStatus)(std::uintptr_t, std::uint32_t*, std::int32_t*); + int (APS5_VABI* abort)(std::uintptr_t); + int (APS5_VABI* term)(std::uintptr_t); +}; + +void TestShellCalls(std::intptr_t expectedHandle) { + Require(sceShellCoreUtilActivateInit(7, nullptr) == shellInvalidArgument); + std::intptr_t handle = 0; + Require(sceShellCoreUtilActivateInit(7, &handle) == 0 && handle == expectedHandle); + std::int32_t status = static_cast(untouchedStatus); + std::int32_t result = untouchedResult; + Require(sceShellCoreUtilActivateGetStatus(0, nullptr, &result) == shellInvalidArgument && result == untouchedResult); + Require(sceShellCoreUtilActivateGetStatus(0, &status, nullptr) == shellInvalidArgument && status == static_cast(untouchedStatus)); + Require(sceShellCoreUtilActivateGetStatus(0, &status, &result) == 0 && status == 2 && result == 0); + Require(sceShellCoreUtilActivateIsActivated(7, nullptr) == shellInvalidArgument); + bool activated = false; + Require(sceShellCoreUtilActivateIsActivated(-1, &activated) == 0 && activated); + for (std::intptr_t value : {std::intptr_t{0}, handle, std::intptr_t{-1}}) { + Require(sceShellCoreUtilActivateStart(value) == 0); + Require(sceShellCoreUtilActivateStartAsync(value) == 0); + Require(sceShellCoreUtilActivateAbort(value) == 0); + Require(sceShellCoreUtilActivateTerm(value) == 0); + } + Require(sceShellCoreUtilActivateRecordActivation() == 0); +} + +void TestCodec(const Codec& codec, std::uintptr_t expectedHandle) { + Require(codec.init(nullptr) == invalidArgument); + std::uintptr_t handle = untouchedHandle; + Require(codec.init(&handle) == 0 && handle == expectedHandle); + Require(codec.isActivated(nullptr) == invalidArgument); + std::uint8_t activated = untouchedFlag; + Require(codec.isActivated(&activated) == 0 && activated == 1); + std::uint32_t status = untouchedStatus; + std::int32_t result = untouchedResult; + Require(codec.getStatus(0, &status, &result) == invalidArgument); + Require(codec.getStatus(handle, nullptr, &result) == invalidArgument); + Require(codec.getStatus(handle, &status, nullptr) == invalidArgument); + Require(status == untouchedStatus && result == untouchedResult); + for (auto call : {codec.start, codec.abort, codec.term}) { + Require(call(0) == invalidArgument); + Require(call(handle) == 0); + Require(call(untouchedHandle) == 0); + } + Require(codec.getStatus(handle, &status, &result) == 0 && status == 2 && result == 0); + Require(codec.getStatus(untouchedHandle, &status, &result) == 0 && status == 2 && result == 0); +} + +} + +int main() { + TestCodec({sceSystemServiceActivateHevcInit, sceSystemServiceActivateHevcIsActivated, sceSystemServiceActivateHevcStart, + sceSystemServiceActivateHevcGetStatus, sceSystemServiceActivateHevcAbort, sceSystemServiceActivateHevcTerm}, 0x1000); + TestCodec({sceSystemServiceActivateHevcSoftInit, sceSystemServiceActivateHevcSoftIsActivated, sceSystemServiceActivateHevcSoftStart, + sceSystemServiceActivateHevcSoftGetStatus, sceSystemServiceActivateHevcSoftAbort, sceSystemServiceActivateHevcSoftTerm}, 0x1004); + TestCodec({sceSystemServiceActivateMpeg2Init, sceSystemServiceActivateMpeg2IsActivated, sceSystemServiceActivateMpeg2Start, + sceSystemServiceActivateMpeg2GetStatus, sceSystemServiceActivateMpeg2Abort, sceSystemServiceActivateMpeg2Term}, 0x1008); + TestShellCalls(0x100C); + Require(sceSystemServiceActivateHevc() == 0); + Require(sceSystemServiceActivateHevcSoft() == 0); + return 0; +} diff --git a/docs/dev/debt/libSceSystemService.md b/docs/dev/debt/libSceSystemService.md index baf22a6271..e117495079 100644 --- a/docs/dev/debt/libSceSystemService.md +++ b/docs/dev/debt/libSceSystemService.md @@ -2,10 +2,6 @@ ### Silent stubs -- [sceShellCoreUtilActivateAbort, sceShellCoreUtilActivateStart, sceShellCoreUtilActivateStartAsync, sceShellCoreUtilActivateTerm](../../../core/libs/prx/libSceSystemService/PendingExports.cpp) return zero with the recovered handle argument. Activation service state is not modeled. - -- [sceShellCoreUtilActivateRecordActivation](../../../core/libs/prx/libSceSystemService/PendingExports.cpp) - activation service state is not modeled; this parameterless export returns zero without recording an activation. - - [libSceSystemService](../../../core/libs/prx/libSceSystemService/Export.cpp) - `sceSystemServicePowerTick`, `sceSystemServiceReportAbnormalTermination`, `sceSystemServiceDisableMusicPlayer`, `sceSystemServiceReenableMusicPlayer`, `sceSystemServiceDisableMediaPlay` and `sceSystemServiceReenableMediaPlay` return success and do nothing: the host has no auto power-off timer to reset, no system crash reporter to notify, no system music player to pause or resume and no system media playback to disable. PPSA01521 calls `sceSystemServiceDisableMediaPlay` during start-up, and its wrappers for both pass no arguments - [sceSystemServiceLaunchWebBrowser](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - AnyPS5 launches no web browser, so the call fails and nothing is shown. RommPS PPSA76677 then tells the player to scan its QR code instead - [sceSystemServiceShowControllerSettings](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - returns success without showing the controller settings: AnyPS5 has no system UI to open. Cat Quest III (PPSA15065) imports it @@ -145,7 +141,8 @@ - [ShellCoreUtil dialog parameters](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): the 24-byte error-dialog parameter contains a message pointer at offset zero, a null-terminated vector of string arguments at offset eight and a byte flag at offset 16. Field offsets and size are asserted; the flag meaning remains unresolved. Start/stop streaming-game and unmount requests take strings. Bus-transfer testing writes one byte after a string input. -- [SystemService activation and process signatures](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): activation wrappers validate a machine-sized handle and expose 32-bit status/result outputs or a byte activation flag. Shell activation initialization also receives a type selector; managed callers confirm the machine-sized handle consumed by constant-return start/abort/terminate exports. SystemService local-process parameters differ from LncUtil parameters and are kept as distinct types; general creation reads through offset 71, JVM/PS2 creation reads two 32-bit fields at offsets four and eight. Install status writes 12 bytes and application status writes 16 bytes; their remaining field meanings are unresolved. +- [Codec activation](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): the `sceShellCoreUtilActivate*` calls and the HEVC, HEVC soft and MPEG-2 `sceSystemServiceActivate*` wrappers follow firmware 13.42's libSceSystemService, read from disassembly, not measured on a console. There they ask no other process: initialization hands out handles counted from 0x1000 in steps of 4, shared by every codec; the status is always 2 with result 0; a codec always reads as activated; start, abort, terminate and recording return 0. The wrappers return 0x80A10003 for a null handle or output, the shell calls 0x80E30005 for a null output. The name of status 2 and whether older firmware asked the shell are unknown. +- [SystemService process signatures](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): SystemService local-process parameters differ from LncUtil parameters and are kept as distinct types; general creation reads through offset 71, JVM/PS2 creation reads two 32-bit fields at offsets four and eight. Install status writes 12 bytes and application status writes 16 bytes; their remaining field meanings are unresolved. - [SystemService query payloads](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): daemon events write 12 bytes; local-process entries occupy 36 bytes, consisting of a 32-bit value and 32-byte name. PS-button extraction reads a 16-byte event and rearranges its fields into a distinct 16-byte output; semantic names remain unresolved. Privacy definition/version queries take string keys and distinct output types; their layouts remain unresolved. Workaround information writes four bytes at offset zero and 16 bytes at offset eight. From 2ad46833a754f7fdb267bc1fcd1bee8d012caac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0?= Date: Fri, 30 Oct 2026 15:19:05 +0100 Subject: [PATCH 23/27] docs(libSceSystemService): drop the activation entry the codec answers make false Co-Authored-By: Claude Sonnet 5.5 --- docs/dev/debt/libSceSystemService.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/dev/debt/libSceSystemService.md b/docs/dev/debt/libSceSystemService.md index e117495079..c3730089a9 100644 --- a/docs/dev/debt/libSceSystemService.md +++ b/docs/dev/debt/libSceSystemService.md @@ -13,8 +13,6 @@ ### Unknown function info -- [sceShellCoreUtilActivateGetStatus, sceShellCoreUtilActivateInit, sceShellCoreUtilActivateIsActivated](../../../core/libs/prx/libSceSystemService/PendingExports.cpp) - the activation contract is unresolved: handle values and lifecycle, the activation flag, status/result fields, null-output errors and repeated initialization are unverified. All calls throw `NotImplemented_nid_no_patch`, including calls with null outputs. - - [sceSystemServiceAddLocalProcessForPsmKit, sceSystemServiceKillLocalProcessForPsmKit](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - AnyPS5 runs one process and starts or stops no other, so both throw without reading their arguments. The parameters follow Forever Launcher's own firmware 5.50 probes (`ps5/probes/localproc` in [bizkut/ps5-forever](https://github.com/bizkut/ps5-forever)): a path, a null-terminated argv and a 0x90-byte parameter block in, a handle out; the kill takes that handle. Their results on the console are unknown. Forever Launcher (PPSA99030) imports both and stops at load without them. - [sceSystemServiceGetAppId, sceSystemServiceGetAppTitleId, sceLncUtilGetAppTitleId](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the console's results for a title ID or app ID other than the running title are unknown, and so are its errors for null buffers, so those calls throw. A null title ID, or one whose length isn't 9, makes `sceSystemServiceGetAppId` return -1 before any request, as firmware 13.42's `libSceSystemService.sprx` does ([emulator run](https://github.com/boykopovar/AnyPS5/pull/10801#issuecomment-6403075212)); otherwise it compares the title ID exactly with the one in `param.json` or `param.sfo`. The title ID buffer is 10 bytes, so a running title ID of 10 or more characters throws too. The signatures follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/SystemService/SystemService.cs#L236-L260), except that `sceLncUtilGetAppTitleId` returns an `int`, as declared in the pending exports, and 0 on success like `sceSystemServiceGetAppTitleId`; SharpProspero names `ErrorNoAppInfo` (0x80A10009) only for `sceSystemServiceGetAppFocusedAppStatus`. - [sceLncUtilGetAppId, sceLncUtilIsAppLaunched, sceLncUtilGetAppIdListOfBigApp, sceSystemServiceGetAppIdListOfBigApp](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - only the running title answers. `sceLncUtilGetAppId` returns its app ID as `sceSystemServiceGetAppId` does, `sceLncUtilIsAppLaunched` reports it launched, and both big-app lists write its app ID as their only entry with a count of 1, because AnyPS5 runs one title and cannot start another. A null or non-9-character title ID returns -1 from `sceLncUtilGetAppId` and 0x80940005 from `sceLncUtilIsAppLaunched`, and a null output, list or count returns 0x80940005, before any request, as firmware 13.42's `libSceSystemService.sprx` does ([emulator run](https://github.com/boykopovar/AnyPS5/pull/10801#issuecomment-6403075212)). These calls answer as if the LncUtil client were initialized; for valid arguments without it, 13.42 returns 0x80940001. Another 9-character title ID and a list capacity of 0 throw: the module passes them on as a request, so whether the console reports another installed title as not launched, and what it answers for capacity 0, are unknown. The signatures are the ones the pending exports recovered From 5d1a05bb369ad3ebee4bb8d18e4125af1b27dbe7 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Fri, 30 Oct 2026 14:20:50 +0000 Subject: [PATCH 24/27] feat(libSceFontGs): add export placeholders AI-assisted: yes --- core/libs/prx/libSceFontGs/Export.cpp | 29 +++++++++++++++++++ .../libSceFontGs/include/InventoryExports.hpp | 10 +++++++ core/libs/prx/libSceFontGs/libSceFontGs.hpp | 2 ++ docs/dev/debt/libSceFontGs.md | 5 ++++ 4 files changed, 46 insertions(+) create mode 100644 core/libs/prx/libSceFontGs/include/InventoryExports.hpp create mode 100644 docs/dev/debt/libSceFontGs.md diff --git a/core/libs/prx/libSceFontGs/Export.cpp b/core/libs/prx/libSceFontGs/Export.cpp index d81f4a5e32..c625cc94a1 100644 --- a/core/libs/prx/libSceFontGs/Export.cpp +++ b/core/libs/prx/libSceFontGs/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceFontGs/include/InventoryExports.hpp" #include #include #include "SceTypes.hpp" @@ -7,4 +8,32 @@ extern "C" { APS5_DUMMY_FUN +int APS5_VABI sceFontGraphicsGnmSurfaceInit(void) { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFontSelectGraphicsGnm(void) { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFontGsUnknown_DI4LrTUrD04_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("DI4LrTUrD04", sceFontGsUnknown_DI4LrTUrD04_nid_no_patch); + +int APS5_VABI sceFontGsUnknown_NtbMKwRg6qg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("NtbMKwRg6qg", sceFontGsUnknown_NtbMKwRg6qg_nid_no_patch); + +int APS5_VABI sceFontGsUnknown_UsBLoyXsQZs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("UsBLoyXsQZs", sceFontGsUnknown_UsBLoyXsQZs_nid_no_patch); + +int APS5_VABI sceFontGsUnknown_uksHKVEsxu4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("uksHKVEsxu4", sceFontGsUnknown_uksHKVEsxu4_nid_no_patch); + } diff --git a/core/libs/prx/libSceFontGs/include/InventoryExports.hpp b/core/libs/prx/libSceFontGs/include/InventoryExports.hpp new file mode 100644 index 0000000000..c3ae4d3351 --- /dev/null +++ b/core/libs/prx/libSceFontGs/include/InventoryExports.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceFontGraphicsGnmSurfaceInit(void); +int APS5_VABI sceFontSelectGraphicsGnm(void); + +} diff --git a/core/libs/prx/libSceFontGs/libSceFontGs.hpp b/core/libs/prx/libSceFontGs/libSceFontGs.hpp index 6f70f09bee..3ec3b9d1c6 100644 --- a/core/libs/prx/libSceFontGs/libSceFontGs.hpp +++ b/core/libs/prx/libSceFontGs/libSceFontGs.hpp @@ -1 +1,3 @@ #pragma once + +#include "prx/libSceFontGs/include/InventoryExports.hpp" diff --git a/docs/dev/debt/libSceFontGs.md b/docs/dev/debt/libSceFontGs.md new file mode 100644 index 0000000000..5405d4e1ad --- /dev/null +++ b/docs/dev/debt/libSceFontGs.md @@ -0,0 +1,5 @@ +# libSceFontGs technical debt + +### Unknown function info + +- [`sceFontGraphicsGnmSurfaceInit`, `sceFontSelectGraphicsGnm` and four unnamed exports](../../../core/libs/prx/libSceFontGs/Export.cpp) (libSceFontGs) - parameter lists, return types and behaviour are unverified, so every entry throws through `NotImplemented_nid_no_patch`; the parameterless `int` declarations only resolve imports. The [PS5 13.60 export inventory](https://github.com/xdCloudy/PS5-13.60-Function-Stub-Library/blob/226383a7fa4b1275d6cebf61eb1c1e7ca7b58bcb/data/firmware-13.60/libscefontgs-b42f55c1c1.jsonl) names two of them and lists `DI4LrTUrD04`, `NtbMKwRg6qg`, `UsBLoyXsQZs` and `uksHKVEsxu4` without names; the pinned [module map](https://github.com/boykopovar/ps5-nid-map/blob/cc1d586fddab576a520f226da6b2632b031e6d09/data/Modules/libSceFontGs.inc) assigns all six to `libSceFontGs`. The six exports shared with `libSceFontGs.native` are left to that library. From 7b007018247b1f57cbfd2ee6a313b5e28b2dc7a0 Mon Sep 17 00:00:00 2001 From: DeveshB-1 Date: Fri, 30 Oct 2026 18:42:37 +0530 Subject: [PATCH 25/27] fix(libkernel_sys): return EFAULT from sceKernelGetCompiledSdkVersion for an unwritable store --- core/libs/prx/libkernel_sys/Export.cpp | 2 +- core/libs/tests/GuestKernelSysSdkVersion.cpp | 1 + docs/dev/debt/libkernel_sys.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index a26fd1347f..64af771c0f 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -350,7 +350,7 @@ extern "C" { int APS5_VABI sceKernelGetCompiledSdkVersion(std::int32_t* version) { if (version == nullptr) return SCE_KERNEL_ERROR_EINVAL; static const auto* parameters = static_cast(ApplicationProcessParameters_nid_no_patch()); - std::memcpy(version, parameters + 0x10, sizeof(*version)); + if (!GuestCopyOut_nid_no_patch(version, parameters + 0x10, sizeof(*version))) return SCE_KERNEL_ERROR_EFAULT; return 0; } diff --git a/core/libs/tests/GuestKernelSysSdkVersion.cpp b/core/libs/tests/GuestKernelSysSdkVersion.cpp index dc20a51ac8..0dc451e8f9 100644 --- a/core/libs/tests/GuestKernelSysSdkVersion.cpp +++ b/core/libs/tests/GuestKernelSysSdkVersion.cpp @@ -34,6 +34,7 @@ int main() { Require(ProcessParametersFixture::Install(process.data(), process.size()), "process parameters were not installed"); Require(sceKernelGetCompiledSdkVersion(nullptr) == static_cast(0x80020016), "null output is not EINVAL"); + Require(sceKernelGetCompiledSdkVersion(reinterpret_cast(0x10)) == static_cast(0x8002000e), "unwritable output is not EFAULT"); std::array output{-1, 0x5a5a5a5a}; Require(sceKernelGetCompiledSdkVersion(output.data()) == 0, "query failed"); Require(output[0] == 0x07008001, "version is not the 32-bit field at offset 0x10 of the process parameters"); diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index fc859388db..9a24071656 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -340,7 +340,7 @@ | `Unknown` | `zAloppPJxy0` | - [sceKernelGetHwModelName, sceKernelIsProspero](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - both throw. 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), and 0 from `sceKernelIsProspero()`. The model string belongs to one console and the buffer size and null behaviour are not known, and 0 from `sceKernelIsProspero` on a PS5 suggests it answers something other than the model, so neither is 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`, the one [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) reads and the one [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/615bcba5e21b82dc59588575f7ecbce8da09ca2c/src/emulator.cpp#L110-L138) (PS4, a single 64-bit `sdk_version` there) reads from the eboot. The `int (int32_t*)` signature and `EINVAL` (`0x80020016`) for a null output follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/615bcba5e21b82dc59588575f7ecbce8da09ca2c/src/core/libraries/kernel/process.cpp#L56-L62). Closes when: measured on a PS5 +- [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); unwritable pointers return `SCE_KERNEL_ERROR_EFAULT`. Closes when: measured on a PS5 - [scePthreadCondattrGetclock](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - the `int (const PthreadCondattr*, KernelClockid*)` signature and checks follow FreeBSD 11.4 libthr's [`_pthread_condattr_getclock`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libthr/thread/thr_condattr.c#L82-L90) with the error code of libkernel's `scePthreadCondattrSetclock`: a null or destroyed attribute returns `SCE_KERNEL_ERROR_EINVAL`, and a null clock pointer throws, since libthr writes through it unchecked. Console behaviour is unverified ### Functional From 7a5caa13708b1492e40737a6149cbe0c87554ccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0?= Date: Fri, 30 Oct 2026 15:37:36 +0100 Subject: [PATCH 26/27] docs(libkernel_sys): mark EFAULT for sceKernelGetCompiledSdkVersion as an unmeasured host-safety fallback --- 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 9a24071656..8466f8a45e 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -340,7 +340,7 @@ | `Unknown` | `zAloppPJxy0` | - [sceKernelGetHwModelName, sceKernelIsProspero](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - both throw. 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), and 0 from `sceKernelIsProspero()`. The model string belongs to one console and the buffer size and null behaviour are not known, and 0 from `sceKernelIsProspero` on a PS5 suggests it answers something other than the model, so neither is 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); unwritable pointers return `SCE_KERNEL_ERROR_EFAULT`. Closes when: measured on a PS5 +- [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) - the `int (const PthreadCondattr*, KernelClockid*)` signature and checks follow FreeBSD 11.4 libthr's [`_pthread_condattr_getclock`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libthr/thread/thr_condattr.c#L82-L90) with the error code of libkernel's `scePthreadCondattrSetclock`: a null or destroyed attribute returns `SCE_KERNEL_ERROR_EINVAL`, and a null clock pointer throws, since libthr writes through it unchecked. Console behaviour is unverified ### Functional From 5fbe7bc016c200883c43a1e0b4ca42ef8b7f4da4 Mon Sep 17 00:00:00 2001 From: boykopovar Date: Fri, 30 Oct 2026 21:17:55 +0300 Subject: [PATCH 27/27] fix(ci): increase release timeouts to one hour --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 20ba79446b..7a6bab95cd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,10 +29,10 @@ jobs: include: - platform: linux runner: ubuntu-latest - timeout: 30 + timeout: 60 - platform: windows runner: windows-latest - timeout: 45 + timeout: 60 runs-on: ${{ matrix.runner }} timeout-minutes: ${{ matrix.timeout }} defaults: