From eb47d753f6d07e9c89990cd68c4677245480bd96 Mon Sep 17 00:00:00 2001 From: samto6 <123419830+samto6@users.noreply.github.com> Date: Tue, 21 Jul 2026 20:00:30 -0400 Subject: [PATCH] [Ampr] Implement the FW 4.00 write-address command exports (#510) --- .../Native/DirectExecutionBackend.Imports.cs | 4 + src/SharpEmu.Libs/Ampr/AmprExports.cs | 38 +++++++++ .../Ampr/AmprWriteAddressTests.cs | 80 +++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 tests/SharpEmu.Libs.Tests/Ampr/AmprWriteAddressTests.cs diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs index 45fa5ca..345a23f 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs @@ -1404,11 +1404,13 @@ public sealed partial class DirectExecutionBackend "vWU-odnS+fU" or // sceAmprMeasureCommandSizeReadFile "sSAUCCU1dv4" or // sceAmprMeasureCommandSizeWriteKernelEventQueue_04_00 "C+IEj+BsAFM" or // sceAmprMeasureCommandSizeWriteAddressOnCompletion + "4fgtGfXDrFc" or // sceAmprMeasureCommandSizeWriteAddress_04_00 "tZDDEo2tE5k" or // sceAmprCommandBufferGetSize "GnxKOHEawhk" or // sceAmprCommandBufferGetCurrentOffset "gzndltBEzWc" or // sceAmprCommandBufferGetNumCommands "H896Pt-yB4I" or // sceAmprCommandBufferWriteKernelEventQueue_04_00 "sJXyWHjP-F8" or // sceAmprCommandBufferWriteAddressOnCompletion + "j0+3uJMxYJY" or // sceAmprCommandBufferWriteAddress_04_00 "mPpPxv5CZt4" or // sceSystemServiceGetHdrToneMapLuminance "1FZBKy8HeNU" or // sceVideoOutGetVblankStatus "ASoW5WE-UPo" or // sceKernelAprSubmitCommandBufferAndGetResult @@ -1554,11 +1556,13 @@ public sealed partial class DirectExecutionBackend "vWU-odnS+fU" or "sSAUCCU1dv4" or "C+IEj+BsAFM" or + "4fgtGfXDrFc" or "tZDDEo2tE5k" or "GnxKOHEawhk" or "gzndltBEzWc" or "H896Pt-yB4I" or "sJXyWHjP-F8" or + "j0+3uJMxYJY" or "mPpPxv5CZt4" or "1FZBKy8HeNU" or "ASoW5WE-UPo" or diff --git a/src/SharpEmu.Libs/Ampr/AmprExports.cs b/src/SharpEmu.Libs/Ampr/AmprExports.cs index e9202bc..001386f 100644 --- a/src/SharpEmu.Libs/Ampr/AmprExports.cs +++ b/src/SharpEmu.Libs/Ampr/AmprExports.cs @@ -340,6 +340,18 @@ public static class AmprExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "4fgtGfXDrFc", + ExportName = "sceAmprMeasureCommandSizeWriteAddress_04_00", + Target = Generation.Gen5, + LibraryName = "libSceAmpr")] + public static int MeasureCommandSizeWriteAddress0400(CpuContext ctx) + { + TraceAmpr(ctx, "measure_write_address", 0, WriteAddressRecordSize, 0); + ctx[CpuRegister.Rax] = WriteAddressRecordSize; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + [SysAbiExport( Nid = "tZDDEo2tE5k", ExportName = "sceAmprCommandBufferGetSize", @@ -509,6 +521,32 @@ public static class AmprExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "j0+3uJMxYJY", + ExportName = "sceAmprCommandBufferWriteAddress_04_00", + Target = Generation.Gen5, + LibraryName = "libSceAmpr")] + public static int CommandBufferWriteAddress0400(CpuContext ctx) + { + var commandBuffer = ctx[CpuRegister.Rdi]; + var address = ctx[CpuRegister.Rsi]; + var value = ctx[CpuRegister.Rdx]; + + if (commandBuffer == 0 || address == 0) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + if (!AppendWriteAddressRecord(ctx, commandBuffer, address, value)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + TraceAmpr(ctx, "write_address", commandBuffer, address, value); + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + public static int CompleteCommandBuffer(CpuContext ctx, ulong commandBuffer) { if (commandBuffer == 0) diff --git a/tests/SharpEmu.Libs.Tests/Ampr/AmprWriteAddressTests.cs b/tests/SharpEmu.Libs.Tests/Ampr/AmprWriteAddressTests.cs new file mode 100644 index 0000000..a78ddc2 --- /dev/null +++ b/tests/SharpEmu.Libs.Tests/Ampr/AmprWriteAddressTests.cs @@ -0,0 +1,80 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using SharpEmu.HLE; +using SharpEmu.Libs.Ampr; +using System.Buffers.Binary; +using Xunit; + +namespace SharpEmu.Libs.Tests.Ampr; + +public sealed class AmprWriteAddressTests +{ + [Fact] + public void MeasureCommandSizeWriteAddress0400_MatchesOnCompletionVariant() + { + const string nid = "4fgtGfXDrFc"; + const ulong memoryBase = 0x1_0000_0000; + var memory = new FakeCpuMemory(memoryBase, 0x1000); + var context = new CpuContext(memory, Generation.Gen5); + var manager = CreateManagerWithExport( + nid, + "sceAmprMeasureCommandSizeWriteAddress_04_00"); + + Assert.Equal(OrbisGen2Result.ORBIS_GEN2_OK, manager.Dispatch(nid, context)); + var measured = context[CpuRegister.Rax]; + + Assert.Equal(0, AmprExports.MeasureCommandSizeWriteAddressOnCompletion(context)); + Assert.Equal(context[CpuRegister.Rax], measured); + } + + [Fact] + public void CommandBufferWriteAddress0400_WritesValueOnCompletion() + { + const string nid = "j0+3uJMxYJY"; + const ulong memoryBase = 0x1_0000_0000; + const ulong commandBufferAddress = memoryBase + 0x100; + const ulong recordBufferAddress = memoryBase + 0x200; + const ulong watcherAddress = memoryBase + 0x800; + const ulong watcherValue = 1; + var memory = new FakeCpuMemory(memoryBase, 0x1000); + var context = new CpuContext(memory, Generation.Gen5); + var manager = CreateManagerWithExport( + nid, + "sceAmprCommandBufferWriteAddress_04_00"); + + context[CpuRegister.Rdi] = commandBufferAddress; + context[CpuRegister.Rsi] = recordBufferAddress; + context[CpuRegister.Rdx] = 0x100; + + Assert.Equal(0, AmprExports.CommandBufferConstructor(context)); + + context[CpuRegister.Rdi] = commandBufferAddress; + context[CpuRegister.Rsi] = watcherAddress; + context[CpuRegister.Rdx] = watcherValue; + + Assert.Equal(OrbisGen2Result.ORBIS_GEN2_OK, manager.Dispatch(nid, context)); + + Span watcher = stackalloc byte[sizeof(ulong)]; + Assert.True(memory.TryRead(watcherAddress, watcher)); + Assert.Equal(0UL, BinaryPrimitives.ReadUInt64LittleEndian(watcher)); + + Assert.Equal(0, AmprExports.CompleteCommandBuffer(context, commandBufferAddress)); + + Assert.True(memory.TryRead(watcherAddress, watcher)); + Assert.Equal(watcherValue, BinaryPrimitives.ReadUInt64LittleEndian(watcher)); + } + + private static ModuleManager CreateManagerWithExport(string nid, string exportName) + { + var manager = new ModuleManager(); + manager.RegisterExports( + SharpEmu.Generated.SysAbiExportRegistry.CreateExports(Generation.Gen5)); + + Assert.True(manager.TryGetExport(nid, out var export), $"NID {nid} did not register."); + Assert.Equal(exportName, export.Name); + Assert.Equal("libSceAmpr", export.LibraryName); + Assert.Equal(Generation.Gen5, export.Target); + return manager; + } +}