fix(net): return the console's errors from the memory pool stats calls

Measured on a PS5 (firmware 13.60):
- sceSslGetMemoryPoolStats returns 0x8095F007 for a null output, before
  the id check, and 0x8095F006 for an unknown or terminated context,
  zeroing the 32-byte output.
- sceHttp2GetMemoryPoolStats returns 0x817B1225 for a null output,
  before the id check, and 0x817B1100 for an unknown or terminated
  context, zeroing the 32-byte output.
- sceNetGetMemoryPoolStats checks the pool before the output, so an
  unknown pool with a null output returns EBADF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
hugodearaujo 2026-10-10 22:38:09 -03:00 committed by Adrià Franch
parent 65a4e8ef34
commit 7e5a34c58c
6 changed files with 120 additions and 8 deletions

View File

@ -189,6 +189,16 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceRudp>")
endif()
add_test_executable(guest_memory_pool_stats_tests tests/GuestMemoryPoolStats.cpp)
target_include_directories(guest_memory_pool_stats_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_memory_pool_stats_tests PRIVATE libSceSsl libSceHttp2 libSceNet libkernel libc)
configure_windows_unwind(guest_memory_pool_stats_tests)
add_test(NAME guest_memory_pool_stats COMMAND guest_memory_pool_stats_tests)
if(WIN32)
set_tests_properties(guest_memory_pool_stats PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceSsl>")
endif()
add_test_executable(guest_system_gesture_tests tests/GuestSystemGesture.cpp)
target_include_directories(guest_system_gesture_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_system_gesture_tests PRIVATE libSceSystemGesture libc)

View File

@ -1,5 +1,6 @@
#include <cstdint>
#include <cstddef>
#include <cstring>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/Equeue/Equeue.hpp"
@ -13,6 +14,8 @@
// No network is emulated: contexts, templates and requests can be created, but any request
// that would touch the network fails with the library's network error.
static constexpr int ERROR_NETWORK = static_cast<int>(0x80436063);
static constexpr int ERROR_INVALID_ID = static_cast<int>(0x817B1100);
static constexpr int ERROR_NULL_POINTER = static_cast<int>(0x817B1225);
static std::atomic<int> g_nextHandle{1};
static std::mutex g_poolsMutex;
static std::map<int, size_t> g_pools;
@ -256,10 +259,13 @@ int APS5_VABI sceHttp2AbortRequest(int req_id) {
}
int APS5_VABI sceHttp2GetMemoryPoolStats(int lib_http2_ctx_id, Http2MemoryPoolStats* stats) {
if (stats == nullptr) APS5_INVALID_ARG_EX;
if (stats == nullptr) return ERROR_NULL_POINTER;
std::lock_guard lock(g_poolsMutex);
const auto pool = g_pools.find(lib_http2_ctx_id);
if (pool == g_pools.end()) throw std::invalid_argument("sceHttp2GetMemoryPoolStats: unknown context");
if (pool == g_pools.end()) {
std::memset(stats, 0, sizeof(*stats));
return ERROR_INVALID_ID;
}
*stats = {pool->second, 0, 0, 0};
return 0;
}

View File

@ -912,14 +912,14 @@ int APS5_VABI sceNetEpollCreate(const char* name, int flags) {
}
int APS5_VABI sceNetGetMemoryPoolStats(int memid, NetMemoryPoolStats* stats) {
if (stats == nullptr) {
return fail(NET_EINVAL);
}
std::lock_guard lk(g_mutex);
const auto pool = g_pools.find(memid);
if (pool == g_pools.end()) {
return fail(NET_EBADF);
}
if (stats == nullptr) {
return fail(NET_EINVAL);
}
*stats = {pool->second, 0, 0};
return 0;
}

View File

@ -1,5 +1,6 @@
#include <cstdint>
#include <cstddef>
#include <cstring>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include <atomic>
@ -19,6 +20,8 @@ namespace {
constexpr int ERROR_NOT_FOUND = static_cast<int>(0x8095F004);
constexpr int ERROR_INVALID_ARG = static_cast<int>(0x8095177A);
constexpr int ERROR_INVALID_ID = static_cast<int>(0x8095F006);
constexpr int ERROR_INVALID_VALUE = static_cast<int>(0x8095F007);
struct SslData {
char* ptr;
@ -115,10 +118,13 @@ int APS5_VABI sceSslUnloadCert(int sslCtxId) {
}
int APS5_VABI sceSslGetMemoryPoolStats(int ssl_ctx_id, SslMemoryPoolStats* stats) {
if (stats == nullptr) APS5_INVALID_ARG_EX;
if (stats == nullptr) return ERROR_INVALID_VALUE;
std::lock_guard lock(g_poolsMutex);
const auto pool = g_pools.find(ssl_ctx_id);
if (pool == g_pools.end()) throw std::invalid_argument("sceSslGetMemoryPoolStats: unknown context");
if (pool == g_pools.end()) {
std::memset(stats, 0, sizeof(*stats));
return ERROR_INVALID_ID;
}
*stats = {static_cast<size_t>(pool->second), 0, 0, 0};
return 0;
}

View File

@ -0,0 +1,90 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
struct PoolStats {
std::size_t pool_size;
std::size_t max_inuse_size;
std::size_t current_inuse_size;
std::int32_t reserved;
};
struct NetPoolStats {
std::size_t pool_size;
std::size_t max_inuse_size;
std::size_t current_inuse_size;
};
extern "C" {
int APS5_VABI sceSslInit_nid_postfix(std::size_t);
int APS5_VABI sceSslTerm_nid_postfix(int);
int APS5_VABI sceSslGetMemoryPoolStats(int, void*);
int APS5_VABI sceHttp2Init(int, int, std::size_t, int);
int APS5_VABI sceHttp2Term(int);
int APS5_VABI sceHttp2GetMemoryPoolStats(int, void*);
int APS5_VABI sceNetPoolCreate(const char*, int, int);
int APS5_VABI sceNetPoolDestroy(int);
int APS5_VABI sceNetGetMemoryPoolStats(int, void*);
}
static void Require(bool value) { if (!value) std::abort(); }
using StatsCall = int(APS5_VABI*)(int, void*);
static void CheckRejected(StatsCall call, int id, int expected) {
std::array<unsigned char, 0x40> buffer;
buffer.fill(0x5a);
Require(call(id, buffer.data()) == expected);
for (std::size_t i = 0; i < sizeof(PoolStats); ++i) Require(buffer[i] == 0);
for (std::size_t i = sizeof(PoolStats); i < buffer.size(); ++i) Require(buffer[i] == 0x5a);
}
int main() {
constexpr int sslInvalidId = static_cast<int>(0x8095F006);
constexpr int sslInvalidValue = static_cast<int>(0x8095F007);
constexpr int http2InvalidId = static_cast<int>(0x817B1100);
constexpr int http2NullPointer = static_cast<int>(0x817B1225);
constexpr int netBadDescriptor = static_cast<int>(0x80410109);
constexpr int netInvalid = static_cast<int>(0x80410116);
static_assert(sizeof(PoolStats) == 0x20 && sizeof(NetPoolStats) == 0x18);
const int ssl = sceSslInit_nid_postfix(0x40000);
Require(ssl > 0);
PoolStats stats{};
Require(sceSslGetMemoryPoolStats(ssl, &stats) == 0 && stats.pool_size == 0x40000);
Require(sceSslGetMemoryPoolStats(ssl, nullptr) == sslInvalidValue);
Require(sceSslGetMemoryPoolStats(12345, nullptr) == sslInvalidValue);
CheckRejected(sceSslGetMemoryPoolStats, 12345, sslInvalidId);
CheckRejected(sceSslGetMemoryPoolStats, -1, sslInvalidId);
const int http2 = sceHttp2Init(1, ssl, 0x40000, 1);
Require(http2 > 0);
Require(sceHttp2GetMemoryPoolStats(http2, &stats) == 0 && stats.pool_size == 0x40000);
Require(sceHttp2GetMemoryPoolStats(http2, nullptr) == http2NullPointer);
Require(sceHttp2GetMemoryPoolStats(12345, nullptr) == http2NullPointer);
CheckRejected(sceHttp2GetMemoryPoolStats, 12345, http2InvalidId);
CheckRejected(sceHttp2GetMemoryPoolStats, -1, http2InvalidId);
Require(sceHttp2Term(http2) == 0);
CheckRejected(sceHttp2GetMemoryPoolStats, http2, http2InvalidId);
Require(sceSslTerm_nid_postfix(ssl) == 0);
CheckRejected(sceSslGetMemoryPoolStats, ssl, sslInvalidId);
const int pool = sceNetPoolCreate("stats", 0x20000, 0);
Require(pool > 0);
NetPoolStats netStats{};
Require(sceNetGetMemoryPoolStats(pool, &netStats) == 0 && netStats.pool_size == 0x20000);
Require(sceNetGetMemoryPoolStats(pool, nullptr) == netInvalid);
Require(sceNetGetMemoryPoolStats(12345, nullptr) == netBadDescriptor);
std::array<unsigned char, 0x20> untouched;
untouched.fill(0x5a);
auto buffer = untouched;
Require(sceNetGetMemoryPoolStats(12345, buffer.data()) == netBadDescriptor && buffer == untouched);
Require(sceNetPoolDestroy(pool) == 0);
Require(sceNetGetMemoryPoolStats(pool, nullptr) == netBadDescriptor);
return 0;
}

View File

@ -212,7 +212,7 @@ Throughout the project, every function at every stage either **does exactly what
- [scePadSetAngularVelocityBiasCorrectionState](../../core/libs/prx/libScePad/Export.cpp) (libScePad) - inferred signature `(int handle, bool enabled)` follows `scePadSetTiltCorrectionState` and `scePadSetAngularVelocityDeadbandState`; an invalid handle returns `PAD_ERROR_INVALID_HANDLE`, disabling is a no-op, and enabling throws because bias correction is not modelled
- [sceKernelGetOperationMode](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - the signature `(int* mode, int* submode)` comes from PPSA12544, which logs both values after the call; the values are unknown and 0 is reported for both, as in the [prosper](https://github.com/mattias800/prosper/pull/4153) reimplementation. Null outputs throw
- Font, Http2, Net, Ssl, SystemService and libkernel exports imported by PPSA12544 and added without an implementation have unknown signatures
- [sceHttp2GetMemoryPoolStats, sceSslGetMemoryPoolStats, sceNetGetMemoryPoolStats](../../core/libs/prx/libSceNet/Export.cpp) (libSceHttp2, libSceSsl, libSceNet) - signatures `(int id, stats*)` and the statistics layouts (pool size, peak and current use, and for Http2 and Ssl a 32-bit reserved field) follow the PS4 SDK's `SceHttp2MemoryPoolStats`, `SceSslMemoryPoolStats` and `SceNetMemoryPoolStats`, not confirmed for the PS5. The pool size is the one given at init or pool creation; nothing is allocated from these pools, so the peak and current use are 0. Net returns `EBADF` for an unknown pool and `EINVAL` for a null output, as its other pool calls do; for Http2 and Ssl the error codes are unknown, so both cases throw
- [sceHttp2GetMemoryPoolStats, sceSslGetMemoryPoolStats, sceNetGetMemoryPoolStats](../../core/libs/prx/libSceNet/Export.cpp) (libSceHttp2, libSceSsl, libSceNet) - the signatures `(int id, stats*)`, the layouts (pool size, peak and current use, and for Http2 and Ssl a 32-bit reserved field) and the error codes were measured on a PS5 (firmware 13.60). The pool size is the one given at init or pool creation; nothing is allocated from these pools, so the peak and current use are 0, where the console already reports use right after initialization (0x15C0 bytes for Ssl, 0x1640 for Http2 and 4 for a Net pool)
- [sceSslGetCaList, sceSslFreeCaList, sceSslUnloadCert](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - signatures and the 16-byte `SceSslCaList` layout (an array of certificate handles and a 32-bit count) follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http.cs) and shadPS4's `OrbisSslCaList`, not confirmed for the PS5. On the console the list holds the certificate authorities the SSL service has; [shadPS4](https://github.com/shadps4-emu/shadPS4/pull/4104) returns a placeholder certificate from `sceSslGetCaCerts` because Rise of the Tomb Raider fails its initialization checks without one. No root certificate bundle is shipped and no loaded certificate is kept, so here a context holds none: `sceSslGetCaList` clears the list and returns `NOT_FOUND`, as `sceSslGetCaCerts` does, `sceSslFreeCaList` accepts only that cleared list, and `sceSslUnloadCert` (imported by PPSA15508) has nothing to drop and is assumed to return 0. A null list returns `0x8095177A`, which `sceSslGetCaCerts` and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/59183dbfa81ed7efdff6069177fed50de0552725/src/libs/network.cpp) return for a null argument (shadPS4, PS4, returns `0x8095F007`). An unknown context throws (KytyPS5 returns `SSL_ERROR_INVALID_ID`, `0x8095F006`, from `sceSslGetCaCerts`), and so does a list `sceSslGetCaList` did not return
- [sceHttp2SetResolveRetry](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - signature `(int id, int32_t retry)` from libSceHttp's `sceHttpSetResolveRetry`; the value is accepted and ignored like the other Http2 settings, since no request reaches the network
- [sceHttp2WebSocketCreateRequest, sceHttp2WebSocketCloseAsync, sceHttp2WebSocketSendTextMessageAsync, sceHttp2WebSocketSendDataMessageAsync](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - imported by PPSA16064 and not called during its start-up; unknown signatures