sharpemu/src/SharpEmu.Libs/Agc/AgcExports.ControlFlow.cs

363 lines
13 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
namespace SharpEmu.Libs.Agc;
public static partial class AgcExports
{
// This partial constructs AGC command-buffer control flow.
// Hardware REWIND is a fixed 2-dword header + body (valid bit 31).
[SysAbiExport(
Nid = "QIXCsbipds0",
ExportName = "sceAgcDcbRewindGetSize",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbRewindGetSize(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 2u * sizeof(uint);
return (int)ctx[CpuRegister.Rax];
}
// Writes IT_REWIND. When valid=0 the submit parser suspends until
// sceAgcRewindPatchSetRewindState sets bit 31 on the body dword.
[SysAbiExport(
Nid = "zfcxg-ewMK8",
ExportName = "sceAgcDcbRewind",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbRewind(CpuContext ctx)
{
var dcb = ctx[CpuRegister.Rdi];
// rsi bit0 = valid; bit1 = offload_enable (PM4 body bits 31 / 24).
var flags = ctx[CpuRegister.Rsi];
var valid = (flags & 1UL) != 0;
var offloadEnable = (flags & 2UL) != 0;
if (dcb == 0)
{
return ReturnPointer(ctx, 0);
}
var body = (valid ? RewindValidBit : 0u) |
(offloadEnable ? RewindOffloadEnableBit : 0u);
if (!TryAllocateCommandDwords(ctx, dcb, 2, out var cmd) ||
!ctx.TryWriteUInt32(cmd, Pm4(2, ItRewind, RZero)) ||
!ctx.TryWriteUInt32(cmd + 4, body))
{
return ReturnPointer(ctx, 0);
}
TraceAgc($"agc.dcb_rewind buf=0x{dcb:X16} cmd=0x{cmd:X16} valid={valid} offload={offloadEnable}");
return ReturnPointer(ctx, cmd);
}
[SysAbiExport(
Nid = "DwICrVxerkY",
ExportName = "sceAgcAcbRewind",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int AcbRewind(CpuContext ctx)
{
var acb = ctx[CpuRegister.Rdi];
var flags = ctx[CpuRegister.Rsi];
var valid = (flags & 1UL) != 0;
var offloadEnable = (flags & 2UL) != 0;
if (acb == 0)
{
return ReturnPointer(ctx, 0);
}
var body = (valid ? RewindValidBit : 0u) |
(offloadEnable ? RewindOffloadEnableBit : 0u);
if (!TryAllocateCommandDwords(ctx, acb, 2, out var cmd) ||
!ctx.TryWriteUInt32(cmd, Pm4(2, ItRewind, RZero)) ||
!ctx.TryWriteUInt32(cmd + 4, body))
{
return ReturnPointer(ctx, 0);
}
TraceAgc($"agc.acb_rewind buf=0x{acb:X16} cmd=0x{cmd:X16} valid={valid} offload={offloadEnable}");
return ReturnPointer(ctx, cmd);
}
// Patches the REWIND body dword's valid bit and wakes any DCB suspended on it.
// rdi is the packet pointer returned by sceAgcDcbRewind (header address).
[SysAbiExport(
Nid = "ziVA3whp3p4",
ExportName = "sceAgcRewindPatchSetRewindState",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int RewindPatchSetRewindState(CpuContext ctx)
{
var packetAddress = ctx[CpuRegister.Rdi];
var valid = (ctx[CpuRegister.Rsi] & 1UL) != 0;
if (packetAddress == 0 ||
(long)packetAddress < 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
var bodyAddress = packetAddress;
if (TryReadUInt32(ctx, packetAddress, out var header) &&
((header >> 8) & 0xFFu) == ItRewind)
{
bodyAddress = packetAddress + sizeof(uint);
}
if (!TryReadUInt32(ctx, bodyAddress, out var body) ||
!TryWriteUInt32(
ctx,
bodyAddress,
valid ? body | RewindValidBit : body & ~RewindValidBit))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (!TryReadUInt32(ctx, bodyAddress, out var patched))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceAgc($"agc.rewind_patch addr=0x{bodyAddress:X16} valid={valid} body=0x{patched:X8}");
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "eWaWyFegzgQ",
ExportName = "sceAgcAsyncRewindPatchSetRewindState",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int AsyncRewindPatchSetRewindState(CpuContext ctx) =>
RewindPatchSetRewindState(ctx);
// Matches the 4-dword INDIRECT_BUFFER packet DcbJump writes below.
// Returning NOT_FOUND here left callers with a null packet pointer and an
// immediate write AV on RenderThread.
[SysAbiExport(
Nid = "VEGu4dixjUg",
ExportName = "sceAgcDcbJumpGetSize",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbJumpGetSize(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 4u * sizeof(uint);
return (int)ctx[CpuRegister.Rax];
}
[SysAbiExport(
Nid = "xSAR0LTcRKM",
ExportName = "sceAgcDcbJump",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbJump(CpuContext ctx)
{
var dcb = ctx[CpuRegister.Rdi];
var mode = (uint)ctx[CpuRegister.Rsi];
var cachePolicy = (uint)ctx[CpuRegister.Rdx];
var target = ctx[CpuRegister.Rcx];
var sizeDwords = (uint)ctx[CpuRegister.R8];
if (dcb == 0 || mode > 1 || cachePolicy > 3)
{
return ReturnPointer(ctx, 0);
}
var control = 0x0F20_0000u |
((cachePolicy & 0x3u) << 28) |
((mode & 0x1u) << 20) |
(sizeDwords & 0xFFFFFu);
if (!TryAllocateCommandDwords(ctx, dcb, 4, out var cmd) ||
!ctx.TryWriteUInt32(cmd, Pm4(4, ItIndirectBuffer, RZero)) ||
!ctx.TryWriteUInt32(cmd + 4, (uint)(target & 0xFFFF_FFFFUL)) ||
!ctx.TryWriteUInt32(cmd + 8, (uint)((target >> 32) & 0xFFFFUL)) ||
!ctx.TryWriteUInt32(cmd + 12, control))
{
return ReturnPointer(ctx, 0);
}
return ReturnPointer(ctx, cmd);
}
// Matches the 4-dword INDIRECT_BUFFER packet CbBranch writes below.
[SysAbiExport(
Nid = "uZW-mqsxkrM",
ExportName = "sceAgcCbBranchGetSize",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int CbBranchGetSize(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 4u * sizeof(uint);
return (int)ctx[CpuRegister.Rax];
}
// The interpreter reads the predicate when this packet executes.
// A zero value skips the specified number of following dwords.
[SysAbiExport(
Nid = "BIPexNBSGog",
ExportName = "sceAgcDcbCondExec",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbCondExec(CpuContext ctx)
{
var dcb = ctx[CpuRegister.Rdi];
var predicateAddress = ctx[CpuRegister.Rsi];
var execCountDwords = (uint)ctx[CpuRegister.Rdx];
if (dcb == 0)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, dcb, 5, out var cmd) ||
!ctx.TryWriteUInt32(cmd, Pm4(5, ItCondExec, RZero)) ||
!ctx.TryWriteUInt32(cmd + 4, (uint)(predicateAddress & 0xFFFF_FFFFUL)) ||
!ctx.TryWriteUInt32(cmd + 8, (uint)(predicateAddress >> 32)) ||
!ctx.TryWriteUInt32(cmd + 12, 0) ||
!ctx.TryWriteUInt32(cmd + 16, execCountDwords & 0x3FFF))
{
return ReturnPointer(ctx, 0);
}
return ReturnPointer(ctx, cmd);
}
// How a title continues a frame whose command arena filled: it branches from
// the tail of the exhausted buffer into a fresh one and submits only the first
// buffer, leaving the driver to follow the link. Dropping this packet strands
// everything written after the switch -- for UE 4.27 that is the rest of the
// frame, including its flip and the end-of-frame labels the guest's AGC
// interrupt thread needs before it will trigger the backbuffer event.
//
// The branch target and its length arrive on the stack, past six register
// arguments (verified against a live call: the values matched the continuation
// buffer the title had already written into).
[SysAbiExport(
Nid = "w1KFAHVqpaU",
ExportName = "sceAgcCbBranch",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int CbBranch(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
if (commandBufferAddress == 0 ||
!TryReadUInt64(ctx, ctx[CpuRegister.Rsp] + (2 * sizeof(ulong)), out var target) ||
!TryReadUInt64(ctx, ctx[CpuRegister.Rsp] + (3 * sizeof(ulong)), out var targetDwords))
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 4, out var commandAddress) ||
!TryWriteUInt32(ctx, commandAddress, Pm4(4, ItIndirectBuffer, RZero)) ||
!TryWriteUInt32(ctx, commandAddress + 4, (uint)(target & 0xFFFF_FFFFUL)) ||
!TryWriteUInt32(ctx, commandAddress + 8, (uint)((target >> 32) & 0xFFFFUL)) ||
!TryWriteUInt32(ctx, commandAddress + 12, (uint)targetDwords & 0xFFFFFu))
{
return ReturnPointer(ctx, 0);
}
TraceAgc(
$"agc.cb_branch buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} " +
$"target=0x{target:X16} dwords={targetDwords}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "b-oySn+G2tE",
ExportName = "sceAgcAcbJumpGetSize",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int AcbJumpGetSize(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 4u * sizeof(uint);
return (int)ctx[CpuRegister.Rax];
}
[SysAbiExport(
Nid = "e1DFTg+Sd8U",
ExportName = "sceAgcAcbJump",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
// (acb, cachePolicy, target, sizeDwords): an ACB jump always chains, so unlike
// sceAgcDcbJump it takes no mode argument.
public static int AcbJump(CpuContext ctx)
{
var acb = ctx[CpuRegister.Rdi];
var cachePolicy = (uint)ctx[CpuRegister.Rsi];
var target = ctx[CpuRegister.Rdx];
var sizeDwords = (uint)ctx[CpuRegister.Rcx];
if (acb == 0 || cachePolicy > 3)
{
return ReturnPointer(ctx, 0);
}
const uint chainMode = 1;
var control = 0x0F20_0000u |
((cachePolicy & 0x3u) << 28) |
(chainMode << 20) |
(sizeDwords & 0xFFFFFu);
if (!TryAllocateCommandDwords(ctx, acb, 4, out var cmd) ||
!ctx.TryWriteUInt32(cmd, Pm4(4, ItIndirectBuffer, RZero)) ||
!ctx.TryWriteUInt32(cmd + 4, (uint)(target & 0xFFFF_FFFFUL)) ||
!ctx.TryWriteUInt32(cmd + 8, (uint)((target >> 32) & 0xFFFFUL)) ||
!ctx.TryWriteUInt32(cmd + 12, control))
{
return ReturnPointer(ctx, 0);
}
return ReturnPointer(ctx, cmd);
}
[SysAbiExport(
Nid = "bbFueFP+J4k",
ExportName = "sceAgcDcbSetPredication",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbSetPredication(CpuContext ctx)
{
var dcb = ctx[CpuRegister.Rdi];
var condition = (uint)(ctx[CpuRegister.Rsi] & 1u);
var operation = (uint)(ctx[CpuRegister.Rdx] & 0x7u);
var waitOperation = (uint)(ctx[CpuRegister.Rcx] & 1u);
var address = ctx[CpuRegister.R8];
if (dcb == 0)
{
return ReturnPointer(ctx, 0);
}
var flags = (condition << 8) | (waitOperation << 12) | (operation << 16);
if (!TryAllocateCommandDwords(ctx, dcb, 4, out var cmd) ||
!ctx.TryWriteUInt32(cmd, Pm4(4, ItSetPredication, RZero)) ||
!ctx.TryWriteUInt32(cmd + 4, flags) ||
!ctx.TryWriteUInt32(cmd + 8, (uint)address & 0xFFFF_FFF0u) ||
!ctx.TryWriteUInt32(cmd + 12, (uint)(address >> 32)))
{
return ReturnPointer(ctx, 0);
}
return ReturnPointer(ctx, cmd);
}
[SysAbiExport(
Nid = "w6Dj1VJt5qY",
ExportName = "sceAgcSetPacketPredication",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int SetPacketPredication(CpuContext ctx)
{
var packetAddress = ctx[CpuRegister.Rdi];
var predication = ctx[CpuRegister.Rsi];
if (packetAddress == 0 || !TryReadUInt32(ctx, packetAddress, out var header))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
header = (header & ~1u) | (predication == 1 ? 1u : 0u);
return !ctx.TryWriteUInt32(packetAddress, header)
? ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT)
: ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
}
}