diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index 8ba86476a..85acc59f5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -163,25 +163,34 @@ std::uint64_t NullPixelProgramAddress() { void Driver::RegisterShader(const Shader* shader) { CheckFailure(); - GuestMemory::CheckRange(shader, sizeof(Shader), alignof(Shader)); - require(shader->file_header == 0x34333231u && shader->version == 0x18u, "invalid shader header"); - require(shader->header_size >= sizeof(Shader), "shader header is smaller than its fixed fields"); - require(shader->shader_size != 0 && (shader->shader_size & 3u) == 0, "invalid shader size"); - GuestMemory::CheckRange(shader, shader->header_size, alignof(Shader)); - const auto* code = const_cast(shader->code); - GuestMemory::CheckRange(code, shader->shader_size, 256); - ShaderSnapshot snapshot{reinterpret_cast(code), reinterpret_cast(shader), shader->type, {}, {}}; - snapshot.code.resize(shader->shader_size / sizeof(std::uint32_t)); - std::memcpy(snapshot.code.data(), code, shader->shader_size); - snapshot.header.resize(shader->header_size); - std::memcpy(snapshot.header.data(), shader, shader->header_size); + GuestMemory::CheckRange(shader, sizeof(Shader), 1); + Shader fields; + std::memcpy(&fields, static_cast(shader), sizeof(Shader)); + require(fields.file_header == 0x34333231u && fields.version == 0x18u, "invalid shader header"); + require(fields.header_size >= sizeof(Shader), "shader header is smaller than its fixed fields"); + require(fields.shader_size != 0 && (fields.shader_size & 3u) == 0, "invalid shader size"); + GuestMemory::CheckRange(shader, fields.header_size, 1); + const auto* code = const_cast(fields.code); + GuestMemory::CheckRange(code, fields.shader_size, 256); + ShaderSnapshot snapshot{reinterpret_cast(code), reinterpret_cast(shader), fields.type, {}, {}}; + snapshot.code.resize(fields.shader_size / sizeof(std::uint32_t)); + std::memcpy(snapshot.code.data(), code, fields.shader_size); + snapshot.header.resize(fields.header_size); + std::memcpy(snapshot.header.data(), static_cast(shader), fields.header_size); static const char* traceRegs = std::getenv("APS5_TRACE_SHADER_REGS"); if (traceRegs != nullptr && (std::string(traceRegs) == "all" || std::strtoull(traceRegs, nullptr, 16) == snapshot.codeAddress)) { - std::fprintf(stderr, "[shader] 0x%llx type %u cx", static_cast(snapshot.codeAddress), shader->type); - for (std::uint32_t i = 0; i < shader->num_cx_registers && shader->cx_registers != nullptr; ++i) std::fprintf(stderr, " %x=%08x", shader->cx_registers[i].offset, shader->cx_registers[i].value); + const auto print = [](const ShaderRegister* registers, std::uint32_t count) { + for (std::uint32_t i = 0; i < count && registers != nullptr; ++i) { + ShaderRegister value; + std::memcpy(&value, static_cast(registers + i), sizeof(value)); + std::fprintf(stderr, " %x=%08x", value.offset, value.value); + } + }; + std::fprintf(stderr, "[shader] 0x%llx type %u cx", static_cast(snapshot.codeAddress), fields.type); + print(fields.cx_registers, fields.num_cx_registers); std::fprintf(stderr, " sh"); - for (std::uint32_t i = 0; i < shader->num_sh_registers && shader->sh_registers != nullptr; ++i) std::fprintf(stderr, " %x=%08x", shader->sh_registers[i].offset, shader->sh_registers[i].value); + print(fields.sh_registers, fields.num_sh_registers); std::fprintf(stderr, "\n"); } std::lock_guard lock(mutex); diff --git a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp index 1e7f5d2a1..d611db473 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp @@ -11,7 +11,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -374,6 +376,34 @@ void testMultiSubmissions() { } } +void testShaderHeaderAlignment() { + alignas(256) static const std::array code{0xbf810000}; + alignas(8) static std::array storage{}; + Shader shader{}; + shader.file_header = 0x34333231; + shader.version = 0x18; + shader.header_size = sizeof(Shader); + shader.shader_size = sizeof(code); + shader.code = code.data(); + const auto at = [](std::size_t offset, const Shader& fields) { + std::memcpy(storage.data() + offset, &fields, sizeof(fields)); + return reinterpret_cast(storage.data() + offset); + }; + for (const std::size_t offset : {0, 4, 1}) AgcDriverRegisterShader_nid_postfix(at(offset, shader)); + const auto refused = [](const std::string& message, const char* reason) { check(message.find(reason) != std::string::npos, message.c_str()); }; + refused(expectFailure([] { AgcDriverRegisterShader_nid_postfix(nullptr); }), "null or misaligned address"); + refused(expectFailure([] { AgcDriverRegisterShader_nid_postfix(reinterpret_cast(0x1001)); }), "not readable"); + Shader misplaced = shader; + misplaced.code = code.data() + 1; + refused(expectFailure([&] { AgcDriverRegisterShader_nid_postfix(at(4, misplaced)); }), "null or misaligned address"); + Shader older = shader; + older.version = 0x17; + refused(expectFailure([&] { AgcDriverRegisterShader_nid_postfix(at(1, older)); }), "invalid shader header"); + Shader truncated = shader; + truncated.header_size = sizeof(Shader) - 4; + refused(expectFailure([&] { AgcDriverRegisterShader_nid_postfix(at(4, truncated)); }), "smaller than its fixed fields"); +} + void testWorkerFailure() { std::array words{0xc0031500, 1, 1, 1, 0x41}; Packet packet{words.data(), static_cast(words.size()), 0, {}}; @@ -470,6 +500,7 @@ int main() { testWaitFreeSubmissionBehindHeldOne(); testWaitFreeSubmissionTheCpuWaitsFor(); testMultiSubmissions(); + testShaderHeaderAlignment(); testWorkerFailure(); check(expectFailure([] { LibcRunShutdown_nid_postfix(); }).find("required shader register") != std::string::npos, "shutdown lost worker failure"); std::puts("AGC driver submit tests passed"); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index dbd9ee2f3..dd9d86092 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -165,6 +165,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceShareGetCurrentStatus](../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - validation (feature flag non-zero, status non-null) and the all-zero 16-byte status taken from the [KytyPS5](https://github.com/KytyPS5/KytyPS5) reimplementation, not confirmed on a PS5 title; the meaning of the recording status values is unknown - [sceAgcDcbSetZPassPredicationEnableGetSize, sceAgcDcbSetPredicationDisableGetSize, sceAgcDcbSetBoolPredicationEnableGetSize](../../core/libs/prx/libSceAgc/DcbState/src/Predication.cpp) (libSceAgc) - the builders are inline in the SDK and not exported; the size is assumed to be that of the SET_PREDICATION packet `sceAgcDcbSetPredication` writes (16 bytes) - [Resource registration](../../core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp) (libSceAgcDriver) - registration always fails with `RESOURCE_REGISTRATION_UNAVAILABLE`, so the owner and resource queries, `sceAgcDriverSetResourceUserData`, `sceAgcDriverFindResourcesPublic` and `sceAgcDriverUnregisterAllResourcesForOwner` return the same error without reading their arguments. Their parameter lists follow the libSceGnmDriver counterparts and are unverified. `sceAgcDriverGetResourceRegistrationMaxNameLength` writes 0xfc and returns 0, as [prosper](https://github.com/mattias800/prosper) does from a title's registration configuration; the `uint32_t*` parameter matches the SDK wrapper `sce::Agc::ResourceRegistration::getMaxNameLength(unsigned int*)`. sharpemu writes 256 and KytyPS5 returns `RESOURCE_REGISTRATION_UNAVAILABLE`, so the value is unverified; a null output throws +- [Shader registration](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - a shader header is accepted at any address: PPSA26344 passes headers 4 and 1 bytes past an 8-byte boundary; whether the console refuses an alignment is unknown - [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 - [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 - [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