diff --git a/src/SharpEmu.Libs/Agc/AgcExports.cs b/src/SharpEmu.Libs/Agc/AgcExports.cs
index 25ad782e..e471daa0 100644
--- a/src/SharpEmu.Libs/Agc/AgcExports.cs
+++ b/src/SharpEmu.Libs/Agc/AgcExports.cs
@@ -95,8 +95,10 @@ public static partial class AgcExports
private const uint SpiShaderPgmRsrc1Hs = 0x10A;
private const uint SpiShaderPgmLoLs = 0x148;
private const uint SpiShaderPgmHiLs = 0x149;
- private const uint SpiShaderPgmLoGs = 0x8A;
- private const uint SpiShaderPgmHiGs = 0x8B;
+ // Not 0x8A/0x8B - those are SPI_SHADER_PGM_RSRC1/RSRC2_GS, and reading them
+ // as an address yields a 58-bit value (observed live: 0x30004622C008300).
+ private const uint SpiShaderPgmLoGs = 0x88;
+ private const uint SpiShaderPgmHiGs = 0x89;
private const uint SpiShaderPgmChksumGs = 0x80;
private const uint SpiPsInputEna = 0x1B3;
private const uint SpiPsInputAddr = 0x1B4;
@@ -2174,6 +2176,18 @@ public static partial class AgcExports
return (int)ctx[CpuRegister.Rax];
}
+ [SysAbiExport(
+ Nid = "r98I08t+LOg",
+ ExportName = "sceAgcDcbDrawIndexIndirectMultiGetSize",
+ Target = Generation.Gen5,
+ LibraryName = "libSceAgc")]
+ public static int DcbDrawIndexIndirectMultiGetSize(CpuContext ctx)
+ {
+ // Eight, matching the packet DcbDrawIndexIndirectMulti emits.
+ ctx[CpuRegister.Rax] = 8u * sizeof(uint);
+ return (int)ctx[CpuRegister.Rax];
+ }
+
[SysAbiExport(
Nid = "rUuVjyR+Rd4",
ExportName = "sceAgcDcbGetLodStatsGetSize",
@@ -6426,6 +6440,48 @@ public static partial class AgcExports
}
}
+ ///
+ /// Test-only view of a parsed graphics context register. False when the
+ /// register was never written.
+ ///
+ internal static bool TryGetGraphicsContextRegisterForTests(
+ CpuContext ctx,
+ uint registerOffset,
+ out uint value)
+ {
+ value = 0;
+ if (!_submittedGpuStates.TryGetValue(ctx.Memory, out var gpuState))
+ {
+ return false;
+ }
+
+ lock (gpuState.Gate)
+ {
+ return gpuState.Graphics.CxRegisters.TryGetValue(registerOffset, out value);
+ }
+ }
+
+ ///
+ /// SH-register counterpart of ;
+ /// the shader stage addresses live here.
+ ///
+ internal static bool TryGetGraphicsShRegisterForTests(
+ CpuContext ctx,
+ uint registerOffset,
+ out uint value)
+ {
+ value = 0;
+ if (!_submittedGpuStates.TryGetValue(ctx.Memory, out var gpuState))
+ {
+ return false;
+ }
+
+ lock (gpuState.Gate)
+ {
+ return gpuState.Graphics.ShRegisters.TryGetValue(registerOffset, out value);
+ }
+ }
+
///
/// GraphicsDcbSetIndexSize writes VGT_INDEX_TYPE via SET_UCONFIG_REG.
/// Mirror that into .
@@ -7444,22 +7500,6 @@ public static partial class AgcExports
}
}
- state.UcRegisters.TryGetValue(VgtPrimitiveType, out var earlyPrimitiveType);
- if (IsRectListPrimitive(earlyPrimitiveType) &&
- (exportEvaluation.VertexInputs is null || exportEvaluation.VertexInputs.Count == 0) &&
- !VertexProgramExportsParameters(exportState.Program) &&
- GetInterpolatedAttributeCount(pixelState) != 0)
- {
- ReturnPooledEvaluationArrays(exportEvaluation);
- ReturnPooledEvaluationArrays(pixelEvaluation);
- error =
- $"rect-list-no-param-exports ps_inputs={GetInterpolatedAttributeCount(pixelState)}";
- TraceAgcShader(
- $"agc.rect_list_skip es=0x{exportShaderAddress:X16} " +
- $"ps=0x{pixelShaderAddress:X16} {error}");
- return false;
- }
-
// Every bound color target the shader exports to. Deferred renderers
// draw a multi-render-target G-buffer (up to eight slots) in one pass.
// Fall back to slot 0 if we cannot match any export to a bound target.
@@ -8236,20 +8276,6 @@ public static partial class AgcExports
? (packedMasks >> (int)(target * 4)) & 0xFu
: 0;
- private static bool VertexProgramExportsParameters(Gen5ShaderProgram program)
- {
- foreach (var instruction in program.Instructions)
- {
- if (instruction.Control is Gen5ExportControl export &&
- export.Target is >= 32 and < 64)
- {
- return true;
- }
- }
-
- return false;
- }
-
private static uint GetInterpolatedAttributeCount(Gen5ShaderState state)
{
var maxAttribute = -1;
@@ -12673,9 +12699,6 @@ public static partial class AgcExports
private static bool IsEsGeometryShaderType(byte shaderType) =>
shaderType is GsShaderType or GsBackShaderType;
- private static bool IsRectListPrimitive(uint primitiveType) =>
- AgcPrimitiveHelpers.IsRectListPrimitive(primitiveType);
-
private static int SetIndirectPatchAddress(CpuContext ctx, string registerSpace)
{
var commandAddress = ctx[CpuRegister.Rdi];
diff --git a/tests/SharpEmu.Libs.Tests/Agc/AgcContextRegisterTests.cs b/tests/SharpEmu.Libs.Tests/Agc/AgcContextRegisterTests.cs
new file mode 100644
index 00000000..9d70dc0d
--- /dev/null
+++ b/tests/SharpEmu.Libs.Tests/Agc/AgcContextRegisterTests.cs
@@ -0,0 +1,215 @@
+// Copyright (C) 2026 SharpEmu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+using System.Buffers.Binary;
+using SharpEmu.HLE;
+using SharpEmu.Libs.Agc;
+using Xunit;
+
+namespace SharpEmu.Libs.Tests.Agc;
+
+///
+/// Coverage for the graphics context-register path in the PM4 parser. Draw
+/// translation reads render state out of this dictionary (CB_TARGET_MASK
+/// decides whether a draw writes alpha, CB_COLOR_CONTROL decides what the draw
+/// means), so a write that lands under the wrong key, or fails to overwrite an
+/// earlier one, silently changes what every later draw does. These drive real
+/// PM4 packets through the public submit export and assert what the parser
+/// retained.
+///
+public sealed class AgcContextRegisterTests
+{
+ private const ulong BaseAddress = 0x2_0000_0000;
+ private const ulong SubmitPacketAddress = BaseAddress + 0x40;
+ private const ulong CommandAddress = BaseAddress + 0x200;
+ private const ulong IndirectTableAddress = BaseAddress + 0x600;
+
+ private const uint ItNop = 0x10;
+ private const uint ItSetContextReg = 0x69;
+ private const uint RCxRegsIndirect = 0x12;
+ private const uint CbTargetMask = 0x8E;
+ private const uint CbColorControl = 0x202;
+
+ // PM4 type-3 header: 0xC0000000 | ((dwords - 2) << 16) | (opcode << 8), with the
+ // NOP sub-register in bits 2..7 — the parser reads it as (header >> 2) & 0x3F.
+ private static uint Pm4Header(uint dwords, uint opcode, uint register = 0) =>
+ 0xC000_0000u | ((dwords - 2) << 16) | (opcode << 8) | ((register & 0x3Fu) << 2);
+
+ [Fact]
+ public void SetContextRegRetainsTargetMask()
+ {
+ var ctx = CreateContext(out var memory);
+ WriteDwords(
+ memory,
+ CommandAddress,
+ Pm4Header(3, ItSetContextReg),
+ CbTargetMask,
+ 0x0000_0007u);
+ Submit(ctx, memory, dwordCount: 3);
+
+ Assert.True(
+ AgcExports.TryGetGraphicsContextRegisterForTests(ctx, CbTargetMask, out var value));
+ Assert.Equal(0x0000_0007u, value);
+ }
+
+ ///
+ /// The indirect form carries (offset, value) pairs out of guest memory
+ /// rather than inline dwords, so an offset-encoding mismatch here would
+ /// store the register under a key no reader looks at.
+ ///
+ [Fact]
+ public void IndirectRegisterWriteRetainsTargetMask()
+ {
+ var ctx = CreateContext(out var memory);
+ WriteIndirectRegisterCommand(memory, (CbTargetMask, 0xFFFF_FFFFu));
+ Submit(ctx, memory, dwordCount: 4);
+
+ Assert.True(
+ AgcExports.TryGetGraphicsContextRegisterForTests(ctx, CbTargetMask, out var value));
+ Assert.Equal(0xFFFF_FFFFu, value);
+ }
+
+ ///
+ /// Context registers persist across submissions on hardware until something
+ /// clears them, so a mask written in one submission has to still be there
+ /// for a draw in the next.
+ ///
+ [Fact]
+ public void TargetMaskSurvivesASecondSubmission()
+ {
+ var ctx = CreateContext(out var memory);
+ WriteIndirectRegisterCommand(memory, (CbTargetMask, 0x8888_8888u));
+ Submit(ctx, memory, dwordCount: 4);
+
+ // A second, unrelated submission: a bare NOP that touches no registers.
+ WriteDwords(memory, CommandAddress, Pm4Header(2, ItNop), 0);
+ Submit(ctx, memory, dwordCount: 2);
+
+ Assert.True(
+ AgcExports.TryGetGraphicsContextRegisterForTests(ctx, CbTargetMask, out var value));
+ Assert.Equal(0x8888_8888u, value);
+ }
+
+ ///
+ /// Both encodings must land on the same key, or a title that sets the
+ /// register one way and a reader that expects the other silently disagree.
+ ///
+ [Fact]
+ public void DirectAndIndirectWritesShareOneKey()
+ {
+ var ctx = CreateContext(out var memory);
+ WriteDwords(
+ memory,
+ CommandAddress,
+ Pm4Header(3, ItSetContextReg),
+ CbTargetMask,
+ 0x0000_0007u);
+ Submit(ctx, memory, dwordCount: 3);
+
+ WriteIndirectRegisterCommand(memory, (CbTargetMask, 0x0000_000Fu));
+ Submit(ctx, memory, dwordCount: 4);
+
+ Assert.True(
+ AgcExports.TryGetGraphicsContextRegisterForTests(ctx, CbTargetMask, out var value));
+ Assert.Equal(0x0000_000Fu, value);
+ }
+
+ ///
+ /// CB_COLOR_CONTROL (0x202) MODE bits [6:4] give Normal=1,
+ /// EliminateFastClear=2, Resolve=3, FmaskDecompress=5, DccDecompress=6. The
+ /// value has to survive the parser intact, ROP3 bits and all, because the
+ /// mode decides whether a draw shades or resolves.
+ ///
+ [Theory]
+ [InlineData(0x0000_0010u, 1u)] // Normal
+ [InlineData(0x0000_0020u, 2u)] // EliminateFastClear
+ [InlineData(0x00CC_0060u, 6u)] // DccDecompress, with ROP3=0xCC alongside
+ public void ColorControlRetainsMode(uint written, uint expectedMode)
+ {
+ var ctx = CreateContext(out var memory);
+ WriteIndirectRegisterCommand(memory, (CbColorControl, written));
+ Submit(ctx, memory, dwordCount: 4);
+
+ Assert.True(
+ AgcExports.TryGetGraphicsContextRegisterForTests(ctx, CbColorControl, out var value));
+ Assert.Equal(written, value);
+ Assert.Equal(expectedMode, (value >> 4) & 0x7u);
+ }
+
+ ///
+ /// A later write must win. If the parser kept the first value, a draw that
+ /// sets EliminateFastClear after an earlier Normal would still read Normal
+ /// and the clear would be silently dropped.
+ ///
+ [Fact]
+ public void ColorControlLaterWriteOverwritesEarlier()
+ {
+ var ctx = CreateContext(out var memory);
+ WriteIndirectRegisterCommand(memory, (CbColorControl, 0x00CC_0010u));
+ Submit(ctx, memory, dwordCount: 4);
+
+ WriteIndirectRegisterCommand(memory, (CbColorControl, 0x00CC_0020u));
+ Submit(ctx, memory, dwordCount: 4);
+
+ Assert.True(
+ AgcExports.TryGetGraphicsContextRegisterForTests(ctx, CbColorControl, out var value));
+ Assert.Equal(0x00CC_0020u, value);
+ Assert.Equal(2u, (value >> 4) & 0x7u);
+ }
+
+ private static void WriteIndirectRegisterCommand(
+ FakeCpuMemory memory,
+ params (uint Offset, uint Value)[] registers)
+ {
+ WriteDwords(
+ memory,
+ CommandAddress,
+ Pm4Header(4, ItNop, RCxRegsIndirect),
+ (uint)registers.Length,
+ (uint)(IndirectTableAddress & 0xFFFF_FFFFu),
+ (uint)(IndirectTableAddress >> 32));
+
+ for (var index = 0; index < registers.Length; index++)
+ {
+ var entry = IndirectTableAddress + ((ulong)index * 8);
+ WriteUInt32(memory, entry, registers[index].Offset);
+ WriteUInt32(memory, entry + 4, registers[index].Value);
+ }
+ }
+
+ private static void Submit(CpuContext ctx, FakeCpuMemory memory, uint dwordCount)
+ {
+ WriteUInt64(memory, SubmitPacketAddress, CommandAddress);
+ WriteUInt32(memory, SubmitPacketAddress + 8, dwordCount);
+ ctx[CpuRegister.Rdi] = SubmitPacketAddress;
+ AgcExports.DriverSubmitDcb(ctx);
+ }
+
+ private static CpuContext CreateContext(out FakeCpuMemory memory)
+ {
+ memory = new FakeCpuMemory(BaseAddress, 0x1000);
+ return new CpuContext(memory, Generation.Gen5);
+ }
+
+ private static void WriteDwords(FakeCpuMemory memory, ulong address, params uint[] values)
+ {
+ for (var index = 0; index < values.Length; index++)
+ {
+ WriteUInt32(memory, address + ((ulong)index * sizeof(uint)), values[index]);
+ }
+ }
+
+ private static void WriteUInt32(FakeCpuMemory memory, ulong address, uint value)
+ {
+ Span buffer = stackalloc byte[sizeof(uint)];
+ BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
+ Assert.True(memory.TryWrite(address, buffer));
+ }
+
+ private static void WriteUInt64(FakeCpuMemory memory, ulong address, ulong value)
+ {
+ Span buffer = stackalloc byte[sizeof(ulong)];
+ BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
+ Assert.True(memory.TryWrite(address, buffer));
+ }
+}
diff --git a/tests/SharpEmu.Libs.Tests/Agc/AgcShaderStageRegisterTests.cs b/tests/SharpEmu.Libs.Tests/Agc/AgcShaderStageRegisterTests.cs
new file mode 100644
index 00000000..16379cfe
--- /dev/null
+++ b/tests/SharpEmu.Libs.Tests/Agc/AgcShaderStageRegisterTests.cs
@@ -0,0 +1,232 @@
+// Copyright (C) 2026 SharpEmu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+using System.Buffers.Binary;
+using SharpEmu.HLE;
+using SharpEmu.Libs.Agc;
+using Xunit;
+
+namespace SharpEmu.Libs.Tests.Agc;
+
+///
+/// Coverage for the SH-register path in the PM4 parser. A draw resolves its
+/// vertex stage from SPI_SHADER_PGM_LO_ES/HI_ES and its pixel stage from
+/// SPI_SHADER_PGM_LO_PS/HI_PS, both out of this dictionary, so a key that is
+/// dropped or written under a different encoding pairs a current pixel shader
+/// with a stale vertex shader — a failure that produces plausible-looking
+/// garbage rather than an error. These drive real PM4 packets through the
+/// public submit export and assert what the parser retained.
+///
+public sealed class AgcShaderStageRegisterTests
+{
+ private const ulong BaseAddress = 0x2_0000_0000;
+ private const ulong SubmitPacketAddress = BaseAddress + 0x40;
+ private const ulong CommandAddress = BaseAddress + 0x200;
+ private const ulong IndirectTableAddress = BaseAddress + 0x600;
+
+ private const uint ItNop = 0x10;
+ private const uint ItSetShReg = 0x76;
+ private const uint RShRegsIndirect = 0x11;
+
+ // SH register offsets. ES is the vertex stage on GFX10 — the standalone
+ // PGM_LO/HI_GS pair is dead post-GCN and the merged ES/GS stage is addressed
+ // through ES.
+ private const uint SpiShaderPgmLoPs = 0x8;
+ private const uint SpiShaderPgmLoEs = 0xC8;
+ private const uint SpiShaderPgmHiEs = 0xC9;
+
+ private static uint Pm4Header(uint dwords, uint opcode, uint register = 0) =>
+ 0xC000_0000u | ((dwords - 2) << 16) | (opcode << 8) | ((register & 0x3Fu) << 2);
+
+ ///
+ /// The baseline: a direct SET_SH_REG write of the vertex stage address has
+ /// to be readable afterwards. If this fails, nothing downstream can pair
+ /// shaders correctly.
+ ///
+ [Fact]
+ public void SetShRegRetainsExportShaderAddress()
+ {
+ var ctx = CreateContext(out var memory);
+ WriteDwords(
+ memory,
+ CommandAddress,
+ Pm4Header(3, ItSetShReg),
+ SpiShaderPgmLoEs,
+ 0x0044_8582u);
+ Submit(ctx, memory, dwordCount: 3);
+
+ Assert.True(
+ AgcExports.TryGetGraphicsShRegisterForTests(ctx, SpiShaderPgmLoEs, out var value));
+ Assert.Equal(0x0044_8582u, value);
+ }
+
+ ///
+ /// The indirect encoding must land on the same keys as the direct one. A
+ /// mismatch would store the stage address where the draw never reads it,
+ /// leaving the draw to see whatever a previous submission left behind.
+ ///
+ [Fact]
+ public void IndirectShRegisterWriteRetainsExportShaderAddress()
+ {
+ var ctx = CreateContext(out var memory);
+ WriteIndirectShRegisterCommand(memory, (SpiShaderPgmLoEs, 0x0044_8DD1u));
+ Submit(ctx, memory, dwordCount: 4);
+
+ Assert.True(
+ AgcExports.TryGetGraphicsShRegisterForTests(ctx, SpiShaderPgmLoEs, out var value));
+ Assert.Equal(0x0044_8DD1u, value);
+ }
+
+ ///
+ /// Both stages written in one submission must both read back as written. If
+ /// the vertex stage kept an older value while the pixel stage updated, every
+ /// draw after it would be mis-paired.
+ ///
+ [Fact]
+ public void BothStagesUpdateTogetherWithinOneSubmission()
+ {
+ var ctx = CreateContext(out var memory);
+ WriteIndirectShRegisterCommand(
+ memory,
+ (SpiShaderPgmLoEs, 0x0080_2933u),
+ (SpiShaderPgmLoPs, 0x0044_858Au));
+ Submit(ctx, memory, dwordCount: 4);
+
+ WriteIndirectShRegisterCommand(
+ memory,
+ (SpiShaderPgmLoEs, 0x0044_8581u),
+ (SpiShaderPgmLoPs, 0x0044_8719u));
+ Submit(ctx, memory, dwordCount: 4);
+
+ Assert.True(
+ AgcExports.TryGetGraphicsShRegisterForTests(ctx, SpiShaderPgmLoEs, out var es));
+ Assert.True(
+ AgcExports.TryGetGraphicsShRegisterForTests(ctx, SpiShaderPgmLoPs, out var ps));
+ Assert.Equal(0x0044_8581u, es);
+ Assert.Equal(0x0044_8719u, ps);
+ }
+
+ ///
+ /// Updating only the pixel stage must leave the vertex stage at its previous
+ /// value rather than dropping the key, or the draw falls back to whatever
+ /// default the resolver finds.
+ ///
+ [Fact]
+ public void PixelStageUpdateLeavesExportStageIntact()
+ {
+ var ctx = CreateContext(out var memory);
+ WriteIndirectShRegisterCommand(memory, (SpiShaderPgmLoEs, 0x0044_8582u));
+ Submit(ctx, memory, dwordCount: 4);
+
+ WriteIndirectShRegisterCommand(memory, (SpiShaderPgmLoPs, 0x0044_858Au));
+ Submit(ctx, memory, dwordCount: 4);
+
+ Assert.True(
+ AgcExports.TryGetGraphicsShRegisterForTests(ctx, SpiShaderPgmLoEs, out var es));
+ Assert.Equal(0x0044_8582u, es);
+ }
+
+ ///
+ /// Stage addresses are 64-bit: LO carries bits 39:8 and HI the top bits, and
+ /// the draw combines them. A HI retained from an earlier shader while LO
+ /// updates resolves to a splice of two different programs.
+ ///
+ [Fact]
+ public void HighAndLowHalvesUpdateTogether()
+ {
+ var ctx = CreateContext(out var memory);
+ WriteIndirectShRegisterCommand(
+ memory,
+ (SpiShaderPgmLoEs, 0x0080_2933u),
+ (SpiShaderPgmHiEs, 0x0000_0008u));
+ Submit(ctx, memory, dwordCount: 4);
+
+ WriteIndirectShRegisterCommand(
+ memory,
+ (SpiShaderPgmLoEs, 0x0044_8582u),
+ (SpiShaderPgmHiEs, 0x0000_0004u));
+ Submit(ctx, memory, dwordCount: 4);
+
+ Assert.True(
+ AgcExports.TryGetGraphicsShRegisterForTests(ctx, SpiShaderPgmLoEs, out var lo));
+ Assert.True(
+ AgcExports.TryGetGraphicsShRegisterForTests(ctx, SpiShaderPgmHiEs, out var hi));
+ Assert.Equal(0x0044_8582u, lo);
+ Assert.Equal(0x0000_0004u, hi);
+ }
+
+ ///
+ /// SH registers persist across submissions on hardware. A stage address set
+ /// in one submission must still be there for a draw in the next.
+ ///
+ [Fact]
+ public void ExportShaderAddressSurvivesASecondSubmission()
+ {
+ var ctx = CreateContext(out var memory);
+ WriteIndirectShRegisterCommand(memory, (SpiShaderPgmLoEs, 0x0044_8583u));
+ Submit(ctx, memory, dwordCount: 4);
+
+ WriteDwords(memory, CommandAddress, Pm4Header(2, ItNop), 0);
+ Submit(ctx, memory, dwordCount: 2);
+
+ Assert.True(
+ AgcExports.TryGetGraphicsShRegisterForTests(ctx, SpiShaderPgmLoEs, out var value));
+ Assert.Equal(0x0044_8583u, value);
+ }
+
+ private static void WriteIndirectShRegisterCommand(
+ FakeCpuMemory memory,
+ params (uint Offset, uint Value)[] registers)
+ {
+ WriteDwords(
+ memory,
+ CommandAddress,
+ Pm4Header(4, ItNop, RShRegsIndirect),
+ (uint)registers.Length,
+ (uint)(IndirectTableAddress & 0xFFFF_FFFFu),
+ (uint)(IndirectTableAddress >> 32));
+
+ for (var index = 0; index < registers.Length; index++)
+ {
+ var entry = IndirectTableAddress + ((ulong)index * 8);
+ WriteUInt32(memory, entry, registers[index].Offset);
+ WriteUInt32(memory, entry + 4, registers[index].Value);
+ }
+ }
+
+ private static void Submit(CpuContext ctx, FakeCpuMemory memory, uint dwordCount)
+ {
+ WriteUInt64(memory, SubmitPacketAddress, CommandAddress);
+ WriteUInt32(memory, SubmitPacketAddress + 8, dwordCount);
+ ctx[CpuRegister.Rdi] = SubmitPacketAddress;
+ AgcExports.DriverSubmitDcb(ctx);
+ }
+
+ private static CpuContext CreateContext(out FakeCpuMemory memory)
+ {
+ memory = new FakeCpuMemory(BaseAddress, 0x1000);
+ return new CpuContext(memory, Generation.Gen5);
+ }
+
+ private static void WriteDwords(FakeCpuMemory memory, ulong address, params uint[] values)
+ {
+ for (var index = 0; index < values.Length; index++)
+ {
+ WriteUInt32(memory, address + ((ulong)index * sizeof(uint)), values[index]);
+ }
+ }
+
+ private static void WriteUInt32(FakeCpuMemory memory, ulong address, uint value)
+ {
+ Span buffer = stackalloc byte[sizeof(uint)];
+ BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
+ Assert.True(memory.TryWrite(address, buffer));
+ }
+
+ private static void WriteUInt64(FakeCpuMemory memory, ulong address, ulong value)
+ {
+ Span buffer = stackalloc byte[sizeof(ulong)];
+ BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
+ Assert.True(memory.TryWrite(address, buffer));
+ }
+}