From fbf619ef0d05fe28a16e2407e6f39de7a952b6fd Mon Sep 17 00:00:00 2001 From: pushkarreddyy <212133856+pushkarreddyy@users.noreply.github.com> Date: Tue, 13 Oct 2026 20:28:23 +0530 Subject: [PATCH] feat(libSceAgc): ignore address argument on non-sample-dump events in sceAgcDcbEventWrite --- core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp | 1 - core/libs/prx/libSceAgc/tests/Commands.cpp | 44 ++++++++++++++++++++ docs/dev/debt/libSceAgc.md | 1 + 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp b/core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp index e7271811c..96c45f0d3 100644 --- a/core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp +++ b/core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp @@ -36,7 +36,6 @@ std::uint32_t* APS5_VABI sceAgcDcbEventWrite(CommandBuffer* buf, std::uint8_t ev Agc::Command::CheckAddress(guestAddress, 8, __func__); return Agc::Command::Emit(buf, 0x46u, {0x100u | eventType, static_cast(guestAddress), static_cast(guestAddress >> 32u)}, __func__); } - Agc::Command::Require(address == nullptr, __func__, "this event does not use an address"); const auto eventIndex = eventType == 7 || eventType == 15 || eventType == 16 ? 0x400u : 0u; return Agc::Command::Emit(buf, 0x46u, {eventIndex | eventType}, __func__); } diff --git a/core/libs/prx/libSceAgc/tests/Commands.cpp b/core/libs/prx/libSceAgc/tests/Commands.cpp index 03aeff906..6fc0d679e 100644 --- a/core/libs/prx/libSceAgc/tests/Commands.cpp +++ b/core/libs/prx/libSceAgc/tests/Commands.cpp @@ -10,6 +10,8 @@ #include #include +extern "C" std::uint32_t* APS5_VABI sceAgcDcbEventWrite(CommandBuffer* buf, std::uint8_t eventType, const volatile void* address); +extern "C" std::uint64_t APS5_VABI sceAgcDcbEventWriteGetSize(std::uint8_t eventType); extern "C" std::uint32_t* APS5_VABI sceAgcDcbResetQueue(CommandBuffer* buf, std::uint32_t op, std::uint32_t state); extern "C" std::uint32_t* APS5_VABI sceAgcDcbClearState(CommandBuffer* buf, std::uint32_t command); extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetFlip(CommandBuffer* buf, std::uint32_t handle, std::int32_t index, std::uint32_t mode, std::int64_t argument); @@ -641,6 +643,47 @@ void testDefaults() { expectFailure([] { Agc::Command::GetRegisterDefaults(0xffffffffu, true, __func__); }); } +void testEventWrite() { + Storage storage; + const auto* samplePacket = sceAgcDcbEventWrite(&storage.buffer, 0x38, reinterpret_cast(0x1000)); + check(samplePacket == storage.words.data(), "incorrect event 0x38 return pointer"); + check(storage.words[0] == 0xc0024600u, "incorrect event 0x38 packet header"); + check(storage.words[1] == (0x100u | 0x38u), "incorrect event 0x38 first payload word"); + check(storage.words[2] == 0x1000u, "incorrect event 0x38 low address word"); + check(storage.words[3] == 0u, "incorrect event 0x38 high address word"); + check(sceAgcDcbEventWriteGetSize(0x38) == 16, "incorrect event 0x38 size"); + check(sceAgcDcbEventWriteGetSize(0x39) == 16, "incorrect event 0x39 size"); + + Storage sample39; + sceAgcDcbEventWrite(&sample39.buffer, 0x39, reinterpret_cast(0x2008)); + check(sample39.words[0] == 0xc0024600u && sample39.words[1] == (0x100u | 0x39u) && sample39.words[2] == 0x2008u && sample39.words[3] == 0u, "incorrect event 0x39 packet"); + + expectFailure([&] { sceAgcDcbEventWrite(&storage.buffer, 0x38, reinterpret_cast(0x1001)); }); + expectFailure([&] { sceAgcDcbEventWrite(&storage.buffer, 0x40, nullptr); }); + + Storage partialFlush; + sceAgcDcbEventWrite(&partialFlush.buffer, 7, nullptr); + check(partialFlush.words[0] == 0xc0004600u && partialFlush.words[1] == 0x407u, "incorrect event 7 partial flush packet"); + check(sceAgcDcbEventWriteGetSize(7) == 8, "incorrect event 7 size"); + + Storage partialFlush15; + sceAgcDcbEventWrite(&partialFlush15.buffer, 15, nullptr); + check(partialFlush15.words[0] == 0xc0004600u && partialFlush15.words[1] == 0x40fu, "incorrect event 15 partial flush packet"); + + Storage partialFlush16; + sceAgcDcbEventWrite(&partialFlush16.buffer, 16, nullptr); + check(partialFlush16.words[0] == 0xc0004600u && partialFlush16.words[1] == 0x410u, "incorrect event 16 partial flush packet"); + + Storage cacheFlushNull; + sceAgcDcbEventWrite(&cacheFlushNull.buffer, 0x16, nullptr); + check(cacheFlushNull.words[0] == 0xc0004600u && cacheFlushNull.words[1] == 0x16u, "incorrect event 0x16 null address packet"); + check(sceAgcDcbEventWriteGetSize(0x16) == 8, "incorrect event 0x16 size"); + + Storage cacheFlushWithAddress; + sceAgcDcbEventWrite(&cacheFlushWithAddress.buffer, 0x16, reinterpret_cast(0x2d2a10000ull)); + check(cacheFlushWithAddress.words[0] == 0xc0004600u && cacheFlushWithAddress.words[1] == 0x16u, "incorrect event 0x16 with address packet"); +} + } int main(int argc, char** argv) { @@ -652,6 +695,7 @@ int main(int argc, char** argv) { return 0; } testPackets(); + testEventWrite(); testNop(); testClearState(); testIndexedIndirectDraws(); diff --git a/docs/dev/debt/libSceAgc.md b/docs/dev/debt/libSceAgc.md index cf540b7c5..2a4136080 100644 --- a/docs/dev/debt/libSceAgc.md +++ b/docs/dev/debt/libSceAgc.md @@ -38,3 +38,4 @@ - [sceAgcDcbSetWorkloadsActive and sceAgcDcbSetWorkloadComplete](../../../core/libs/prx/libSceAgc/DcbState/src/Workload.cpp) (libSceAgc) and [sceAgcDriverRegisterWorkloadStream](../../../core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp) (libSceAgcDriver) - the driver call records only the stream id (1 to 31; it returns 0 for any argument and keeps no stream record), and the DCB calls write the packets [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/agc.cpp) writes: an 18-dword (active) or 12-dword (complete) `IT_NOP` packet with the stream id, and for active the 64-bit mask of the workload ids, for complete the workload id and the 64-bit clear mask of that id. Both return null for a null command buffer, an unregistered or out-of-range stream, a workload id above 63, and for active a duplicate id or a count of 0 or above 63. The layouts and checks come from KytyPS5 alone; the console oracle in [prosper](https://github.com/mattias800/prosper/blob/main/prosper/tests/data/console_oracle/agcbuild.golden.tsv) measures the null return only for unregistered streams, not the packet contents of a registered one. The error codes the console driver returns for an invalid or repeated registration are unknown, so registration never fails here. PPSA29866 calls sceAgcDcbSetWorkloadsActive while building its command buffers (sceAgcDcbSetWorkloadComplete was not observed); nothing else was observed. - [sceAgcDcbSetWorkloadStreamInactive](../../../core/libs/prx/libSceAgc/DcbState/src/Workload.cpp) and [sceAgcAcbSetWorkloadsActive, sceAgcAcbSetWorkloadComplete, sceAgcAcbSetWorkloadStreamInactive](../../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - return null without writing to the buffer for a null command buffer or a stream that is not registered (out-of-range ids included), and throw for a registered stream. The console oracle in prosper measured only that null return, with no stream registered, three argument patterns each and nothing written ([ACB](https://github.com/mattias800/prosper/blob/df074498e56c0df22d74f9c6622e4709bc1c9a19/prosper/tests/data/console_oracle/agcbuild.golden.tsv#L48-L56), [DCB](https://github.com/mattias800/prosper/blob/df074498e56c0df22d74f9c6622e4709bc1c9a19/prosper/tests/data/console_oracle/agcbuild.golden.tsv#L196-L204)); the packet a registered stream gets is not measured. KytyPS5 has no body for these four, and its `IT_NOP` layout for the DCB active and complete calls differs from the SET_UCONFIG_REG, WRITE_DATA and SET_WORKLOAD sequence [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/agc_workload_packet.c) derives from the driver, so neither is taken. The `(buf, streamId)`, `(buf, streamId, workloadId)` and `(buf, streamId, workloadIds, workloadCount)` signatures are those of the DCB calls in [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c#L2317-L2384), which says the ACB calls use the same driver builders; no ACB declaration was found. `sceAgcDcbSetWorkloadsActive` checks the stream before it reads the workload ids, as OpenAGC does: the console returned null for stream 1 with the ids at address 2 ([measured](https://github.com/mattias800/prosper/blob/df074498e56c0df22d74f9c6622e4709bc1c9a19/prosper/tests/data/console_oracle/agcbuild.golden.tsv#L203)) without faulting - [sceAgcCreatePrimState](../../../core/libs/prx/libSceAgc/Shader/src/PrimState.cpp) (libSceAgc) - the `ge_user_vgpr_en` special is passed to the user configuration registers at whatever offset the shader header stores, as the SPRX does according to [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c#L1181-L1252), which copies all four specials verbatim (KytyPS5 stops on any other offset). PPSA99039, PPSA99505, PPSA99515 and PPSA99997 build their headers by hand with it at 0x25c (`GE_USER_VGPR1`) instead of 0x262 (`GE_USER_VGPR_EN`), value 0, and run on the console; it is not moved to 0x262, and the driver reads neither register. `ge_cntl`, `vgt_shader_stages_en` and a used `vgt_gs_out_prim_type` must still be at their own offsets, since the driver reads those registers there, so a header that stores one elsewhere throws naming the offset, although by OpenAGC the SPRX would copy it as well +- [sceAgcDcbEventWrite](../../../core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp) (libSceAgc) - non-sample-dump event types (any event other than 0x38 and 0x39) emit a 2-dword PM4 packet 0x46 without address dwords; the address argument is ignored for these events. Void Terrarium (PPSA03061, #5055) passes a non-null address buffer (0x00000002d2a10000) with event type 0x16 (cache flush).