From db775f2208a03246374f94efd6db5004ba365a02 Mon Sep 17 00:00:00 2001 From: BunnyFl <166477956+BunnyFl@users.noreply.github.com> Date: Fri, 30 Oct 2026 04:46:26 +0100 Subject: [PATCH 1/2] fix(libSceNpUniversalDataSystem): size and describe events as the console does Co-Authored-By: Claude Opus 5.5 --- .../include/UdsEvent.hpp | 5 + .../libSceNpUniversalDataSystem/src/Event.cpp | 26 ++-- .../src/NpUniversalDataSystem.hpp | 4 +- .../src/UdsEvent.cpp | 134 ++++++++++++++++++ .../tests/Events.cpp | 112 +++++++++++++++ docs/dev/debt/libSceNpUniversalDataSystem.md | 1 + 6 files changed, 267 insertions(+), 15 deletions(-) diff --git a/core/libs/prx/libSceNpUniversalDataSystem/include/UdsEvent.hpp b/core/libs/prx/libSceNpUniversalDataSystem/include/UdsEvent.hpp index 35c8196824..ab014e8545 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/include/UdsEvent.hpp +++ b/core/libs/prx/libSceNpUniversalDataSystem/include/UdsEvent.hpp @@ -1,6 +1,7 @@ #ifndef CORE_LIBS_PRX_LIBSCENPUNIVERSALDATASYSTEM_INCLUDE_UDSEVENT_HPP #define CORE_LIBS_PRX_LIBSCENPUNIVERSALDATASYSTEM_INCLUDE_UDSEVENT_HPP +#include #include #include #include @@ -48,6 +49,8 @@ namespace Uds { inline constexpr std::string_view UnlockTrophyEvent = "_UnlockTrophy"; inline constexpr std::string_view UpdateTrophyProgressEvent = "_UpdateTrophyProgress"; inline constexpr std::string_view TrophyIdProperty = "_trophy_id"; +inline constexpr std::string_view EventNameKey = "eventName"; +inline constexpr std::string_view PropertiesKey = "properties"; enum class TrophyEventKind { None, Unlock, Progress, Malformed }; @@ -61,6 +64,8 @@ TrophyEvent RecognizeTrophyEvent(const NpUniversalDataSystemEvent& event); std::string Describe(const Value& value); std::string Describe(const NpUniversalDataSystemEventPropertyObject& object); std::string Describe(const NpUniversalDataSystemEvent& event); +std::size_t MessagePackSize(const NpUniversalDataSystemEvent& event); +std::string DebugString(const NpUniversalDataSystemEvent& event); NpTrophy2Event StatEvent(const NpUniversalDataSystemEvent& event); } diff --git a/core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp b/core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp index 12bcbd69c4..101ea88a94 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp +++ b/core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp @@ -1,4 +1,5 @@ #include "prx/libSceNpUniversalDataSystem/include/Api.hpp" +#include #include #include #include @@ -84,10 +85,9 @@ int APS5_VABI sceNpUniversalDataSystemPostEvent(int context, int handle, const v } int APS5_VABI sceNpUniversalDataSystemEventEstimateSize(const NpUniversalDataSystemEvent* event, size_t* size) { - if (event == nullptr || size == nullptr) { - APS5_INVALID_ARG_EX; - } - *size = NP_UNIVERSAL_DATA_SYSTEM_EMPTY_EVENT_SIZE; + if (event == nullptr) return SCE_NP_UNIVERSAL_DATA_SYSTEM_ERROR_NULL_EVENT; + if (size == nullptr) return SCE_NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + *size = Uds::MessagePackSize(*event); return SCE_NP_UNIVERSAL_DATA_SYSTEM_OK; } @@ -97,16 +97,18 @@ int APS5_VABI sceNpUniversalDataSystemEventToString( size_t buf_size, size_t* string_size) { - if (event == nullptr) { - APS5_INVALID_ARG_EX; + if (event == nullptr) return SCE_NP_UNIVERSAL_DATA_SYSTEM_ERROR_NULL_EVENT; + if (buf == nullptr) { + if (string_size == nullptr) return SCE_NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + buf_size = 0; } - const size_t json_len = std::strlen(NP_UNIVERSAL_DATA_SYSTEM_EMPTY_JSON) + 1; - if (string_size != nullptr) { - *string_size = json_len; - } - if (buf != nullptr && buf_size > 0) { - std::snprintf(buf, buf_size, "%s", NP_UNIVERSAL_DATA_SYSTEM_EMPTY_JSON); + const std::string text = Uds::DebugString(*event); + if (buf_size != 0) { + const size_t copied = std::min(text.size(), buf_size - 1); + std::memcpy(buf, text.data(), copied); + buf[copied] = '\0'; } + if (string_size != nullptr) *string_size = text.size(); return SCE_NP_UNIVERSAL_DATA_SYSTEM_OK; } diff --git a/core/libs/prx/libSceNpUniversalDataSystem/src/NpUniversalDataSystem.hpp b/core/libs/prx/libSceNpUniversalDataSystem/src/NpUniversalDataSystem.hpp index e89083d5bd..db4ee0fbf1 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/src/NpUniversalDataSystem.hpp +++ b/core/libs/prx/libSceNpUniversalDataSystem/src/NpUniversalDataSystem.hpp @@ -14,13 +14,11 @@ static constexpr int SCE_NP_UNIVERSAL_DATA_SYSTEM_OK = 0; static constexpr int SCE_NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT = -2141900542; +static constexpr int SCE_NP_UNIVERSAL_DATA_SYSTEM_ERROR_NULL_EVENT = -2141900522; static constexpr int NP_UNIVERSAL_DATA_SYSTEM_CONTEXT_DEFAULT = 1; static constexpr int NP_UNIVERSAL_DATA_SYSTEM_HANDLE_DEFAULT = 1; -static constexpr const char* NP_UNIVERSAL_DATA_SYSTEM_EMPTY_JSON = "{}"; -static constexpr size_t NP_UNIVERSAL_DATA_SYSTEM_EMPTY_EVENT_SIZE = 3; - namespace Uds { template diff --git a/core/libs/prx/libSceNpUniversalDataSystem/src/UdsEvent.cpp b/core/libs/prx/libSceNpUniversalDataSystem/src/UdsEvent.cpp index 94767a7d35..823b1a1161 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/src/UdsEvent.cpp +++ b/core/libs/prx/libSceNpUniversalDataSystem/src/UdsEvent.cpp @@ -91,6 +91,126 @@ void appendValue(std::string& out, const Value& value) { }, value); } +std::size_t lengthPrefixed(std::size_t length, std::size_t smallLimit) { + if (length < smallLimit) return (smallLimit == 32 ? 1 : 2) + length; + if (length < 0x100) return 2 + length; + if (length < 0x10000) return 3 + length; + return 5 + length; +} + +std::size_t containerHeader(std::size_t count) { + if (count < 16) return 1; + if (count < 0x10000) return 3; + return 5; +} + +std::size_t packedSize(const Value& value); + +std::size_t packedSize(const NpUniversalDataSystemEventPropertyObject& object) { + std::size_t size = containerHeader(object.properties.size()); + for (const auto& [key, value] : object.properties) size += lengthPrefixed(key.size(), 32) + packedSize(value); + return size; +} + +std::size_t packedSize(const Value& value) { + return std::visit([](const auto& held) -> std::size_t { + using Held = std::decay_t; + if constexpr (std::is_same_v) { + return 1; + } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { + return 5; + } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { + return 9; + } else if constexpr (std::is_same_v) { + return lengthPrefixed(held.size(), 32); + } else if constexpr (std::is_same_v) { + return lengthPrefixed(held.size(), 0x100); + } else if constexpr (std::is_same_v>) { + return packedSize(*held); + } else { + std::size_t size = containerHeader(held->values.size()); + for (const auto& element : held->values) size += packedSize(element); + return size; + } + }, value); +} + +template +void appendTagged(std::string& out, T value, const char* tag) { + char number[40]; + const auto result = std::to_chars(number, number + sizeof(number), value); + if (result.ec != std::errc{}) throw std::runtime_error("event property number formatting failed"); + out.append(number, static_cast(result.ptr - number)); + out += tag; +} + +void appendTaggedReal(std::string& out, double value, const char* tag) { + char number[40]; + const auto result = std::to_chars(number, number + sizeof(number), value, std::chars_format::general, 6); + if (result.ec != std::errc{}) throw std::runtime_error("event property number formatting failed"); + out.append(number, static_cast(result.ptr - number)); + out += tag; +} + +void appendDebug(std::string& out, const Value& value, std::size_t indent); + +void appendDebug(std::string& out, const NpUniversalDataSystemEventPropertyObject& object, std::size_t indent) { + out += "{\n"; + for (const auto& [key, value] : object.properties) { + out.append(indent + 4, ' '); + out += '"'; + out += key; + out += "\": "; + appendDebug(out, value, indent + 4); + out += ",\n"; + } + out.append(indent, ' '); + out += '}'; +} + +void appendDebug(std::string& out, const Value& value, std::size_t indent) { + std::visit([&](const auto& held) { + using Held = std::decay_t; + if constexpr (std::is_same_v) { + out += held ? "true" : "false"; + } else if constexpr (std::is_same_v) { + appendTagged(out, held, " (Int32)"); + } else if constexpr (std::is_same_v) { + appendTagged(out, held, " (UInt32)"); + } else if constexpr (std::is_same_v) { + appendTagged(out, held, " (Int64)"); + } else if constexpr (std::is_same_v) { + appendTagged(out, held, " (UInt64)"); + } else if constexpr (std::is_same_v) { + appendTaggedReal(out, held, " (Float32)"); + } else if constexpr (std::is_same_v) { + appendTaggedReal(out, held, " (Float64)"); + } else if constexpr (std::is_same_v) { + out += '"'; + out += held; + out += '"'; + } else if constexpr (std::is_same_v) { + static constexpr char Digits[] = "0123456789ABCDEF"; + for (const std::uint8_t byte : held) { + out += Digits[byte >> 4]; + out += Digits[byte & 0xF]; + } + out += " (Binary)"; + } else if constexpr (std::is_same_v>) { + appendDebug(out, *held, indent); + } else { + out += "[\n"; + for (const auto& element : held->values) { + out.append(indent + 4, ' '); + appendDebug(out, element, indent + 4); + out += ",\n"; + } + out.append(indent, ' '); + out += ']'; + } + }, value); +} + std::optional unsignedValue(const Value& value) { return std::visit([](const auto& held) -> std::optional { using Held = std::decay_t; @@ -150,6 +270,20 @@ std::string Describe(const NpUniversalDataSystemEvent& event) { return event.name + " " + Describe(event.properties); } +std::size_t MessagePackSize(const NpUniversalDataSystemEvent& event) { + return containerHeader(2) + lengthPrefixed(EventNameKey.size(), 32) + lengthPrefixed(event.name.size(), 32) + + lengthPrefixed(PropertiesKey.size(), 32) + packedSize(event.properties); +} + +std::string DebugString(const NpUniversalDataSystemEvent& event) { + NpUniversalDataSystemEventPropertyObject root; + root.Set(EventNameKey, event.name); + root.Set(PropertiesKey, std::make_shared(event.properties)); + std::string out; + appendDebug(out, root, 0); + return out; +} + NpTrophy2Event StatEvent(const NpUniversalDataSystemEvent& event) { NpTrophy2Event converted; converted.name = event.name; diff --git a/core/libs/prx/libSceNpUniversalDataSystem/tests/Events.cpp b/core/libs/prx/libSceNpUniversalDataSystem/tests/Events.cpp index 992af0fe45..5caec164bc 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/tests/Events.cpp +++ b/core/libs/prx/libSceNpUniversalDataSystem/tests/Events.cpp @@ -7,10 +7,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -143,6 +145,115 @@ void testEvents() { }), "an event without a name was created"); } +void checkEventText(const NpUniversalDataSystemEvent* event, size_t expectedSize, std::string_view expected, const char* message) { + size_t size = 0; + call(sceNpUniversalDataSystemEventEstimateSize(event, &size), "EstimateSize failed"); + check(size == expectedSize, message); + size_t length = 0; + call(sceNpUniversalDataSystemEventToString(event, nullptr, 0, &length), "ToString without a buffer failed"); + check(length == expected.size(), message); + std::string text(expected.size() + 2, '\x7f'); + length = 0; + call(sceNpUniversalDataSystemEventToString(event, text.data(), expected.size() + 1, &length), "ToString failed"); + check(length == expected.size() && text.compare(0, expected.size(), expected) == 0 && text[expected.size()] == '\0' && text.back() == '\x7f', message); +} + +void testEventSizeAndString() { + using Fill = void (*)(NpUniversalDataSystemEventPropertyObject*); + struct Case { + Fill fill; + size_t size; + std::string_view text; + const char* message; + }; + const Case cases[] = { + {[](NpUniversalDataSystemEventPropertyObject*) {}, 25, + "{\n \"eventName\": \"E\",\n \"properties\": {\n },\n}", "an empty event was not described as the console does"}, + {[](NpUniversalDataSystemEventPropertyObject* object) { call(sceNpUniversalDataSystemEventPropertyObjectSetInt64(object, "k", INT64_MIN), "SetInt64 failed"); }, 36, + "{\n \"eventName\": \"E\",\n \"properties\": {\n \"k\": -9223372036854775808 (Int64),\n },\n}", "an int64 property was not described as the console does"}, + {[](NpUniversalDataSystemEventPropertyObject* object) { call(sceNpUniversalDataSystemEventPropertyObjectSetUInt64(object, "k", UINT64_MAX), "SetUInt64 failed"); }, 36, + "{\n \"eventName\": \"E\",\n \"properties\": {\n \"k\": 18446744073709551615 (UInt64),\n },\n}", "a uint64 property was not described as the console does"}, + {[](NpUniversalDataSystemEventPropertyObject* object) { call(sceNpUniversalDataSystemEventPropertyObjectSetFloat32(object, "k", 0.1f), "SetFloat32 failed"); }, 32, + "{\n \"eventName\": \"E\",\n \"properties\": {\n \"k\": 0.1 (Float32),\n },\n}", "a float32 property was not described as the console does"}, + {[](NpUniversalDataSystemEventPropertyObject* object) { call(sceNpUniversalDataSystemEventPropertyObjectSetFloat64(object, "k", 1e300), "SetFloat64 failed"); }, 36, + "{\n \"eventName\": \"E\",\n \"properties\": {\n \"k\": 1e+300 (Float64),\n },\n}", "a float64 property was not described as the console does"}, + {[](NpUniversalDataSystemEventPropertyObject* object) { call(sceNpUniversalDataSystemEventPropertyObjectSetString(object, "s", "a\"b\\c\n\x01/z"), "SetString failed"); }, 37, + "{\n \"eventName\": \"E\",\n \"properties\": {\n \"s\": \"a\"b\\c\n\x01/z\",\n },\n}", "a string property was escaped or resized"}, + {[](NpUniversalDataSystemEventPropertyObject* object) { call(sceNpUniversalDataSystemEventPropertyObjectSetBinary(object, "b", "\x00\x7f\xff", 3), "SetBinary failed"); }, 32, + "{\n \"eventName\": \"E\",\n \"properties\": {\n \"b\": 007FFF (Binary),\n },\n}", "a binary property was not described as the console does"}, + {[](NpUniversalDataSystemEventPropertyObject* object) { + call(sceNpUniversalDataSystemEventPropertyObjectSetInt32(object, "zeta", 1), "SetInt32 failed"); + call(sceNpUniversalDataSystemEventPropertyObjectSetInt32(object, "alpha", 2), "SetInt32 failed"); + call(sceNpUniversalDataSystemEventPropertyObjectSetInt32(object, "mid", 3), "SetInt32 failed"); + call(sceNpUniversalDataSystemEventPropertyObjectSetInt32(object, "alpha", 4), "SetInt32 failed"); + }, 55, + "{\n \"eventName\": \"E\",\n \"properties\": {\n \"zeta\": 1 (Int32),\n \"alpha\": 4 (Int32),\n \"mid\": 3 (Int32),\n },\n}", + "properties were not described in the order they were first set"}, + {[](NpUniversalDataSystemEventPropertyObject* object) { + auto* child = createObject(); + call(sceNpUniversalDataSystemEventPropertyObjectSetInt32(child, "x", 7), "SetInt32 failed"); + NpUniversalDataSystemEventPropertyObject* stored = nullptr; + call(sceNpUniversalDataSystemEventPropertyObjectSetObject(object, "obj", child, &stored), "SetObject failed"); + call(sceNpUniversalDataSystemEventPropertyObjectSetInt32(child, "after", 1), "SetInt32 failed"); + call(sceNpUniversalDataSystemEventPropertyObjectSetInt32(stored, "viaPtr", 2), "SetInt32 failed"); + call(sceNpUniversalDataSystemDestroyEventPropertyObject(child), "child object was not destroyed"); + }, 49, + "{\n \"eventName\": \"E\",\n \"properties\": {\n \"obj\": {\n \"x\": 7 (Int32),\n \"viaPtr\": 2 (Int32),\n },\n },\n}", + "a nested object was not described as the console does"}, + {[](NpUniversalDataSystemEventPropertyObject* object) { + NpUniversalDataSystemEventPropertyArray* array = nullptr; + call(sceNpUniversalDataSystemCreateEventPropertyArray(&array), "array was not created"); + call(sceNpUniversalDataSystemEventPropertyArraySetInt32(array, -1), "array SetInt32 failed"); + call(sceNpUniversalDataSystemEventPropertyArraySetString(array, "x"), "array SetString failed"); + call(sceNpUniversalDataSystemEventPropertyArraySetBool(array, true), "array SetBool failed"); + call(sceNpUniversalDataSystemEventPropertyArraySetUInt64(array, 9), "array SetUInt64 failed"); + call(sceNpUniversalDataSystemEventPropertyArraySetFloat32(array, 2.5f), "array SetFloat32 failed"); + call(sceNpUniversalDataSystemEventPropertyArraySetBinary(array, "\x01\x02", 2), "array SetBinary failed"); + auto* inner = createObject(); + call(sceNpUniversalDataSystemEventPropertyObjectSetInt32(inner, "i", 1), "SetInt32 failed"); + call(sceNpUniversalDataSystemEventPropertyArraySetObject(array, inner, nullptr), "array SetObject failed"); + NpUniversalDataSystemEventPropertyArray* empty = nullptr; + call(sceNpUniversalDataSystemCreateEventPropertyArray(&empty), "empty array was not created"); + call(sceNpUniversalDataSystemEventPropertyArraySetArray(array, empty, nullptr), "array SetArray failed"); + call(sceNpUniversalDataSystemEventPropertyObjectSetArray(object, "arr", array, nullptr), "SetArray failed"); + call(sceNpUniversalDataSystemDestroyEventPropertyObject(inner), "inner object was not destroyed"); + call(sceNpUniversalDataSystemDestroyEventPropertyArray(empty), "empty array was not destroyed"); + call(sceNpUniversalDataSystemDestroyEventPropertyArray(array), "array was not destroyed"); + }, 65, + "{\n \"eventName\": \"E\",\n \"properties\": {\n \"arr\": [\n -1 (Int32),\n \"x\",\n true,\n 9 (UInt64),\n" + " 2.5 (Float32),\n 0102 (Binary),\n {\n \"i\": 1 (Int32),\n },\n [\n ],\n ],\n },\n}", + "an array was not described as the console does"}, + }; + for (const auto& item : cases) { + NpUniversalDataSystemEvent* event = nullptr; + NpUniversalDataSystemEventPropertyObject* properties = nullptr; + call(sceNpUniversalDataSystemCreateEvent("E", nullptr, &event, &properties), "event was not created"); + item.fill(properties); + checkEventText(event, item.size, item.text, item.message); + call(sceNpUniversalDataSystemDestroyEvent(event), "event was not destroyed"); + } + + NpUniversalDataSystemEvent* event = nullptr; + call(sceNpUniversalDataSystemCreateEvent("E", nullptr, &event, nullptr), "event was not created"); + size_t length = 0; + check(sceNpUniversalDataSystemEventEstimateSize(nullptr, &length) == SCE_NP_UNIVERSAL_DATA_SYSTEM_ERROR_NULL_EVENT, "EstimateSize accepted a null event"); + check(sceNpUniversalDataSystemEventEstimateSize(event, nullptr) == SCE_NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT, "EstimateSize accepted a null size"); + char text[8]; + check(sceNpUniversalDataSystemEventToString(nullptr, text, sizeof(text), &length) == SCE_NP_UNIVERSAL_DATA_SYSTEM_ERROR_NULL_EVENT, "ToString accepted a null event"); + check(sceNpUniversalDataSystemEventToString(event, nullptr, 0, nullptr) == SCE_NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT, "ToString accepted no buffer and no length"); + length = 0; + call(sceNpUniversalDataSystemEventToString(event, nullptr, sizeof(text), &length), "ToString without a buffer but with a capacity failed"); + check(length == 52, "ToString without a buffer did not report the full length"); + std::memset(text, 'x', sizeof(text)); + call(sceNpUniversalDataSystemEventToString(event, text, 0, &length), "ToString into an empty buffer failed"); + check(length == 52 && text[0] == 'x', "ToString wrote into an empty buffer"); + call(sceNpUniversalDataSystemEventToString(event, text, 1, nullptr), "ToString into a one-byte buffer failed"); + check(text[0] == '\0' && text[1] == 'x', "a one-byte buffer did not get only the terminator"); + call(sceNpUniversalDataSystemEventToString(event, text, 7, &length), "ToString into a short buffer failed"); + check(length == 52 && std::string_view(text, 7) == std::string_view("{\n \0", 7) && text[7] == 'x', "a short buffer was not cut like snprintf"); + call(sceNpUniversalDataSystemDestroyEvent(event), "event was not destroyed"); +} + Uds::TrophyEvent recognize(const char* name, std::optional id) { NpUniversalDataSystemEvent event; event.name = name; @@ -287,6 +398,7 @@ int main() { testObjectSetters(); testNestedValues(); testEvents(); + testEventSizeAndString(); testRecognition(); testStatEvent(); testLocaleIndependentNumberFormatting(); diff --git a/docs/dev/debt/libSceNpUniversalDataSystem.md b/docs/dev/debt/libSceNpUniversalDataSystem.md index cf98cd2908..bd9abf3093 100644 --- a/docs/dev/debt/libSceNpUniversalDataSystem.md +++ b/docs/dev/debt/libSceNpUniversalDataSystem.md @@ -41,3 +41,4 @@ - [Trophy events](../../../core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp): `sceNpUniversalDataSystemPostEvent` stores an `_UnlockTrophy` event with an int32 `_trophy_id` of 0 or more as an unlock for the user and service label of its context, as KytyPS5 does, except an id that [libSceNpTrophy2](../../../core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp) refuses when the title's trophy data is loaded (one not in the data, a platinum or a progressive trophy). `_UpdateTrophyProgress` goes through the title's statistic rules like any other event (below). A context need not be registered, and every post of an event that `sceNpUniversalDataSystemCreateEvent` returned and that is not yet destroyed returns success (a null or any other event pointer stops the game): KytyPS5's errors for an unknown or unregistered context and for a malformed unlock are not verified. The storage statistics stay zero. - [Statistics](../../../core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp): every other event goes through the title's rules in `sce_sys/uds/udsNN.ucp`, which [libSceNpTrophy2](../../../core/libs/prx/libSceNpTrophy2/src/TitleTrophies.cpp) reads. Only `"aggregation":"latest"` and `"max"` statistics of `dataType` `uint32`, `uint64` or `int32` are kept; no public source documents the other aggregations (such as `count`, `collection` and `eachLatest`) or types, so their definitions and rules are skipped. Only rules whose input is a `$.name` path (dotted for nested objects) are applied. A value is used when it is an integer of any width that is not negative and fits the statistic's type and `minValue`/`maxValue`; KytyPS5 requires the statistic's exact width, and whether the console converts, clamps or rejects other values is not known. Statistics are stored per user and service label, and every trophy's `unlockCondition` is compared after each event that sets one. +- [Event checks](../../../core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp): the 13.42 module returns 0x80553117 from `sceNpUniversalDataSystemCreateEvent`, `sceNpUniversalDataSystemEventEstimateSize` and `sceNpUniversalDataSystemEventToString` before `sceNpUniversalDataSystemInitialize`, and `sceNpUniversalDataSystemCreateEvent` returns 0x8055310d for an empty name; its disassembly also compares packed sizes with 0x5000 bytes when `prop` is given, which was not measured. These functions do neither. `sceNpUniversalDataSystemEventToString` writes NaN and infinite properties as `std::to_chars` does; the console's text for them is not measured. From 2099ba1785a13773c966089a5e54dcadf8a6f290 Mon Sep 17 00:00:00 2001 From: BunnyFl <166477956+BunnyFl@users.noreply.github.com> Date: Fri, 30 Oct 2026 08:10:55 +0100 Subject: [PATCH 2/2] test(libSceNpUniversalDataSystem): expect the console's event size, text and null-argument codes in the query test Co-Authored-By: Claude Opus 5.5 --- .../libs/tests/GuestNpUniversalDataSystemQueries.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/libs/tests/GuestNpUniversalDataSystemQueries.cpp b/core/libs/tests/GuestNpUniversalDataSystemQueries.cpp index 222d6f21a9..2a6e1afa98 100644 --- a/core/libs/tests/GuestNpUniversalDataSystemQueries.cpp +++ b/core/libs/tests/GuestNpUniversalDataSystemQueries.cpp @@ -49,18 +49,18 @@ int main() { NpUniversalDataSystemEvent* event = nullptr; CHECK(sceNpUniversalDataSystemCreateEvent("QueryEvent", nullptr, &event, nullptr) == 0 && event != nullptr); std::size_t size = 0; - CHECK(RejectsArgument([&] { sceNpUniversalDataSystemEventEstimateSize(nullptr, &size); })); - CHECK(RejectsArgument([&] { sceNpUniversalDataSystemEventEstimateSize(event, nullptr); })); - CHECK(sceNpUniversalDataSystemEventEstimateSize(event, &size) == 0 && size == 3); + CHECK(sceNpUniversalDataSystemEventEstimateSize(nullptr, &size) == static_cast(0x80553116u)); + CHECK(sceNpUniversalDataSystemEventEstimateSize(event, nullptr) == static_cast(0x80553102u)); + CHECK(sceNpUniversalDataSystemEventEstimateSize(event, &size) == 0 && size == 34); char text[8]; std::memset(text, 'x', sizeof(text)); std::size_t length = 0; - CHECK(RejectsArgument([&] { sceNpUniversalDataSystemEventToString(nullptr, text, sizeof(text), &length); })); + CHECK(sceNpUniversalDataSystemEventToString(nullptr, text, sizeof(text), &length) == static_cast(0x80553116u)); CHECK(text[0] == 'x' && length == 0); - CHECK(sceNpUniversalDataSystemEventToString(event, text, sizeof(text), &length) == 0 && length == 3 && std::strcmp(text, "{}") == 0); + CHECK(sceNpUniversalDataSystemEventToString(event, text, sizeof(text), &length) == 0 && length == 61 && std::strcmp(text, "{\n \"") == 0); length = 0; - CHECK(sceNpUniversalDataSystemEventToString(event, nullptr, 0, &length) == 0 && length == 3); + CHECK(sceNpUniversalDataSystemEventToString(event, nullptr, 0, &length) == 0 && length == 61); char tiny[2] = {'x', 'x'}; CHECK(sceNpUniversalDataSystemEventToString(event, tiny, sizeof(tiny), nullptr) == 0 && tiny[0] == '{' && tiny[1] == '\0'); CHECK(sceNpUniversalDataSystemDestroyEvent(event) == 0);