diff --git a/core/libs/prx/libSceJson2/Export.cpp b/core/libs/prx/libSceJson2/Export.cpp index 765c5ffff..c4271d4b4 100644 --- a/core/libs/prx/libSceJson2/Export.cpp +++ b/core/libs/prx/libSceJson2/Export.cpp @@ -808,30 +808,53 @@ int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(Value* out, const char* } -int APS5_VABI _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +struct InitParameterRtti { + void* allocParam; + void* userData; + std::size_t fileBufferSize; +}; + +void APS5_VABI _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(InitParameterRtti* self, void* allocParam, void* userData, std::size_t fileBufferSize) { + self->allocParam = allocParam; + self->userData = userData; + self->fileBufferSize = fileBufferSize; } + struct InitParameter2 { void* allocator; void* userData; std::size_t fileBufferSize; + std::int32_t specialFloatFormatType; + std::uint32_t reserved[3]; }; -static_assert(sizeof(InitParameter2) <= 40); +static_assert(sizeof(InitParameter2) == 0x28 && offsetof(InitParameter2, specialFloatFormatType) == 0x18); void APS5_VABI _ZN3sce4Json14InitParameter2C1Ev(InitParameter2* self) { *self = {}; } +void APS5_VABI _ZN3sce4Json14InitParameter2C2Ev(InitParameter2* self) { + _ZN3sce4Json14InitParameter2C1Ev(self); +} + void APS5_VABI _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(InitParameter2* self, void* allocator, void* userData) { self->allocator = allocator; self->userData = userData; } +void APS5_VABI _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(InitParameter2* self, void* allocParam, void* userData) { + self->allocator = allocParam; + self->userData = userData; +} + void APS5_VABI _ZN3sce4Json14InitParameter217setFileBufferSizeEm(InitParameter2* self, std::size_t size) { self->fileBufferSize = size; } +void APS5_VABI _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE(InitParameter2* self, std::int32_t type) { + self->specialFloatFormatType = type; +} + int APS5_VABI _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(void* self, const InitParameter2* parameter) { (void)self; if (parameter == nullptr) throw std::invalid_argument("sce::Json::Initializer::initialize: null parameter"); @@ -848,15 +871,6 @@ int APS5_VABI _ZN3sce4Json12MemAllocator11notifyErrorEimPv(void) { return 0; } -void APS5_VABI _ZN3sce4Json14InitParameter2C2Ev(InitParameter2* self) { - _ZN3sce4Json14InitParameter2C1Ev(self); -} - -int APS5_VABI _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/tests/GuestJson.cpp b/core/libs/tests/GuestJson.cpp index 47c57c6d9..eb29e781e 100644 --- a/core/libs/tests/GuestJson.cpp +++ b/core/libs/tests/GuestJson.cpp @@ -95,6 +95,9 @@ void APS5_VABI _ZN3sce4Json14InitParameter2C2Ev(void*); void APS5_VABI _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(void*, void*, void*); void APS5_VABI _ZN3sce4Json14InitParameter217setFileBufferSizeEm(void*, std::size_t); int APS5_VABI _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(void*, const void*); +void APS5_VABI _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(void*, void*, void*); +void APS5_VABI _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE(void*, std::int32_t); +void APS5_VABI _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(void*, void*, void*, std::size_t); } static void Check(bool value, int line) { @@ -475,6 +478,69 @@ static void InitParameter2BaseConstructor() { Require(_ZN3sce4Json11InitializerD1Ev(initializer) == 0); } +struct StoredInitParameter2 { + void* allocator; + void* userData; + std::size_t fileBufferSize; + std::int32_t specialFloatFormatType; + std::uint32_t reserved[3]; +}; +static_assert(sizeof(StoredInitParameter2) == 40); + +static StoredInitParameter2 Stored(const std::uint8_t* parameter) { + StoredInitParameter2 stored{}; + std::memcpy(&stored, parameter, sizeof(stored)); + return stored; +} + +static bool Untouched(const std::uint8_t* parameter, std::size_t from) { + for (std::size_t i = from; i < 40; ++i) + if (parameter[i] != 0xff) return false; + return true; +} + +static void InitParameters() { + using Constructor = void (APS5_VABI*)(void*); + for (Constructor construct : {_ZN3sce4Json14InitParameter2C1Ev, _ZN3sce4Json14InitParameter2C2Ev}) { + alignas(16) std::uint8_t parameter[40]; + std::memset(parameter, 0xff, sizeof(parameter)); + construct(parameter); + StoredInitParameter2 stored = Stored(parameter); + Require(stored.allocator == nullptr && stored.userData == nullptr && stored.fileBufferSize == 0); + Require(stored.specialFloatFormatType == 0); + Require(stored.reserved[0] == 0 && stored.reserved[1] == 0 && stored.reserved[2] == 0); + + int allocParam = 0; + int userData = 0; + _ZN3sce4Json14InitParameter217setFileBufferSizeEm(parameter, 8192); + _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE(parameter, 2); + _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(parameter, &allocParam, &userData); + stored = Stored(parameter); + Require(stored.allocator == &allocParam && stored.userData == &userData && stored.fileBufferSize == 8192); + Require(stored.specialFloatFormatType == 2); + Require(stored.reserved[0] == 0 && stored.reserved[1] == 0 && stored.reserved[2] == 0); + + _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE(parameter, 0); + Require(Stored(parameter).specialFloatFormatType == 0 && Stored(parameter).fileBufferSize == 8192); + + alignas(16) std::uint8_t initializer[16]{}; + Require(_ZN3sce4Json11InitializerC1Ev(initializer) == 0); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(initializer, parameter) == 0); + Require(_ZN3sce4Json11Initializer9terminateEv(initializer) == 0); + Require(_ZN3sce4Json11InitializerD1Ev(initializer) == 0); + } + + alignas(16) std::uint8_t rttiParameter[40]; + std::memset(rttiParameter, 0xff, sizeof(rttiParameter)); + int allocParam = 0; + int userData = 0; + _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(rttiParameter, &allocParam, &userData, 4096); + void* rttiStored[3]{}; + std::memcpy(rttiStored, rttiParameter, sizeof(rttiStored)); + Require(rttiStored[0] == &allocParam && rttiStored[1] == &userData && reinterpret_cast(rttiStored[2]) == 4096); + Require(Untouched(rttiParameter, sizeof(rttiStored))); +} + static void ValueClear() { Value value{}; _ZN3sce4Json5ValueC1Ev(&value); @@ -553,6 +619,7 @@ int main() { NullAccess(_ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_); ValueAccess(); InitParameter2BaseConstructor(); + InitParameters(); ValueClear(); StreamParse(); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index df0e4e3f2..0f088b167 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -356,7 +356,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceNgs2VoiceControl sampler filter](../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - param `0x1000000a`, its layout and meaning follow KytyPS5 only: location 1 type 1 is an RBJ low-pass biquad at the system sample rate after resampling, scaled by `level`, a set `channel_mask` bit bypasses that channel, type 0 turns the filter off. Other locations and types throw. The voice keeps playing a filter tail after its last block until the history is zero, with outputs below 1e-20 flushed to zero (a KytyPS5 choice); the console's tail length is unknown - [sceNgs2PanInit / sceNgs2PanGetVolumeMatrix](../../core/libs/prx/libSceNgs2.native/src/Pan.cpp) (libSceNgs2.native) - follow the PS4 firmware 11.00 `libSceNgs2.sprx`, read and checked against its output for 2, 4, 5 and 7 speakers into 1, 2, 6 and 8 channels; not yet confirmed on the PS5 library. Speakers are taken in the order L R, L R Ls Rs, L R C Ls Rs or L R C Ls Rs Lb Rb and kept in a fixed circular order, not sorted; without angles the defaults are 90/270, 40/110/250/320, 0/40/110/250/320 and 0/40/110/150/210/250/320 degrees. As in that library, defaults converted to radians keep the wrap-around entry in degrees, so angles past the last speaker pan almost fully to it. Error codes are not known for the PS5, so invalid arguments throw, including a speaker angle equal to the unit angle, on which that library loops forever. Angles so large that adding or subtracting a turn leaves them unchanged throw for the same reason - [sceRtcGetDosTime, sceRtcSetDosTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - bit layout from shadPS4 (PS4, the standard FAT date/time). A year outside 1980-2107 makes `GetDosTime` return `INVALID_YEAR` after writing 0 for an earlier one and `0xff9fbf7d` (2107-12-31 23:59:58, the largest encodable time) for a later one, and `SetDosTime` decodes the year as the unsigned 7-bit FAT field (1980-2107) without validating the fields, as the disassembly-derived [fpPS4](https://github.com/red-prig/fpPS4) reimplementation of the PS4 library does; not confirmed on a PS5 title, and shadPS4 instead masks out-of-range years into the packed value and sign-extends the 16-bit date -- [sce::Json::InitParameter2](../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - layout inferred, not known: assumed to start with the allocator, its user data and the file buffer size, in that order, and the whole struct is assumed to fit in 40 bytes; `Initializer::initialize(InitParameter2)` ignores them as `initialize(InitParameter)` does +- [sce::Json::InitParameter2, InitParameterRtti and InitParameterRtti2](../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - layouts inferred, not known. `InitParameter2` is taken as the 40-byte object of [SharpEmu](https://github.com/sharpemu/sharpemu/blob/1811cef94435db7655209fb39f65ddf258b93fd6/src/SharpEmu.Libs/Json/JsonExports.cs) and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/eda6ba37ece127ab0396378404d99182e3e8a530/src/libs/libJson2.cpp): the allocator, its user data, the file buffer size, the 32-bit special float format type at 0x18 and 12 bytes both zero, and both constructors zero all of it. `InitParameterRtti2::setAllocatorRtti` writes the allocation parameters and user data where `setAllocator` writes the allocator, as both references do; whether the console also marks the object as using RTTI allocation is unknown. `InitParameterRtti`'s constructor writes the allocation parameters, user data and file buffer size to the first 24 bytes, an order inferred from its mangled arguments. The values of `SpecialFloatFormatType` and what they select are unknown, so `setSpecialFloatFormatType` stores any value and nothing reads it: `Initializer::initialize(InitParameter2)` ignores every field as `initialize(InitParameter)` does, serializing a non-finite real throws whatever the type, and whether the type lets the parser accept NaN or infinity is unknown. The setters return nothing; whether the console's return `this` is unknown - [sceAjmBatchJobSetResampleParameters, sceAjmBatchJobGetResampleInfo](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm) - the ratio is taken as decoded samples per output sample and num_samples as the decoded samples the resampler holds, both inferred from FMOD (Hades II); the console's interpolation is unknown, a 4-point Catmull-Rom is used. The flags argument is ignored, as in shadPS4 and Kyty - [scePlayerReviewDialogOpen, scePlayerReviewDialogGetResult](../../core/libs/prx/libSceCdlgPlayerReview/Export.cpp) (libSceCdlgPlayerReview) - the PS5-only dialog has no public reference: the parameter is not read and the result is assumed to start with the 32-bit common dialog result, as `sceWebBrowserDialogGetResult` assumes - [sceHttpWaitRequest](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the timeout is assumed to be in microseconds and a negative timeout to wait without limit; the invalid-value error for a null handle or event array or a non-positive event count is assumed, not measured. No request is ever pending, so a wait returns 0 events when its timeout passes