[VideoOut] Fix sceVideoOutGetFlipStatus structure layout

flipArg was hardcoded to 0 and nothing recorded the argument passed to
SubmitFlip, so a title waiting for its own flip label never observed it
complete. currentBuffer was written as a u64 at 0x20, which is the
submitTsc slot; the real field is a u32 at 0x38. Offsets 0x28-0x3C were
never written at all, leaving the guest to read stale stack bytes as
gcQueueNum/flipPendingNum.

Record LastFlipArg on the port and write the full documented layout.
TryWriteUInt32Compat becomes internal so the u32 tail can be written.

Observed on DOOM + DOOM II (PPSA21444), which spun ~4.4M iterations of
sceKernelWaitEqueue/sceVideoOutGetFlipStatus until the import-loop guard
fired. The spin no longer occurs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
shadowbeat070
2026-07-19 14:00:22 +02:00
parent d7f6e3f578
commit 3da22dd568
2 changed files with 18 additions and 4 deletions
@@ -5408,7 +5408,7 @@ public static partial class KernelMemoryCompatExports
return true;
}
private static bool TryWriteUInt32Compat(CpuContext ctx, ulong address, uint value)
internal static bool TryWriteUInt32Compat(CpuContext ctx, ulong address, uint value)
{
Span<byte> bytes = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(bytes, value);
+17 -3
View File
@@ -197,6 +197,7 @@ public static class VideoOutExports
public int FlipRate { get; set; }
public ulong VblankCount { get; set; }
public ulong FlipCount { get; set; }
public long LastFlipArg { get; set; }
public int CurrentBuffer { get; set; } = -1;
public uint OutputWidth { get; set; } = 1920;
public uint OutputHeight { get; set; } = 1080;
@@ -709,18 +710,30 @@ public static class VideoOutExports
}
ulong count;
long flipArg;
uint currentBuffer;
lock (_stateGate)
{
count = port.FlipCount;
flipArg = port.LastFlipArg;
currentBuffer = unchecked((uint)port.CurrentBuffer);
}
// SceVideoOutFlipStatus: count(0x00) processTime(0x08) tsc(0x10) flipArg(0x18)
// submitTsc(0x20) reserved(0x28) gcQueueNum(0x30,u32) flipPendingNum(0x34,u32)
// currentBuffer(0x38,u32) reserved(0x3C,u32).
var tsc = unchecked((ulong)Stopwatch.GetTimestamp());
KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, statusAddress + 0x00, count);
KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, statusAddress + 0x08, 0);
KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, statusAddress + 0x10, 0);
KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, statusAddress + 0x18, 0);
KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, statusAddress + 0x20, currentBuffer);
KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, statusAddress + 0x10, tsc);
KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, statusAddress + 0x18, unchecked((ulong)flipArg));
KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, statusAddress + 0x20, tsc);
KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, statusAddress + 0x28, 0);
// No flips are ever left outstanding: SubmitFlip completes synchronously.
KernelMemoryCompatExports.TryWriteUInt32Compat(ctx, statusAddress + 0x30, 0);
KernelMemoryCompatExports.TryWriteUInt32Compat(ctx, statusAddress + 0x34, 0);
KernelMemoryCompatExports.TryWriteUInt32Compat(ctx, statusAddress + 0x38, currentBuffer);
KernelMemoryCompatExports.TryWriteUInt32Compat(ctx, statusAddress + 0x3C, 0);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -1159,6 +1172,7 @@ public static class VideoOutExports
port.CurrentBuffer = bufferIndex;
port.FlipCount++;
port.LastFlipArg = flipArg;
eventHint = SceVideoOutInternalEventFlip |
((unchecked((ulong)flipArg) & 0x0000_FFFF_FFFF_FFFFUL) << 16);
flipEventCount = port.FlipEvents.Count;