diff --git a/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt index 7079b95d84..cd3460bd81 100644 --- a/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt +++ b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt @@ -40,9 +40,15 @@ if(BUILD_TESTING) add_test(NAME guest_np_cpp_web_api_alias_intrusive_vector COMMAND guest_np_cpp_web_api_intrusive_ptr_tests alias-vector) set_tests_properties(guest_np_cpp_web_api_alias_append guest_np_cpp_web_api_alias_string_vector guest_np_cpp_web_api_alias_intrusive_vector PROPERTIES TIMEOUT 30) + add_test_executable(guest_np_cpp_web_api_binary_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpCppWebApiBinary.cpp) + target_include_directories(guest_np_cpp_web_api_binary_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_cpp_web_api_binary_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_binary_tests) + add_test(NAME guest_np_cpp_web_api_binary COMMAND guest_np_cpp_web_api_binary_tests) if(WIN32) set_tests_properties(guest_np_cpp_web_api_string guest_np_cpp_web_api_alias_append guest_np_cpp_web_api_alias_string_vector guest_np_cpp_web_api_alias_intrusive_vector + guest_np_cpp_web_api_binary PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp index 16b152f9b1..1ccf71c286 100644 --- a/core/libs/prx/libSceNpCppWebApi/Export.cpp +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -1627,16 +1627,95 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrIN return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } -std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary4sizeEv(const void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); +static constexpr int BinaryInvalidArgument = static_cast(0x80553503); +static constexpr int BinaryOutOfMemory = static_cast(0x80553504); + +static void ResetBinary(CommonBinary* self) { + std::free(self->buffer); + self->buffer = nullptr; + self->size = 0; +} + +static int SetBinary(CommonBinary* self, const void* data, const std::uint64_t size) { + if (size == 0) { + ResetBinary(self); + return 0; + } + if (data == nullptr) return BinaryInvalidArgument; + if (size > SIZE_MAX) return BinaryOutOfMemory; + void* buffer = std::malloc(static_cast(size)); + if (buffer == nullptr) return BinaryOutOfMemory; + std::memcpy(buffer, data, static_cast(size)); + std::free(self->buffer); + self->buffer = buffer; + self->size = size; return 0; } -const void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(const void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return nullptr; +static int AppendBinary(CommonBinary* self, const void* data, const std::uint64_t size) { + if (size == 0) return 0; + if (data == nullptr) return BinaryInvalidArgument; + if (size > UINT64_MAX - self->size) return BinaryOutOfMemory; + const std::uint64_t total = self->size + size; + if (total > SIZE_MAX) return BinaryOutOfMemory; + auto* buffer = static_cast(std::malloc(static_cast(total))); + if (buffer == nullptr) return BinaryOutOfMemory; + if (self->size != 0) std::memcpy(buffer, self->buffer, static_cast(self->size)); + std::memcpy(buffer + static_cast(self->size), data, static_cast(size)); + std::free(self->buffer); + self->buffer = buffer; + self->size = total; + return 0; +} + +static_assert(sizeof(CommonBinary) == 0x20 && offsetof(CommonBinary, buffer) == 0x08); +static_assert(offsetof(CommonBinary, size) == 0x10 && offsetof(CommonBinary, libContext) == 0x18); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6BinaryC1EPNS2_10LibContextE(CommonBinary* self, void* libContext) { + self->referenceCount = 0; + self->buffer = nullptr; + self->size = 0; + self->libContext = libContext; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6BinaryC2EPNS2_10LibContextE(CommonBinary* self, void* libContext) { + _ZN3sce2Np9CppWebApi6Common6BinaryC1EPNS2_10LibContextE(self, libContext); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6BinaryD1Ev(CommonBinary* self) { + ResetBinary(self); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6BinaryD2Ev(CommonBinary* self) { + ResetBinary(self); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6Binary5clearEv(CommonBinary* self) { + ResetBinary(self); +} + +std::int32_t APS5_VABI _ZN3sce2Np9CppWebApi6Common6Binary21intrusive_ptr_add_refEPS3_(CommonBinary* self) { + return std::atomic_ref(self->referenceCount).fetch_add(1) + 1; +} + +std::int32_t APS5_VABI _ZN3sce2Np9CppWebApi6Common6Binary21intrusive_ptr_sub_refEPS3_(CommonBinary* self) { + return std::atomic_ref(self->referenceCount).fetch_sub(1); +} + +std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary4sizeEv(const CommonBinary* self) { + return self->size; +} + +const void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(const CommonBinary* self) { + return self->buffer; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6Binary9setBinaryEPKvm(CommonBinary* self, const void* data, const std::uint64_t size) { + return SetBinary(self, data, size); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6Binary12appendBinaryEPKvm(CommonBinary* self, const void* data, const std::uint64_t size) { + return AppendBinary(self, data, size); } diff --git a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp index a648f82777..acd2bd99ac 100644 --- a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp +++ b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp @@ -2,6 +2,13 @@ #include "SceTypes.hpp" +struct CommonBinary { + std::int32_t referenceCount; + void* buffer; + std::uint64_t size; + void* libContext; +}; + struct CommonString { std::int32_t referenceCount; char* buffer; @@ -282,8 +289,17 @@ void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V1 void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv(const IntrusivePtr* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv(const IntrusivePtr* self); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE18getResponseHeadersERSB_(); -std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary4sizeEv(const void* self); -const void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(const void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6BinaryC1EPNS2_10LibContextE(CommonBinary* self, void* libContext); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6BinaryC2EPNS2_10LibContextE(CommonBinary* self, void* libContext); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6BinaryD1Ev(CommonBinary* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6BinaryD2Ev(CommonBinary* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6Binary5clearEv(CommonBinary* self); +std::int32_t APS5_VABI _ZN3sce2Np9CppWebApi6Common6Binary21intrusive_ptr_add_refEPS3_(CommonBinary* self); +std::int32_t APS5_VABI _ZN3sce2Np9CppWebApi6Common6Binary21intrusive_ptr_sub_refEPS3_(CommonBinary* self); +std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary4sizeEv(const CommonBinary* self); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(const CommonBinary* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6Binary9setBinaryEPKvm(CommonBinary* self, const void* data, std::uint64_t size); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6Binary12appendBinaryEPKvm(CommonBinary* self, const void* data, std::uint64_t size); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEEdeEv(const void* self); bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEEneERKS9_(const void* self, const void* other); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V115LocalizedStringEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); diff --git a/core/libs/tests/GuestNpCppWebApiBinary.cpp b/core/libs/tests/GuestNpCppWebApiBinary.cpp new file mode 100644 index 0000000000..beb8196da0 --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiBinary.cpp @@ -0,0 +1,122 @@ +#include "prx/libSceNpCppWebApi/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool value, std::source_location at = std::source_location::current()) { + if (!value) { + std::fprintf(stderr, "requirement failed at %s:%u\n", at.file_name(), at.line()); + std::abort(); + } +} + +constexpr std::size_t kBinarySize = 0x20; +constexpr std::size_t kGuardSize = 0x10; +constexpr std::size_t kReferenceCountOffset = 0x00; +constexpr std::size_t kBufferOffset = 0x08; +constexpr std::size_t kDataSizeOffset = 0x10; +constexpr std::size_t kLibContextOffset = 0x18; +constexpr int kInvalidArgument = static_cast(0x80553503); +constexpr unsigned char kFill = 0xA5; + +struct Storage { + alignas(8) unsigned char bytes[kBinarySize + kGuardSize]; +}; + +template +TValue Load(const Storage& storage, std::size_t offset) { + TValue value; + std::memcpy(&value, storage.bytes + offset, sizeof(value)); + return value; +} + +bool GuardIntact(const Storage& storage) { + for (std::size_t index = kBinarySize; index < sizeof(storage.bytes); ++index) { + if (storage.bytes[index] != kFill) return false; + } + return true; +} + +void TestConstructedBinaryIsEmpty() { + std::uint64_t libContext = 1; + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + _ZN3sce2Np9CppWebApi6Common6BinaryC1EPNS2_10LibContextE(reinterpret_cast(storage.bytes), &libContext); + Require(Load(storage, kReferenceCountOffset) == 0); + Require(Load(storage, kBufferOffset) == nullptr); + Require(Load(storage, kDataSizeOffset) == 0); + Require(Load(storage, kLibContextOffset) == &libContext); + Require(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast(storage.bytes)) == nullptr); + Require(_ZNK3sce2Np9CppWebApi6Common6Binary4sizeEv(reinterpret_cast(storage.bytes)) == 0); + Require(GuardIntact(storage)); + _ZN3sce2Np9CppWebApi6Common6BinaryD1Ev(reinterpret_cast(storage.bytes)); + Require(GuardIntact(storage)); +} + +void TestSetAndAppend() { + std::uint64_t libContext = 1; + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + auto* binary = reinterpret_cast(storage.bytes); + _ZN3sce2Np9CppWebApi6Common6BinaryC2EPNS2_10LibContextE(binary, &libContext); + + Require(_ZN3sce2Np9CppWebApi6Common6Binary9setBinaryEPKvm(binary, "abc", 3) == 0); + Require(Load(storage, kDataSizeOffset) == 3); + Require(std::memcmp(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast(storage.bytes)), "abc", 3) == 0); + Require(_ZNK3sce2Np9CppWebApi6Common6Binary4sizeEv(reinterpret_cast(storage.bytes)) == 3); + + Require(_ZN3sce2Np9CppWebApi6Common6Binary12appendBinaryEPKvm(binary, "de", 2) == 0); + Require(Load(storage, kDataSizeOffset) == 5); + Require(std::memcmp(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast(storage.bytes)), "abcde", 5) == 0); + + Require(_ZN3sce2Np9CppWebApi6Common6Binary12appendBinaryEPKvm(binary, nullptr, 1) == kInvalidArgument); + Require(Load(storage, kDataSizeOffset) == 5); + Require(std::memcmp(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast(storage.bytes)), "abcde", 5) == 0); + + Require(_ZN3sce2Np9CppWebApi6Common6Binary9setBinaryEPKvm(binary, nullptr, 1) == kInvalidArgument); + Require(Load(storage, kDataSizeOffset) == 5); + Require(std::memcmp(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast(storage.bytes)), "abcde", 5) == 0); + + Require(_ZN3sce2Np9CppWebApi6Common6Binary9setBinaryEPKvm(binary, nullptr, 0) == 0); + Require(_ZN3sce2Np9CppWebApi6Common6Binary12appendBinaryEPKvm(binary, "xy", 2) == 0); + Require(Load(storage, kDataSizeOffset) == 2); + Require(std::memcmp(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast(storage.bytes)), "xy", 2) == 0); + + Require(_ZN3sce2Np9CppWebApi6Common6Binary9setBinaryEPKvm(binary, _ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast(storage.bytes)), 2) == 0); + Require(Load(storage, kDataSizeOffset) == 2); + Require(std::memcmp(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast(storage.bytes)), "xy", 2) == 0); + + Require(_ZN3sce2Np9CppWebApi6Common6Binary12appendBinaryEPKvm(binary, _ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast(storage.bytes)), 2) == 0); + Require(Load(storage, kDataSizeOffset) == 4); + Require(std::memcmp(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast(storage.bytes)), "xyxy", 4) == 0); + + Require(Load(storage, kReferenceCountOffset) == 0); + Require(_ZN3sce2Np9CppWebApi6Common6Binary21intrusive_ptr_add_refEPS3_(binary) == 1); + Require(_ZN3sce2Np9CppWebApi6Common6Binary21intrusive_ptr_add_refEPS3_(binary) == 2); + Require(_ZN3sce2Np9CppWebApi6Common6Binary21intrusive_ptr_sub_refEPS3_(binary) == 2); + Require(Load(storage, kReferenceCountOffset) == 1); + + _ZN3sce2Np9CppWebApi6Common6Binary5clearEv(binary); + Require(Load(storage, kBufferOffset) == nullptr); + Require(Load(storage, kDataSizeOffset) == 0); + Require(GuardIntact(storage)); + + _ZN3sce2Np9CppWebApi6Common6BinaryD2Ev(binary); + Require(Load(storage, kBufferOffset) == nullptr); + Require(Load(storage, kDataSizeOffset) == 0); + Require(GuardIntact(storage)); +} + +} + +int main() { + TestConstructedBinaryIsEmpty(); + TestSetAndAppend(); + return 0; +} diff --git a/docs/dev/debt/libSceNpCppWebApi.md b/docs/dev/debt/libSceNpCppWebApi.md index 3ef2e6814c..7d03d4e382 100644 --- a/docs/dev/debt/libSceNpCppWebApi.md +++ b/docs/dev/debt/libSceNpCppWebApi.md @@ -18,6 +18,7 @@ - [sce::Np::CppWebApi::Common::IntrusivePtr](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x18-byte layout (object, deleter, `LibContext*`) and the offset of the 32-bit reference count in the pointee (0x00; in a `Vector`, 0x20 for `unsigned long`, 0x30 for `IntrusivePtr` and 0x38 for `String` elements) come from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws on the last reference of a pointer without a deleter: destroying the object needs its destructor and the `LibContext` allocator, which are not implemented - [sce::Np::CppWebApi::Common::String](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x20-byte layout (32-bit reference count, buffer at 0x08, used size including the terminator at 0x10, `LibContext*` at 0x18), the `+0x10` semantics and the `0x80553503`/`0x80553504` results follow the module bundled with PPSA21564 and PPSA23732 (builds J03093737/J03341606): `append`, `copyFrom`, `clear`, `size`, `length`, `operator==`, `setContext` and the intrusive reference operations match it. Allocation and release still use the host allocator instead of the `LibContext` allocator, so a title-provided buffer released through this destructor would use the wrong allocator; `find`, `replace` and `npos` remain unimplemented. `pushBack` into `Vector` stores a deep copy, so a destroyed temporary cannot invalidate the stored element; a title that byte-copies a `String` with a live buffer would still double-free, as nothing increments the reference count - [sce::Np::CppWebApi::Common::Vector](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671, whose dump ships no `libSceNpCppWebApi.prx`. A `Vector` handle is an `IntrusivePtr` over a shared body; bodies created by `pushBack` use a host layout with the reference count at 0x30. The pointer/deleter constructor adopts a title-provided `Vector`, counting the reference at 0x38, which the `Vector` constructor zeroes, as the module bundled with PPSA21564 does, and releases it through the stored deleter; `pushBack` on such a body throws, as its element layout is unknown. `Vector::begin`/`end` and `Iterator` operators walk host-created bodies and throw on title-provided ones; other `begin`, `end` and iterators, and `copyFrom`, throw: the only `Vector`s the title reads back come from failed transactions, which never produce one. `empty`, `size` and `operator[]` read host-created bodies and throw on title-provided ones +- [sce::Np::CppWebApi::Common::Binary](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x20-byte layout (32-bit reference count, buffer at 0x08, size at 0x10, `LibContext*` at 0x18) comes from the module bundled with PPSA21564/PPSA23732 (builds `J03093737`/`J03341606`); other SDK versions are not checked. `setBinary` and `appendBinary` use the host allocator instead of the `LibContext` allocator, reject a null source with a non-zero size where the module does not, keep the old contents when the input is invalid or allocation fails, and copy the input before releasing a buffer it aliases. A title that byte-copies a `Binary` with a live buffer would double-free, as nothing increments the reference count - [sce::Np::CppWebApi::SessionManager2V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671. Offline, the factories, `PlayerSessionsApi` calls and transaction `start`/`getResponse`/`readData` fail with `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`; parameter setters, `initialize`, `terminate`, constructors and destructors succeed without keeping state, as no request is ever sent. Response-body accessors (`getSessionId`, `getPlayerSessions`, `getInvitations`, the `IsSet` queries) throw: a failed request never produces a response object - [sce::Np::CppWebApi::Matchmaking::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA21564 (86 of 92) and PPSA05510, PPSA25256, PPSA17226 and PPSA08395 for the other six; behaviour comes from the module bundled with PPSA21564/PPSA23732 (builds J03093737/J03341606), not from a console. Offline, the four `Api` calls and four factories return `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`. The four `ParameterTo*` objects keep only an initialized flag: `initialize` returns 0x80553503 for a null context or string, 0x80553502 for an unset context, 0x8055350f when already initialized and 0x80553512 for an empty `SubmitTicketRequestBody`; `terminate` returns 0x80553510. `ParameterToGetTicket::setview` returns 0x80553502 uninitialized and 0x80553503 for a null string. Other setters succeed without keeping state. Response accessors throw: a failed request produces no response. 0x80553504 is never returned; the stub stores a static pointer. - [sce::Np::CppWebApi::UserProfile::V1, ProfanityFilter::V2, CommunicationRestrictionStatus::V3](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by an Epic Online Services module whose dump ships no `libSceNpCppWebApi.prx`. The reference-count offset 0x00 of `ResponseHeaderBase` and `WebApiFilterRequest` is assumed from the other objects, not measured. Offline, the five API calls, `WebApiFilterRequestFactory::create` and the transactions' `start`/`getResponseHeaders` fail with `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`. Response accessors, `ResponseHeaderBase::getHeaderValue` and the `Vector`/`Iterator` members over `unsigned long` and those over `IntrusivePtr` other than `size` throw: a failed request never produces a response