diff --git a/core/libs/prx/libSceAgc/CMakeLists.txt b/core/libs/prx/libSceAgc/CMakeLists.txt index d3bb48f0b..ac7c86750 100644 --- a/core/libs/prx/libSceAgc/CMakeLists.txt +++ b/core/libs/prx/libSceAgc/CMakeLists.txt @@ -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}) diff --git a/core/libs/prx/libSceAgc/Patch/src/CondExec.cpp b/core/libs/prx/libSceAgc/Patch/src/CondExec.cpp index bae2206ad..7fff4987c 100644 --- a/core/libs/prx/libSceAgc/Patch/src/CondExec.cpp +++ b/core/libs/prx/libSceAgc/Patch/src/CondExec.cpp @@ -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(command); - Agc::Command::CheckAddress(address, 4, __func__); + Agc::Command::CheckAddress(address, 4, function); cmd[1] = (cmd[1] & 3u) | (static_cast(address) & ~3u); cmd[2] = static_cast(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(buffer); const auto packetEnd = reinterpret_cast(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(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__); } } diff --git a/core/libs/prx/libSceAgc/tests/CondExecPatch.cpp b/core/libs/prx/libSceAgc/tests/CondExecPatch.cpp index f338beea7..762f40b1a 100644 --- a/core/libs/prx/libSceAgc/tests/CondExecPatch.cpp +++ b/core/libs/prx/libSceAgc/tests/CondExecPatch.cpp @@ -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 -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(address) && storage.packet[2] == static_cast(address >> 32u), "SetCommandAddress wrote the wrong address"); + check(setCommandAddress(storage.packet, at(address)) == 0, "SetCommandAddress failed"); + check(storage.packet[1] == (static_cast(address) | 3u) && storage.packet[2] == static_cast(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) { diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 645d976a4..7ddfc7644 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -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