agc: implement sceAgcSetNop (#1343)

* agc: implement sceAgcSetNop

Star Wars Outlaws calls sceAgcSetNop (K2mciNVxUCE) and crashes right after
the unresolved stub returns, without the packet being disabled.

The call turns a packet into a NOP of the same length. Clear the emulator's
custom NOP code as well, so packets that the HLE encodes as NOPs, such as
end-of-pipe actions, stop executing too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* agc: keep packets disabled by sceAgcSetNop from reading as markers

A disabled packet whose first payload word carries the 0x6875 marker
signature still reached the marker handler, which can flip or stop on an
unknown marker ID. Clear the signature so the packet is skipped like any
other NOP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* agc: tidy SetNop logging and comments

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
This commit is contained in:
popcoder565 2026-10-10 21:00:09 -04:00 committed by GitHub
parent a6f4e8e7f4
commit fbea51c684
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 53 additions and 0 deletions

View File

@ -3714,6 +3714,20 @@ int KYTY_SYSV_ABI AgcSetPacketPredication(uint32_t* packet, uint32_t predication
return OK;
}
int KYTY_SYSV_ABI AgcSetNop(uint32_t* packet) {
PRINT_NAME();
const auto size = AgcGetPacketSize(packet);
packet[0] = (packet[0] & 0xffff0003u) | (Pm4::IT_NOP << 8u);
// Prevent the disabled payload from being treated as an executable marker.
if (size > 1 && (packet[1] & 0xffff0000u) == 0x68750000u) {
packet[1] &= 0xffffu;
}
return OK;
}
int KYTY_SYSV_ABI AgcSetRangePredication(uint32_t* start, const volatile uint32_t* end,
uint32_t predication) {
PRINT_NAME();

View File

@ -270,6 +270,7 @@ uint32_t KYTY_SYSV_ABI AgcGetPacketSize(uint32_t* packet);
int KYTY_SYSV_ABI AgcSetPacketPredication(uint32_t* packet, uint32_t predication);
int KYTY_SYSV_ABI AgcSetRangePredication(uint32_t* start, const volatile uint32_t* end,
uint32_t predication);
int KYTY_SYSV_ABI AgcSetNop(uint32_t* packet);
int KYTY_SYSV_ABI AgcRewindPatchSetRewindState(uint32_t* cmd, uint8_t state);
int KYTY_SYSV_ABI AgcCondExecPatchSetEnd(uint32_t* cmd, const volatile uint32_t* buffer);
int KYTY_SYSV_ABI AgcCondExecPatchSetCommandAddress(uint32_t* cmd,

View File

@ -143,6 +143,7 @@ LIB_DEFINE(InitAgcDriver_1) {
LIB_FUNC("Lkf86B98qPc", Gen5::AgcGetPacketSize);
LIB_FUNC("w6Dj1VJt5qY", Gen5::AgcSetPacketPredication);
LIB_FUNC("n8vgpaQg6dA", Gen5::AgcSetRangePredication);
LIB_FUNC("K2mciNVxUCE", Gen5::AgcSetNop);
LIB_FUNC("i1jyy49AjXU", Gen5::AgcDcbWriteData);
LIB_FUNC("p9tI+yTvx68", Gen5::AgcDcbWriteDataGetSize);
LIB_FUNC("vuSXe69VILM", Gen5::AgcDcbGetLodStats);

View File

@ -42630,6 +42630,43 @@ void CheckPm4IndirectControlFlow(RenderContext &renderer) {
Gen5::AgcCbNop(reinterpret_cast<Gen5::CommandBuffer*>(&payload_dcb), 3) == payload.data() &&
payload == std::array<uint32_t, 3>{0xc0011000u, 0x12345678u, 0x9abcdef0u},
"NOP emitter overwrote the caller's reserved payload");
alignas(8) uint64_t released = 0;
std::array<uint32_t, 20> disabled_commands{};
const auto disabled_write = write(&returned, 99);
std::copy(disabled_write.begin(), disabled_write.end(), disabled_commands.begin());
disabled_commands[5] = KYTY_PM4(2, Pm4::IT_NUM_INSTANCES, 0);
disabled_commands[6] = 0x68750123u;
CommandBufferLayout release_dcb{disabled_commands.data() + 7, disabled_commands.data() + 15,
disabled_commands.data() + 7, disabled_commands.data() + 15, nullptr, nullptr, 0};
auto* release = Gen5::AgcCbReleaseMem(
reinterpret_cast<Gen5::CommandBuffer*>(&release_dcb), 0x28, 0, 0, 0,
reinterpret_cast<const volatile Gen5::Label*>(&released), 2, 77, 0, 0, 0, 0);
const auto enabled_write = write(&selected, 88);
std::copy(enabled_write.begin(), enabled_write.end(), disabled_commands.begin() + 15);
const auto unpatched_commands = disabled_commands;
Require(name, "SetNop packets",
release == disabled_commands.data() + 7 &&
Gen5::AgcSetNop(disabled_commands.data()) == 0 &&
Gen5::AgcSetNop(disabled_commands.data() + 5) == 0 && Gen5::AgcSetNop(release) == 0 &&
disabled_commands[0] == ((disabled_write[0] & 0xffff00ffu) | (Pm4::IT_NOP << 8u)) &&
disabled_commands[5] == KYTY_PM4(2, Pm4::IT_NOP, Pm4::R_ZERO) &&
disabled_commands[6] == 0x123u &&
release[0] == KYTY_PM4(8, Pm4::IT_NOP, Pm4::R_ZERO) &&
unpatched_commands[7] == KYTY_PM4(8, Pm4::IT_NOP, Pm4::R_RELEASE_MEM) &&
Gen5::AgcGetPacketSize(disabled_commands.data()) == disabled_write.size() &&
Gen5::AgcGetPacketSize(disabled_commands.data() + 5) == 2 &&
Gen5::AgcGetPacketSize(release) == 8 &&
std::equal(disabled_commands.begin() + 1, disabled_commands.begin() + 5,
unpatched_commands.begin() + 1) &&
std::equal(disabled_commands.begin() + 8, disabled_commands.end(),
unpatched_commands.begin() + 8),
"SetNop changed a packet length, kept its operation or marker, or touched its payload");
selected = returned = 0;
Pm4Execution disabled_execution;
Require(name, "SetNop execution",
processor.Process(disabled_execution, disabled_commands) == Pm4ProcessResult::Complete &&
returned == 0 && released == 0 && selected == 88,
"a disabled packet still executed or hid the packet after it");
const auto then_commands = write(&selected, 11);
const auto else_commands = write(&selected, 33);
const auto branch_suffix = write(&selected, 44);