Merge pull request #12808 from edubox/feat/npcpp-common-binary

feat(libSceNpCppWebApi): implement Common::Binary as the module does
This commit is contained in:
Dean authored and GitHub committed 2026-11-04 18:34:45 +13:00
commit b8efa6292a
5 files changed
+233 -9

No files matched your search

@@ -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:$<TARGET_FILE_DIR:libc>")
endif()
endif()
+86 -7
View File
@@ -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<int>(0x80553503);
static constexpr int BinaryOutOfMemory = static_cast<int>(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<std::size_t>(size));
if (buffer == nullptr) return BinaryOutOfMemory;
std::memcpy(buffer, data, static_cast<std::size_t>(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::uint8_t*>(std::malloc(static_cast<std::size_t>(total)));
if (buffer == nullptr) return BinaryOutOfMemory;
if (self->size != 0) std::memcpy(buffer, self->buffer, static_cast<std::size_t>(self->size));
std::memcpy(buffer + static_cast<std::size_t>(self->size), data, static_cast<std::size_t>(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<std::int32_t>(self->referenceCount).fetch_add(1) + 1;
}
std::int32_t APS5_VABI _ZN3sce2Np9CppWebApi6Common6Binary21intrusive_ptr_sub_refEPS3_(CommonBinary* self) {
return std::atomic_ref<std::int32_t>(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);
}
@@ -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);
+122
View File
@@ -0,0 +1,122 @@
#include "prx/libSceNpCppWebApi/include/Api.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <source_location>
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<int>(0x80553503);
constexpr unsigned char kFill = 0xA5;
struct Storage {
alignas(8) unsigned char bytes[kBinarySize + kGuardSize];
};
template <typename TValue>
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<CommonBinary*>(storage.bytes), &libContext);
Require(Load<std::int32_t>(storage, kReferenceCountOffset) == 0);
Require(Load<void*>(storage, kBufferOffset) == nullptr);
Require(Load<std::uint64_t>(storage, kDataSizeOffset) == 0);
Require(Load<void*>(storage, kLibContextOffset) == &libContext);
Require(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast<const CommonBinary*>(storage.bytes)) == nullptr);
Require(_ZNK3sce2Np9CppWebApi6Common6Binary4sizeEv(reinterpret_cast<const CommonBinary*>(storage.bytes)) == 0);
Require(GuardIntact(storage));
_ZN3sce2Np9CppWebApi6Common6BinaryD1Ev(reinterpret_cast<CommonBinary*>(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<CommonBinary*>(storage.bytes);
_ZN3sce2Np9CppWebApi6Common6BinaryC2EPNS2_10LibContextE(binary, &libContext);
Require(_ZN3sce2Np9CppWebApi6Common6Binary9setBinaryEPKvm(binary, "abc", 3) == 0);
Require(Load<std::uint64_t>(storage, kDataSizeOffset) == 3);
Require(std::memcmp(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast<const CommonBinary*>(storage.bytes)), "abc", 3) == 0);
Require(_ZNK3sce2Np9CppWebApi6Common6Binary4sizeEv(reinterpret_cast<const CommonBinary*>(storage.bytes)) == 3);
Require(_ZN3sce2Np9CppWebApi6Common6Binary12appendBinaryEPKvm(binary, "de", 2) == 0);
Require(Load<std::uint64_t>(storage, kDataSizeOffset) == 5);
Require(std::memcmp(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast<const CommonBinary*>(storage.bytes)), "abcde", 5) == 0);
Require(_ZN3sce2Np9CppWebApi6Common6Binary12appendBinaryEPKvm(binary, nullptr, 1) == kInvalidArgument);
Require(Load<std::uint64_t>(storage, kDataSizeOffset) == 5);
Require(std::memcmp(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast<const CommonBinary*>(storage.bytes)), "abcde", 5) == 0);
Require(_ZN3sce2Np9CppWebApi6Common6Binary9setBinaryEPKvm(binary, nullptr, 1) == kInvalidArgument);
Require(Load<std::uint64_t>(storage, kDataSizeOffset) == 5);
Require(std::memcmp(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast<const CommonBinary*>(storage.bytes)), "abcde", 5) == 0);
Require(_ZN3sce2Np9CppWebApi6Common6Binary9setBinaryEPKvm(binary, nullptr, 0) == 0);
Require(_ZN3sce2Np9CppWebApi6Common6Binary12appendBinaryEPKvm(binary, "xy", 2) == 0);
Require(Load<std::uint64_t>(storage, kDataSizeOffset) == 2);
Require(std::memcmp(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast<const CommonBinary*>(storage.bytes)), "xy", 2) == 0);
Require(_ZN3sce2Np9CppWebApi6Common6Binary9setBinaryEPKvm(binary, _ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast<const CommonBinary*>(storage.bytes)), 2) == 0);
Require(Load<std::uint64_t>(storage, kDataSizeOffset) == 2);
Require(std::memcmp(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast<const CommonBinary*>(storage.bytes)), "xy", 2) == 0);
Require(_ZN3sce2Np9CppWebApi6Common6Binary12appendBinaryEPKvm(binary, _ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast<const CommonBinary*>(storage.bytes)), 2) == 0);
Require(Load<std::uint64_t>(storage, kDataSizeOffset) == 4);
Require(std::memcmp(_ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(reinterpret_cast<const CommonBinary*>(storage.bytes)), "xyxy", 4) == 0);
Require(Load<std::int32_t>(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<std::int32_t>(storage, kReferenceCountOffset) == 1);
_ZN3sce2Np9CppWebApi6Common6Binary5clearEv(binary);
Require(Load<void*>(storage, kBufferOffset) == nullptr);
Require(Load<std::uint64_t>(storage, kDataSizeOffset) == 0);
Require(GuardIntact(storage));
_ZN3sce2Np9CppWebApi6Common6BinaryD2Ev(binary);
Require(Load<void*>(storage, kBufferOffset) == nullptr);
Require(Load<std::uint64_t>(storage, kDataSizeOffset) == 0);
Require(GuardIntact(storage));
}
}
int main() {
TestConstructedBinaryIsEmpty();
TestSetAndAppend();
return 0;
}
+1
View File
@@ -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<String>` 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<String>`, counting the reference at 0x38, which the `Vector<String>` 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<String>::begin`/`end` and `Iterator<String>` 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<UserProfile::V1::BasicPresence>` other than `size` throw: a failed request never produces a response