fix(agc): patch indirect-buffer continuation links (#1012)

This commit is contained in:
Mathias 2026-10-02 22:51:09 +01:00 committed by GitHub
parent 6cebf7ec56
commit 6cac2b5f47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 81 additions and 8 deletions

View File

@ -10,6 +10,36 @@ public static partial class AgcExports
{
// This partial patches previously constructed AGC command packets.
// Uncatalogued name; the NID and import library identify this AGC export.
// Observed ABI: packet, cache policy, next-buffer address, length in dwords.
// The caller patches the previous DcbJump after allocating its continuation.
#pragma warning disable SHEM006
[SysAbiExport(Nid = "Ikfdt-rIqCE", ExportName = "sceAgcUnknownIkfdt",
Target = Generation.Gen5, LibraryName = "libSceAgc")]
public static int UnknownIkfdt(CpuContext ctx)
{
var packet = ctx[CpuRegister.Rdi];
var cachePolicy = (uint)ctx[CpuRegister.Rsi];
var target = ctx[CpuRegister.Rdx];
var dwords = (uint)ctx[CpuRegister.Rcx];
if (cachePolicy > 3 || dwords > 0xFFFFFu ||
!TryGetPacketIdentity(ctx, packet, out var opcode, out _) ||
opcode != ItIndirectBuffer ||
!TryReadUInt32(ctx, packet, out var header) ||
((header >> 16) & 0x3FFFu) != 2 ||
!TryReadUInt32(ctx, packet + 12, out var control))
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
// Preserve call/chain mode and every other control bit.
control = (control & ~0x300F_FFFFu) | (cachePolicy << 28) | dwords;
return TryWriteUInt32(ctx, packet + 4, (uint)target) &&
TryWriteUInt32(ctx, packet + 8, (uint)(target >> 32) & 0xFFFFu) &&
TryWriteUInt32(ctx, packet + 12, control)
? SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK)
: SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
#pragma warning restore SHEM006
[SysAbiExport(
Nid = "vcmNN+AAXnY",
ExportName = "sceAgcSetCxRegIndirectPatchSetAddress",

View File

@ -214,12 +214,4 @@ public static class GameServiceStubs
Target = Generation.Gen5, LibraryName = "libSceVideoRecording")]
public static int VideoRecordingSetInfo(CpuContext ctx) => Ok(ctx);
// Captured from GTA V Enhanced (PPSA04264); not in the public NID catalog.
// Side-effect-free success — same as unresolved stub behavior that kept boot
// moving; reverse the ABI before writing guest memory.
#pragma warning disable SHEM006
[SysAbiExport(Nid = "Ikfdt-rIqCE", ExportName = "sceUnknownIkfdt",
Target = Generation.Gen5, LibraryName = "libKernel")]
public static int UnknownIkfdt(CpuContext ctx) => Ok(ctx);
#pragma warning restore SHEM006
}

View File

@ -197,6 +197,57 @@ public sealed class AgcCommandBufferChainTests
uint targetDwords)
=> WriteJump(ctx, memory, linkAddress, 1, target, targetDwords);
[Fact]
public void PatchedPlaceholderChain_ReachesContinuation()
{
var memory = new FakeCpuMemory(BaseAddress, MemorySize);
var ctx = new CpuContext(memory, Generation.Gen5);
var nextDwords = WriteUnsatisfiedWait(ctx, memory, SecondLinkAddress);
var rootDwords = WriteChain(ctx, memory, FirstLinkAddress, 0, 0);
ctx[CpuRegister.Rdi] = FirstLinkAddress;
ctx[CpuRegister.Rsi] = 2;
ctx[CpuRegister.Rdx] = SecondLinkAddress;
ctx[CpuRegister.Rcx] = nextDwords;
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, AgcExports.UnknownIkfdt(ctx));
Assert.Equal(0x2F30_0000u | nextDwords, ReadUInt32(memory, FirstLinkAddress + 12));
SubmitDcb(ctx, memory, FirstLinkAddress, rootDwords);
Assert.Equal(1, StreamOf(memory).BlockedQueueCount);
Assert.Equal(SecondLinkAddress, StreamOf(memory).SnapshotBlocked().SampleWaitAddress);
}
[Theory]
[InlineData(0u)]
[InlineData(1u)]
public void JumpPatch_PreservesModeAndEncodesAddress(uint mode)
{
var memory = new FakeCpuMemory(BaseAddress, MemorySize);
var ctx = new CpuContext(memory, Generation.Gen5);
WriteJump(ctx, memory, FirstLinkAddress, mode, 0, 0);
ctx[CpuRegister.Rdi] = FirstLinkAddress;
ctx[CpuRegister.Rsi] = 3;
ctx[CpuRegister.Rdx] = 0xABCD_1234_5678;
ctx[CpuRegister.Rcx] = 0x123;
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, AgcExports.UnknownIkfdt(ctx));
Assert.Equal(0x1234_5678u, ReadUInt32(memory, FirstLinkAddress + 4));
Assert.Equal(0xABCDu, ReadUInt32(memory, FirstLinkAddress + 8));
Assert.Equal(0x3F20_0123u | (mode << 20), ReadUInt32(memory, FirstLinkAddress + 12));
}
[Fact]
public void JumpPatch_RejectsOtherPacketWithoutChangingIt()
{
var memory = new FakeCpuMemory(BaseAddress, MemorySize);
var ctx = new CpuContext(memory, Generation.Gen5);
WriteUInt32(memory, FirstLinkAddress, 0xC002_1000);
WriteUInt64(memory, FirstLinkAddress + 4, 0xDEAD_BEEF);
ctx[CpuRegister.Rdi] = FirstLinkAddress;
ctx[CpuRegister.Rsi] = 2;
ctx[CpuRegister.Rdx] = SecondLinkAddress;
ctx[CpuRegister.Rcx] = 7;
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT, AgcExports.UnknownIkfdt(ctx));
Assert.Equal(0xDEAD_BEEFul, ReadUInt64(memory, FirstLinkAddress + 4));
}
private static uint WriteJump(
CpuContext ctx,
FakeCpuMemory memory,