diff --git a/core/libs/prx/libSceAgc/CMakeLists.txt b/core/libs/prx/libSceAgc/CMakeLists.txt index dec965a2f..1db3169a0 100644 --- a/core/libs/prx/libSceAgc/CMakeLists.txt +++ b/core/libs/prx/libSceAgc/CMakeLists.txt @@ -68,3 +68,21 @@ target_include_directories(agc_command_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(agc_command_tests PRIVATE libSceAgc libc) configure_windows_unwind(agc_command_tests) configure_windows_unwind(libSceAgc) + +add_executable(agc_register_command_tests EXCLUDE_FROM_ALL + tests/RegisterCommands.cpp + Command/src/Packet.cpp + DcbState/src/Registers.cpp + Patch/src/IndirectRegisters.cpp +) +target_include_directories(agc_register_command_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_register_command_tests PRIVATE libc) +configure_windows_unwind(agc_register_command_tests) +if(BUILD_TESTING) + set_target_properties(agc_register_command_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) + add_test(NAME agc_register_command_tests COMMAND agc_register_command_tests) + if(WIN32) + set_tests_properties(agc_register_command_tests PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgc/Command/include/Packet.hpp b/core/libs/prx/libSceAgc/Command/include/Packet.hpp index f95361fd9..7b92007d8 100644 --- a/core/libs/prx/libSceAgc/Command/include/Packet.hpp +++ b/core/libs/prx/libSceAgc/Command/include/Packet.hpp @@ -21,6 +21,7 @@ std::uint32_t* WriteRegisters(CommandBuffer* buffer, std::uint32_t opcode, const std::uint32_t* WriteIndirectRegisters(CommandBuffer* buffer, std::uint32_t opcode, const volatile ShaderRegister* registers, std::uint32_t count, const char* function); void PatchIndirectAddress(std::uint32_t* packet, std::uint32_t opcode, const volatile ShaderRegister* registers, const char* function); void PatchIndirectCount(std::uint32_t* packet, std::uint32_t opcode, std::uint32_t count, const char* function); +void SetIndirectCount(std::uint32_t* packet, std::uint32_t opcode, std::uint32_t count, const char* function); } diff --git a/core/libs/prx/libSceAgc/Command/src/Packet.cpp b/core/libs/prx/libSceAgc/Command/src/Packet.cpp index b32303b1c..62a12734b 100644 --- a/core/libs/prx/libSceAgc/Command/src/Packet.cpp +++ b/core/libs/prx/libSceAgc/Command/src/Packet.cpp @@ -159,4 +159,11 @@ void PatchIndirectCount(std::uint32_t* packet, std::uint32_t opcode, std::uint32 packet[4] += count; } +void SetIndirectCount(std::uint32_t* packet, std::uint32_t opcode, std::uint32_t count, const char* function) { + ValidatePacket(packet, opcode, 5, function); + Require(packet[3] == 0x80000000u && packet[4] <= 0x3fffu, function, "invalid indirect register packet"); + CheckBits(count, 0x3fffu, function); + packet[4] = count; +} + } diff --git a/core/libs/prx/libSceAgc/DcbState/src/Registers.cpp b/core/libs/prx/libSceAgc/DcbState/src/Registers.cpp index 36c25d7f5..11d3ce528 100644 --- a/core/libs/prx/libSceAgc/DcbState/src/Registers.cpp +++ b/core/libs/prx/libSceAgc/DcbState/src/Registers.cpp @@ -21,9 +21,8 @@ std::uint32_t* APS5_VABI sceAgcDcbSetCxRegistersIndirect(CommandBuffer* buf, con } std::uint32_t APS5_VABI sceAgcDcbSetCxRegistersIndirectGetSize(std::uint32_t numRegs) { - (void)numRegs; - NotImplemented_nid_no_patch(__func__); - return 0; + Agc::Command::CheckBits(numRegs, 0x3fffu, __func__); + return 20; } uint32_t* APS5_VABI sceAgcDcbSetShRegisterDirect(CommandBuffer* buf, ShaderRegister reg) { @@ -56,9 +55,8 @@ std::uint32_t* APS5_VABI sceAgcDcbSetUcRegistersIndirect(CommandBuffer* buf, con } std::uint32_t APS5_VABI sceAgcDcbSetUcRegistersIndirectGetSize(std::uint32_t numRegs) { - (void)numRegs; - NotImplemented_nid_no_patch(__func__); - return 0; + Agc::Command::CheckBits(numRegs, 0x3fffu, __func__); + return 20; } } diff --git a/core/libs/prx/libSceAgc/Patch/src/IndirectRegisters.cpp b/core/libs/prx/libSceAgc/Patch/src/IndirectRegisters.cpp index f14d7b320..ff90be0cd 100644 --- a/core/libs/prx/libSceAgc/Patch/src/IndirectRegisters.cpp +++ b/core/libs/prx/libSceAgc/Patch/src/IndirectRegisters.cpp @@ -19,11 +19,9 @@ int APS5_VABI sceAgcSetCxRegIndirectPatchSetAddress(std::uint32_t* cmd, const vo return 0; } -int APS5_VABI sceAgcSetCxRegIndirectPatchSetNumRegisters(uint32_t* cmd, uint32_t num_regs) { - (void)cmd; - (void)num_regs; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAgcSetCxRegIndirectPatchSetNumRegisters(std::uint32_t* cmd, std::uint32_t numRegs) { + Agc::Command::SetIndirectCount(cmd, 0x9fu, numRegs, __func__); + return 0; } int APS5_VABI sceAgcSetShRegIndirectPatchAddRegisters(std::uint32_t* cmd, std::uint32_t numRegs) { @@ -36,11 +34,9 @@ int APS5_VABI sceAgcSetShRegIndirectPatchSetAddress(std::uint32_t* cmd, const vo return 0; } -int APS5_VABI sceAgcSetShRegIndirectPatchSetNumRegisters(uint32_t* cmd, uint32_t num_regs) { - (void)cmd; - (void)num_regs; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAgcSetShRegIndirectPatchSetNumRegisters(std::uint32_t* cmd, std::uint32_t numRegs) { + Agc::Command::SetIndirectCount(cmd, 0x63u, numRegs, __func__); + return 0; } int APS5_VABI sceAgcSetUcRegIndirectPatchAddRegisters(std::uint32_t* cmd, std::uint32_t numRegs) { @@ -53,11 +49,9 @@ int APS5_VABI sceAgcSetUcRegIndirectPatchSetAddress(std::uint32_t* cmd, const vo return 0; } -int APS5_VABI sceAgcSetUcRegIndirectPatchSetNumRegisters(uint32_t* cmd, uint32_t num_regs) { - (void)cmd; - (void)num_regs; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAgcSetUcRegIndirectPatchSetNumRegisters(std::uint32_t* cmd, std::uint32_t numRegs) { + Agc::Command::SetIndirectCount(cmd, 0x64u, numRegs, __func__); + return 0; } } diff --git a/core/libs/prx/libSceAgc/tests/RegisterCommands.cpp b/core/libs/prx/libSceAgc/tests/RegisterCommands.cpp new file mode 100644 index 000000000..e40249ef6 --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/RegisterCommands.cpp @@ -0,0 +1,116 @@ +#include "prx/libSceAgc/Command/include/Packet.hpp" + +#include +#include +#include +#include + +extern "C" { +std::uint32_t* APS5_VABI sceAgcDcbSetCxRegisterDirect(CommandBuffer*, ShaderRegister); +std::uint32_t* APS5_VABI sceAgcDcbSetShRegisterDirect(CommandBuffer*, ShaderRegister); +std::uint32_t* APS5_VABI sceAgcDcbSetUcRegisterDirect(CommandBuffer*, ShaderRegister); +std::uint32_t APS5_VABI sceAgcDcbSetCxRegisterDirectGetSize(); +std::uint32_t APS5_VABI sceAgcDcbSetShRegisterDirectGetSize(); +std::uint32_t APS5_VABI sceAgcDcbSetUcRegisterDirectGetSize(); +std::uint32_t* APS5_VABI sceAgcDcbSetCxRegistersIndirect(CommandBuffer*, const volatile ShaderRegister*, std::uint32_t); +std::uint32_t* APS5_VABI sceAgcDcbSetShRegistersIndirect(CommandBuffer*, const volatile ShaderRegister*, std::uint32_t); +std::uint32_t* APS5_VABI sceAgcDcbSetUcRegistersIndirect(CommandBuffer*, const volatile ShaderRegister*, std::uint32_t); +std::uint32_t APS5_VABI sceAgcDcbSetCxRegistersIndirectGetSize(std::uint32_t); +std::uint32_t APS5_VABI sceAgcDcbSetShRegistersIndirectGetSize(std::uint32_t); +std::uint32_t APS5_VABI sceAgcDcbSetUcRegistersIndirectGetSize(std::uint32_t); +int APS5_VABI sceAgcSetCxRegIndirectPatchSetNumRegisters(std::uint32_t*, std::uint32_t); +int APS5_VABI sceAgcSetShRegIndirectPatchSetNumRegisters(std::uint32_t*, std::uint32_t); +int APS5_VABI sceAgcSetUcRegIndirectPatchSetNumRegisters(std::uint32_t*, std::uint32_t); +} + +namespace { + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +template +void expectFailure(TAction action) { + try { + action(); + } catch (const std::runtime_error&) { + return; + } + throw std::runtime_error("expected invalid input to fail"); +} + +struct Storage { + std::array words{}; + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; +}; + +void testDirect() { + const std::array writers{sceAgcDcbSetCxRegisterDirect, sceAgcDcbSetShRegisterDirect, sceAgcDcbSetUcRegisterDirect}; + const std::array sizes{sceAgcDcbSetCxRegisterDirectGetSize, sceAgcDcbSetShRegisterDirectGetSize, sceAgcDcbSetUcRegisterDirectGetSize}; + const std::array headers{0xc0016900u, 0xc0017600u, 0xc0017900u}; + for (std::size_t i = 0; i < writers.size(); ++i) { + Storage storage; + storage.words.fill(0xabcdef01u); + auto* packet = writers[i](&storage.buffer, {0xffffu, 0x12345678u}); + const std::array expected{headers[i], 0xffffu, 0x12345678u}; + check(packet == storage.words.data() && std::equal(expected.begin(), expected.end(), packet), "incorrect direct register packet"); + check(storage.buffer.cursor_up == packet + 3 && sizes[i]() == 12, "direct size/cursor mismatch"); + check(packet[3] == 0xabcdef01u, "direct command overwrote following word"); + const auto before = storage.words; + expectFailure([&] { writers[i](&storage.buffer, {0x10000u, 1}); }); + expectFailure([&] { writers[i](nullptr, {0, 1}); }); + storage.buffer.cursor_down = storage.buffer.cursor_up + 2; + expectFailure([&] { writers[i](&storage.buffer, {0, 1}); }); + check(storage.words == before && storage.buffer.cursor_up == packet + 3, "invalid direct write modified buffer"); + } +} + +void testIndirect() { + const std::array writers{sceAgcDcbSetCxRegistersIndirect, sceAgcDcbSetShRegistersIndirect, sceAgcDcbSetUcRegistersIndirect}; + const std::array sizes{sceAgcDcbSetCxRegistersIndirectGetSize, sceAgcDcbSetShRegistersIndirectGetSize, sceAgcDcbSetUcRegistersIndirectGetSize}; + const std::array setters{sceAgcSetCxRegIndirectPatchSetNumRegisters, sceAgcSetShRegIndirectPatchSetNumRegisters, sceAgcSetUcRegIndirectPatchSetNumRegisters}; + const std::array headers{0xc0039f00u, 0xc0036300u, 0xc0036400u}; + ShaderRegister reg{0x10u, 7}; + for (std::size_t i = 0; i < writers.size(); ++i) { + Storage storage; + auto* packet = writers[i](&storage.buffer, ®, 1); + const auto original = storage.words; + check(packet[0] == headers[i] && storage.buffer.cursor_up == packet + 5, "incorrect indirect packet"); + for (const auto count : {0u, 0x3fffu, 2u}) { + check(sizes[i](count) == 20, "indirect size mismatch"); + check(setters[i](packet, count) == 0 && packet[4] == count, "indirect count was not replaced"); + auto expected = original; + expected[4] = count; + check(storage.words == expected, "count setter modified unrelated packet data"); + } + const auto before = storage.words; + for (const auto count : {0x4000u, 0xffffffffu}) { + expectFailure([&] { sizes[i](count); }); + expectFailure([&] { setters[i](packet, count); }); + } + expectFailure([&] { setters[i](nullptr, 0); }); + expectFailure([&] { setters[(i + 1) % setters.size()](packet, 0); }); + check(storage.words == before, "invalid count setter modified packet"); + for (const auto field : {0u, 3u, 4u}) { + storage.words = before; + packet[field] = field == 0 ? headers[i] ^ 0x10000u : 0xffffffffu; + const auto malformed = storage.words; + expectFailure([&] { setters[i](packet, 1); }); + check(storage.words == malformed, "setter modified malformed packet"); + } + } +} + +} + +int main() { + try { + testDirect(); + testIndirect(); + std::puts("AGC register command tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +}