feat(libSceAgc): implement the async COND_EXEC patches

sceAgcAsyncCondExecPatchSetCommandAddress and
sceAgcAsyncCondExecPatchSetEnd patch the COND_EXEC packet like their
graphics counterparts, since sceAgcAcbCondExec writes the same packet
as sceAgcDcbCondExec. Both variants share one implementation, as the
REWIND patches do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
hugodearaujo 2026-10-06 17:31:44 -03:00
parent 5b359092d8
commit 47fa19e15c
4 changed files with 63 additions and 37 deletions

View File

@ -291,6 +291,7 @@ add_test_executable(agc_cond_exec_patch_tests EXCLUDE_FROM_ALL
Command/src/Memory.cpp
Command/src/Control.cpp
DcbFlow/src/Sync.cpp
Acb/src/Sync.cpp
Patch/src/CondExec.cpp
)
target_include_directories(agc_cond_exec_patch_tests PRIVATE ${LIBS_INCLUDE_DIR})

View File

@ -7,40 +7,46 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
namespace {
int APS5_VABI sceAgcCondExecPatchSetCommandAddress(uint32_t* cmd, const volatile uint32_t* command) {
Agc::Command::ValidatePacket(cmd, 0x22u, 5, __func__);
int SetCommandAddress(std::uint32_t* cmd, const volatile std::uint32_t* command, const char* function) {
Agc::Command::ValidatePacket(cmd, 0x22u, 5, function);
const auto address = reinterpret_cast<std::uintptr_t>(command);
Agc::Command::CheckAddress(address, 4, __func__);
Agc::Command::CheckAddress(address, 4, function);
cmd[1] = (cmd[1] & 3u) | (static_cast<std::uint32_t>(address) & ~3u);
cmd[2] = static_cast<std::uint32_t>(address >> 32u);
return 0;
}
int APS5_VABI sceAgcCondExecPatchSetEnd(uint32_t* cmd, const volatile uint32_t* buffer) {
Agc::Command::ValidatePacket(cmd, 0x22u, 5, __func__);
int SetEnd(std::uint32_t* cmd, const volatile std::uint32_t* buffer, const char* function) {
Agc::Command::ValidatePacket(cmd, 0x22u, 5, function);
const auto end = reinterpret_cast<std::uintptr_t>(buffer);
const auto packetEnd = reinterpret_cast<std::uintptr_t>(cmd + 5);
Agc::Command::CheckAddress(end, 4, __func__);
Agc::Command::CheckAddress(end, 4, function);
const auto numDwords = (end - packetEnd) / sizeof(std::uint32_t);
Agc::Command::Require(numDwords <= 0x3fffu, __func__, "conditional execution range is too large");
Agc::Command::Require(numDwords <= 0x3fffu, function, "conditional execution range is too large");
cmd[4] = (cmd[4] & ~0x3fffu) | static_cast<std::uint32_t>(numDwords);
return 0;
}
}
extern "C" {
int APS5_VABI sceAgcCondExecPatchSetCommandAddress(uint32_t* cmd, const volatile uint32_t* command) {
return SetCommandAddress(cmd, command, __func__);
}
int APS5_VABI sceAgcCondExecPatchSetEnd(uint32_t* cmd, const volatile uint32_t* buffer) {
return SetEnd(cmd, buffer, __func__);
}
int APS5_VABI sceAgcAsyncCondExecPatchSetCommandAddress(std::uint32_t* cmd, const volatile std::uint32_t* command) {
(void)cmd;
(void)command;
NotImplemented_nid_no_patch(__func__);
return 0;
return SetCommandAddress(cmd, command, __func__);
}
int APS5_VABI sceAgcAsyncCondExecPatchSetEnd(std::uint32_t* cmd, const volatile std::uint32_t* buffer) {
(void)cmd;
(void)buffer;
NotImplemented_nid_no_patch(__func__);
return 0;
return SetEnd(cmd, buffer, __func__);
}
}

View File

@ -9,14 +9,26 @@
extern "C" {
std::uint32_t* APS5_VABI sceAgcDcbCondExec(CommandBuffer*, const volatile std::uint32_t*, std::uint32_t);
std::uint32_t* APS5_VABI sceAgcAcbCondExec(CommandBuffer*, const volatile std::uint32_t*, std::uint32_t);
int APS5_VABI sceAgcCondExecPatchSetCommandAddress(std::uint32_t*, const volatile std::uint32_t*);
int APS5_VABI sceAgcCondExecPatchSetEnd(std::uint32_t*, const volatile std::uint32_t*);
int APS5_VABI sceAgcAsyncCondExecPatchSetCommandAddress(std::uint32_t*, const volatile std::uint32_t*);
int APS5_VABI sceAgcAsyncCondExecPatchSetEnd(std::uint32_t*, const volatile std::uint32_t*);
}
namespace {
constexpr std::uint32_t Sentinel = 0xabcdef01u;
using CondExecWriter = std::uint32_t* (APS5_VABI *)(CommandBuffer*, const volatile std::uint32_t*, std::uint32_t);
using PatchFunction = int (APS5_VABI *)(std::uint32_t*, const volatile std::uint32_t*);
struct Variant {
CondExecWriter write;
PatchFunction setEnd;
PatchFunction setCommandAddress;
};
void check(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
@ -41,9 +53,9 @@ struct Storage {
std::uint32_t condition = 1;
std::uint32_t* packet = nullptr;
Storage() {
explicit Storage(CondExecWriter write) {
words.fill(Sentinel);
packet = sceAgcDcbCondExec(&buffer, &condition, 3);
packet = write(&buffer, &condition, 3);
check(packet == words.data() && packet[0] == 0xc0032200u && packet[4] == 3, "unexpected COND_EXEC packet");
}
};
@ -53,51 +65,57 @@ std::uintptr_t endAfter(const std::uint32_t* packet, std::uintptr_t numDwords) {
}
template <typename TAction>
void expectUnchanged(TAction action) {
Storage storage;
void expectUnchanged(const Variant& variant, TAction action) {
Storage storage(variant.write);
const auto before = storage.words;
expectFailure([&] { action(storage.packet); });
check(storage.words == before, "failed patch modified the packet");
}
void testSetEnd() {
void testSetEnd(const Variant& variant) {
const auto setEnd = variant.setEnd;
for (const std::uintptr_t numDwords : {0u, 1u, 7u, 0x3fffu}) {
Storage storage;
Storage storage(variant.write);
const auto before = storage.words;
check(sceAgcCondExecPatchSetEnd(storage.packet, at(endAfter(storage.packet, numDwords))) == 0, "SetEnd failed");
check(setEnd(storage.packet, at(endAfter(storage.packet, numDwords))) == 0, "SetEnd failed");
check(storage.packet[4] == numDwords, "SetEnd wrote the wrong dword count");
check(std::equal(before.begin(), before.begin() + 4, storage.words.begin()), "SetEnd modified other packet words");
check(std::equal(before.begin() + 5, before.end(), storage.words.begin() + 5), "SetEnd modified following words");
}
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetEnd(packet, at(endAfter(packet, 0x4000u))); });
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetEnd(packet, packet + 4); });
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetEnd(packet, nullptr); });
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetEnd(packet, at(endAfter(packet, 2) + 2)); });
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetEnd(packet + 1, at(endAfter(packet, 2))); });
expectUnchanged(variant, [=](std::uint32_t* packet) { setEnd(packet, at(endAfter(packet, 0x4000u))); });
expectUnchanged(variant, [=](std::uint32_t* packet) { setEnd(packet, packet + 4); });
expectUnchanged(variant, [=](std::uint32_t* packet) { setEnd(packet, nullptr); });
expectUnchanged(variant, [=](std::uint32_t* packet) { setEnd(packet, at(endAfter(packet, 2) + 2)); });
expectUnchanged(variant, [=](std::uint32_t* packet) { setEnd(packet + 1, at(endAfter(packet, 2))); });
}
void testSetCommandAddress() {
void testSetCommandAddress(const Variant& variant) {
const auto setCommandAddress = variant.setCommandAddress;
for (const std::uintptr_t address : {std::uintptr_t{0x0000123456789abcu}, std::uintptr_t{4u}, std::uintptr_t{0x0000fffffffffffcu}}) {
Storage storage;
Storage storage(variant.write);
storage.packet[1] |= 3u;
const auto before = storage.words;
check(sceAgcCondExecPatchSetCommandAddress(storage.packet, at(address)) == 0, "SetCommandAddress failed");
check(storage.packet[1] == static_cast<std::uint32_t>(address) && storage.packet[2] == static_cast<std::uint32_t>(address >> 32u), "SetCommandAddress wrote the wrong address");
check(setCommandAddress(storage.packet, at(address)) == 0, "SetCommandAddress failed");
check(storage.packet[1] == (static_cast<std::uint32_t>(address) | 3u) && storage.packet[2] == static_cast<std::uint32_t>(address >> 32u), "SetCommandAddress wrote the wrong address");
check(storage.packet[0] == before[0] && storage.packet[3] == before[3] && storage.packet[4] == before[4], "SetCommandAddress modified other packet words");
check(std::equal(before.begin() + 5, before.end(), storage.words.begin() + 5), "SetCommandAddress modified following words");
}
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetCommandAddress(packet, nullptr); });
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetCommandAddress(packet, at(0x1002u)); });
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetCommandAddress(packet + 1, at(0x1000u)); });
expectUnchanged(variant, [=](std::uint32_t* packet) { setCommandAddress(packet, nullptr); });
expectUnchanged(variant, [=](std::uint32_t* packet) { setCommandAddress(packet, at(0x1002u)); });
expectUnchanged(variant, [=](std::uint32_t* packet) { setCommandAddress(packet + 1, at(0x1000u)); });
}
}
int main() {
try {
testSetEnd();
testSetCommandAddress();
for (const Variant& variant : {Variant{sceAgcDcbCondExec, sceAgcCondExecPatchSetEnd, sceAgcCondExecPatchSetCommandAddress},
Variant{sceAgcAcbCondExec, sceAgcAsyncCondExecPatchSetEnd, sceAgcAsyncCondExecPatchSetCommandAddress}}) {
testSetEnd(variant);
testSetCommandAddress(variant);
}
std::puts("AGC COND_EXEC patch tests passed");
return 0;
} catch (const std::exception& error) {

View File

@ -61,6 +61,7 @@ Throughout the project, every function at every stage either **does exactly what
- [dbOlWdppb4o](../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) - unknown name; same arguments as `sceAgcCreateInterpolantMapping`; the `SPI_PS_INPUT_CNTL` bits for `is_f16` 2 inputs follow KytyPS5, not confirmed on hardware
- [V++UgBtQhn0](../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - unknown name
- [sceAgcGetDataPacketPayloadRange](../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - signature, the `{base, size in bytes}` output and the type 0 range (header + 1, one dword longer than the type 1 payload) from KytyPS5 only, matching `V++UgBtQhn0`; prosper returns header + 2 for both types from that address function
- [sceAgcAsyncCondExecPatchSetCommandAddress, sceAgcAsyncCondExecPatchSetEnd](../../core/libs/prx/libSceAgc/Patch/src/CondExec.cpp) (libSceAgc) - no reference has a body for them; they are assumed to patch the COND_EXEC packet like `sceAgcCondExecPatchSetCommandAddress` and `sceAgcCondExecPatchSetEnd`, since `sceAgcAcbCondExec` writes the same packet as `sceAgcDcbCondExec`, as `sceAgcAsyncRewindPatchSetRewindState` does for REWIND
- [gQkqkLttcpw](../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - unknown name, signature
- [sceAgcDcbPrimeUtcl2](../../core/libs/prx/libSceAgc/DcbState/src/Display.cpp) (libSceAgc) - assumed to write the packet `sceAgcAcbPrimeUtcl2` writes: PRIME_UTCL2 has the same 5 dwords on the graphics and compute rings. Not confirmed on a title. Both write a NOP of that size, as there is no TLB to prime
- [sceKernelInternalMemoryGetModuleSegmentInfo](../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature