diff --git a/core/libs/prx/libSceJson2/Export.cpp b/core/libs/prx/libSceJson2/Export.cpp index ea9bc796d59..a8e8ac3af81 100644 --- a/core/libs/prx/libSceJson2/Export.cpp +++ b/core/libs/prx/libSceJson2/Export.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -906,9 +907,22 @@ int APS5_VABI _ZN3sce4Json6Object4PairD1Ev() { return 0; } -int APS5_VABI _ZN3sce4Json6Object6insertERKNS1_4PairE() { - NotImplemented_nid_no_patch(__func__); - return 0; +ObjectIterator* APS5_VABI _ZN3sce4Json6Object6insertERKNS1_4PairE(ObjectIterator* result, Object* self, const Pair* pair) { + if (pair == nullptr) throw std::invalid_argument("sce::Json::Object::insert: null pair"); + for (const auto& existing : *self->items) + if (*existing.key.text == *pair->key.text) throw std::runtime_error("sce::Json::Object::insert: duplicate key; the console's behaviour is unknown"); + Pair copy{}; + Construct(copy.key, *pair->key.text); + try { + CopyInto(copy.value, pair->value); + } catch (...) { + Destroy(copy.key); + Destroy(copy.value); + throw; + } + self->items->push_back(copy); + result->it = std::prev(self->items->end()); + return result; } int APS5_VABI _ZN3sce4Json5Array8iteratorppEi() { diff --git a/core/libs/tests/GuestJson.cpp b/core/libs/tests/GuestJson.cpp index eb29e781ef2..2e5e6cb9b04 100644 --- a/core/libs/tests/GuestJson.cpp +++ b/core/libs/tests/GuestJson.cpp @@ -61,6 +61,7 @@ Pair* APS5_VABI _ZNK3sce4Json6Object8iteratorptEv(const Iterator*); Iterator* APS5_VABI _ZNK3sce4Json6Object4findERKNS0_6StringE(Iterator*, const Object*, const String*); int APS5_VABI _ZNK3sce4Json6String7compareEPKc(const String*, const char*); bool APS5_VABI _ZNK3sce4Json6Object8iteratorneERKS2_(const Iterator*, const Iterator*); +Iterator* APS5_VABI _ZN3sce4Json6Object6insertERKNS1_4PairE(Iterator*, Object*, const Pair*); void APS5_VABI _ZN3sce4Json5ValueC1Ev(Value*); void APS5_VABI _ZN3sce4Json5ValueC1Eb(Value*, bool); void APS5_VABI _ZN3sce4Json5ValueC1Ed(Value*, double); @@ -596,6 +597,44 @@ static int APS5_VABI ReadNext(char& ch, void* context) { return 0; } +static void ObjectInsert() { + Object object{}; + _ZN3sce4Json6ObjectC1Ev(&object); + Pair first{}; + _ZN3sce4Json6StringC1EPKc(&first.key, "k"); + _ZN3sce4Json5ValueC1Ev(&first.value); + Require(_ZN3sce4Json5Value3setEl(&first.value, 7) == 0); + Iterator inserted{}; + Require(_ZN3sce4Json6Object6insertERKNS1_4PairE(&inserted, &object, &first) == &inserted); + const Pair* stored = _ZNK3sce4Json6Object8iteratordeEv(&inserted); + Require(stored != &first && stored->value.node != first.value.node); + Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&stored->key), "k") == 0); + Require(_ZN3sce4Json5Value3setEl(&first.value, 8) == 0); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&stored->value) == 7); + Pair second{}; + _ZN3sce4Json6StringC1EPKc(&second.key, "j"); + _ZN3sce4Json5ValueC1Ev(&second.value); + Require(_ZN3sce4Json5Value3setEPKc(&second.value, "s") == 0); + Iterator next{}; + _ZN3sce4Json6Object6insertERKNS1_4PairE(&next, &object, &second); + Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&_ZNK3sce4Json6Object8iteratordeEv(&next)->key), "j") == 0); + Require(_ZNK3sce4Json6Object4sizeEv(&object) == 2); + bool threw = false; + try { _ZN3sce4Json6Object6insertERKNS1_4PairE(&next, &object, &first); } catch (const std::runtime_error&) { threw = true; } + Require(threw && _ZNK3sce4Json6Object4sizeEv(&object) == 2); + Value wrapped{}; + _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(&wrapped, &object); + Require(Serialize(wrapped) == "{\"k\":7,\"j\":\"s\"}"); + _ZN3sce4Json5ValueD1Ev(&wrapped); + _ZN3sce4Json6Object8iteratorD1Ev(&inserted); + _ZN3sce4Json6Object8iteratorD1Ev(&next); + _ZN3sce4Json6StringD1Ev(&first.key); + _ZN3sce4Json5ValueD1Ev(&first.value); + _ZN3sce4Json6StringD1Ev(&second.key); + _ZN3sce4Json5ValueD1Ev(&second.value); + _ZN3sce4Json6ObjectD1Ev(&object); +} + static void StreamParse() { Value root{}; _ZN3sce4Json5ValueC1Ev(&root); @@ -615,6 +654,7 @@ int main() { NestingDepth(); ObjectsAndArrays(); ObjectFind(); + ObjectInsert(); NullAccess(_ZN3sce4Json11Initializer27setGlobalNullAccessCallBackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_); NullAccess(_ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_); ValueAccess(); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index b2f878ee688..1cbfc058b6a 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -703,7 +703,8 @@ Throughout the project, every function at every stage either **does exactly what - [pthread_kill](../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) returns the error number like FreeBSD's `pthread_kill`: `EINVAL` for signals outside 0 to 128 and `ESRCH` for a null thread. Signal 0 reports any other thread handle as existing, without checking that the thread is still alive. A signal to the calling thread goes through `raise`, so it shares `raise`'s limits; a signal to another thread throws, since guest signals are not delivered to a chosen thread - [select](../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) validates `tv_usec` but saturates a valid timeout beyond the remaining `steady_clock` range at `time_point::max()`, so the wait can continue effectively indefinitely. The console's behavior for a valid timeout outside the host clock's range is unknown. - [AJM](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp), [APR](../../core/libs/prx/libkernel/Apr/src/Apr.cpp) and [NGS2](../../core/libs/prx/libSceNgs2.native/src/System.cpp) state is allocated with `new` and never freed on purpose: title threads still call into these libraries while `std::exit` runs static destructors, so ordinary statics would be destroyed under them. Keep them leaked; `guest_ajm_teardown` covers the AJM case on Unix. -- PPSA30601 imports declared without parameters, signatures unknown, added without an implementation because the title fails to load without them: [six sce::Json::Object::Pair, sce::Json::Object and sce::Json::Array::iterator members](../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2), [sceAmprAmmGetUsageStatsData](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr), [sceMouseResetAbsolutePosition](../../core/libs/prx/libSceMouse/Export.cpp) (libSceMouse) and [sceSaveDataDirNameSearch2](../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) +- PPSA30601 imports declared without parameters, signatures unknown, added without an implementation because the title fails to load without them: [five sce::Json::Object::Pair and sce::Json::Array::iterator members](../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2), [sceAmprAmmGetUsageStatsData](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr), [sceMouseResetAbsolutePosition](../../core/libs/prx/libSceMouse/Export.cpp) (libSceMouse) and [sceSaveDataDirNameSearch2](../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) +- [sce::Json::Object::insert](../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - imported by PPSA30601. The `insert(const Pair&)` signature and its iterator to the inserted pair follow the PS Vita `sce::Json` declaration in [vitasdk](https://github.com/vitasdk/vita-headers/blob/master/include/psp2/json.h); the PS5 behaviour is not confirmed on a title. The pair is copied to the end of the object, so iteration and serialization keep insertion order as `operator[]` does. Inserting a key the object already has throws, because whether the console replaces, keeps or duplicates the pair is unknown - PPSA07411 imports declared without parameters, signatures unknown, added without an implementation because the title fails to load without them: [sceSslCreateConnection, sceSslDeleteConnection, sceSslEnableVerifyOption, sceSslConnect, sceSslRead, sceSslWrite](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl), [four sce::Json::Value members (setElementAccessFailureHandler, setSpecialFloatHandler, setTypeMismatchHandler, operator bool)](../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2), [sceConvertKeycodeGetCharacter](../../core/libs/prx/libSceConvertKeycode/Export.cpp) (libSceConvertKeycode) and [sceNpSessionSignalingGetGroupFromSessionId](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - [Comparison-sampled R32 float and R16 unorm textures](../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) whose surface a storage image holds are sampled from a D32_SFLOAT or D16_UNORM image that the GPU refills from the storage image whenever its version moves, because Vulkan compares only depth formats. Only surfaces of at least 1 MiB (`APS5_DEPTH_COPY_MIN_KIB`) that are not 3D take the copy; smaller ones stay CPU snapshots compared against guest memory. With copies of small surfaces, one title hit an intermittent boot-time crash that was not explained. - [Null vertex buffer V#](../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) (libSceAgcDriver) - a V# whose four dwords are all zero is bound to a zeroed vertex buffer of the attribute's size with stride 0, so every component of its attribute reads 0. This follows the V# fields in the [AMD RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) (`NUM_RECORDS` 0, `DST_SEL` 0 selects 0), not measured on hardware.