[HLE] Implement Hades' high-frequency unresolved AGC DCB builders

Hades boots with ~1.3k unresolved-import hits that corrupt its GPU command
stream — the two hot ones are DCB writers whose garbage return values
derail the game's command-buffer cursor:

- sceAgcDcbSetShRegisterDirect (pFLArOT53+w, ~1.1k calls/boot): single
  SET_SH_REG builder; register packed in rsi (low 16 = offset, high dword =
  byte offset of the dword within a multi-dword write), value in edx. Emits
  the same 3-dword packet as the plural sceAgcCbSetShRegistersDirect path.
- sceAgcDcbWaitOnAddressGetSize (43WJ08sSugE, ~235 calls): size probe for
  the existing sceAgcDcbWaitRegMem writer; mirrors its dword computation
  (7 standard, 6/9 for the 32/64-bit polled forms).
- pthread_rename_np (9vyP6Z7bqzc): POSIX alias of scePthreadRename.

Hades unresolved-import hits drop 1339 -> 6 (two unknown NIDs not in the
public name catalog plus one sceNpTrophy2GetTrophyInfoArray call). Still no
draws — the DCB stream is now well-formed, which unblocks diagnosing why
the game never reaches its render loop. 349 Libs tests pass.
This commit is contained in:
Gutemberg Ribeiro
2026-07-19 04:47:25 +01:00
parent c583728e1b
commit de0816e1b0
2 changed files with 59 additions and 0 deletions
+52
View File
@@ -1773,6 +1773,58 @@ public static partial class AgcExports
return ReturnPointer(ctx, commandAddress);
}
// Single-register variant of the SET_SH_REG builders: the register rides
// in rsi as a packed struct (low 16 bits = register offset, high dword =
// byte offset of this dword within a multi-dword register write) and the
// value in edx. Emits the same 3-dword SET_SH_REG packet the plural
// sceAgcCbSetShRegistersDirect path produces per run. Hades calls this
// ~1k times per boot; leaving it unresolved corrupted its DCB stream.
[SysAbiExport(
Nid = "pFLArOT53+w",
ExportName = "sceAgcDcbSetShRegisterDirect",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbSetShRegisterDirect(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var packedRegister = ctx[CpuRegister.Rsi];
var value = (uint)ctx[CpuRegister.Rdx];
if (commandBufferAddress == 0)
{
return ReturnPointer(ctx, 0);
}
var offset = (uint)(packedRegister & 0xFFFFu) + (uint)((packedRegister >> 32) >> 2);
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 3, out var commandAddress) ||
!TryWriteUInt32(ctx, commandAddress, Pm4(3, ItSetShReg, 0)) ||
!TryWriteUInt32(ctx, commandAddress + 4, offset & 0xFFFFu) ||
!TryWriteUInt32(ctx, commandAddress + 8, value))
{
return ReturnPointer(ctx, 0);
}
TraceAgc($"agc.dcb_set_sh_register_direct buf=0x{commandBufferAddress:X16} reg=0x{offset:X4} value=0x{value:X8}");
return ReturnPointer(ctx, commandAddress);
}
// Size probe for the wait-on-address writer below: same argument prefix
// minus the command buffer, returns the byte size the writer will emit so
// the game can reserve DCB space (7 dwords for a standard WAIT_REG_MEM,
// 6/9 for the 32/64-bit polled-NOP forms).
[SysAbiExport(
Nid = "43WJ08sSugE",
ExportName = "sceAgcDcbWaitOnAddressGetSize",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbWaitOnAddressGetSize(CpuContext ctx)
{
var size = (uint)(ctx[CpuRegister.Rdi] & 0xFF);
var operation = (uint)(ctx[CpuRegister.Rdx] & 0xFF);
var packetDwords = operation is 2 or 3 ? 7u : size == 0 ? 6u : 9u;
ctx[CpuRegister.Rax] = packetDwords * sizeof(uint);
return (int)ctx[CpuRegister.Rax];
}
[SysAbiExport(
Nid = "VmW0Tdpy420",
ExportName = "sceAgcDcbWaitRegMem",
@@ -119,6 +119,13 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "9vyP6Z7bqzc",
ExportName = "pthread_rename_np",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadRenameNp(CpuContext ctx) => PthreadRename(ctx);
[SysAbiExport(
Nid = "GBUY7ywdULE",
ExportName = "scePthreadRename",