183 lines
7.8 KiB
C#
183 lines
7.8 KiB
C#
// 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;
|
|
|
|
[Collection("AmprFileRegistry")]
|
|
public sealed class AmprWaitOnAddressTests
|
|
{
|
|
[Fact]
|
|
public void MeasureCommandSizeWaitOnAddress_UsesReferenceWidth()
|
|
{
|
|
const string nid = "jIlc4p5dSD0";
|
|
const ulong memoryBase = 0x2_0000_0000;
|
|
var memory = new FakeCpuMemory(memoryBase, 0x1000);
|
|
var context = new CpuContext(memory, Generation.Gen5);
|
|
var manager = CreateManagerWithExport(nid, "sceAmprMeasureCommandSizeWaitOnAddress");
|
|
|
|
Assert.Equal(8UL, Measure(manager, context, nid, memoryBase + 0x100, 0, compare: 0, flush: 0));
|
|
Assert.Equal(12UL, Measure(manager, context, nid, memoryBase + 0x100, 0xCAFE, compare: 0, flush: 0));
|
|
Assert.Equal(16UL, Measure(manager, context, nid, memoryBase + 0x100, 0x1_0000_0000, compare: 0, flush: 0));
|
|
}
|
|
|
|
[Fact]
|
|
public void MeasureCommandSizeWaitOnAddress_ValidatesModernAndLegacyEnums()
|
|
{
|
|
const ulong memoryBase = 0x2_0000_0000;
|
|
var memory = new FakeCpuMemory(memoryBase, 0x1000);
|
|
var context = new CpuContext(memory, Generation.Gen5);
|
|
var modern = CreateManagerWithExport("jIlc4p5dSD0", "sceAmprMeasureCommandSizeWaitOnAddress");
|
|
var legacy = CreateManagerWithExport("0BMj1hgG+kE", "sceAmprMeasureCommandSizeWaitOnAddress_04_00");
|
|
|
|
context[CpuRegister.Rdi] = memoryBase + 0x100;
|
|
context[CpuRegister.Rsi] = 0xCAFE;
|
|
context[CpuRegister.Rdx] = 4;
|
|
context[CpuRegister.Rcx] = 0;
|
|
Assert.Equal(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT,
|
|
modern.Dispatch("jIlc4p5dSD0", context));
|
|
|
|
context[CpuRegister.Rdx] = 6;
|
|
Assert.Equal(OrbisGen2Result.ORBIS_GEN2_OK, legacy.Dispatch("0BMj1hgG+kE", context));
|
|
Assert.Equal(12UL, context[CpuRegister.Rax]);
|
|
|
|
context[CpuRegister.Rdi] = 0;
|
|
Assert.Equal(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT,
|
|
legacy.Dispatch("0BMj1hgG+kE", context));
|
|
}
|
|
|
|
[Fact]
|
|
public void CommandBufferWaitOnAddress_EncodesWaitAndCompletesWhenPredicateIsSatisfied()
|
|
{
|
|
const string nid = "V7GQTEeUfhw";
|
|
const ulong memoryBase = 0x2_0000_0000;
|
|
const ulong commandBufferAddress = memoryBase + 0x100;
|
|
const ulong recordBufferAddress = memoryBase + 0x200;
|
|
const ulong waitAddress = memoryBase + 0x800;
|
|
const ulong reference = 0xCAFE;
|
|
var memory = new FakeCpuMemory(memoryBase, 0x1000);
|
|
var context = new CpuContext(memory, Generation.Gen5);
|
|
var manager = CreateManagerWithExport(nid, "sceAmprCommandBufferWaitOnAddress");
|
|
|
|
context[CpuRegister.Rdi] = commandBufferAddress;
|
|
context[CpuRegister.Rsi] = recordBufferAddress;
|
|
context[CpuRegister.Rdx] = 0x100;
|
|
Assert.Equal(0, AmprExports.CommandBufferConstructor(context));
|
|
Assert.Equal(0, AmprExports.CommandBufferSetBuffer(context));
|
|
|
|
Assert.True(context.TryWriteUInt64(waitAddress, reference));
|
|
context[CpuRegister.Rdi] = commandBufferAddress;
|
|
context[CpuRegister.Rsi] = waitAddress;
|
|
context[CpuRegister.Rdx] = reference;
|
|
context[CpuRegister.Rcx] = 0;
|
|
context[CpuRegister.R8] = 0;
|
|
Assert.Equal(OrbisGen2Result.ORBIS_GEN2_OK, manager.Dispatch(nid, context));
|
|
|
|
Assert.True(context.TryReadUInt32(commandBufferAddress + 4, out var offset));
|
|
Assert.True(context.TryReadUInt32(commandBufferAddress + 8, out var commandCount));
|
|
Assert.Equal(12U, offset);
|
|
Assert.Equal(1U, commandCount);
|
|
|
|
Span<byte> record = stackalloc byte[12];
|
|
Assert.True(memory.TryRead(recordBufferAddress, record));
|
|
var expectedHeader = (uint)((waitAddress >> 16) & 0xFFFF0000UL) | 0x0201U;
|
|
Assert.Equal(expectedHeader, BinaryPrimitives.ReadUInt32LittleEndian(record));
|
|
Assert.Equal(0x00000800U, BinaryPrimitives.ReadUInt32LittleEndian(record[4..]));
|
|
Assert.Equal(0x0000CAFEU, BinaryPrimitives.ReadUInt32LittleEndian(record[8..]));
|
|
|
|
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK,
|
|
AmprExports.CompleteCommandBuffer(context, commandBufferAddress));
|
|
}
|
|
|
|
[Fact]
|
|
public void CommandBufferWaitOnAddress_SupportsAllModernAndLegacyComparisons()
|
|
{
|
|
const ulong memoryBase = 0x2_0000_0000;
|
|
const ulong commandBufferAddress = memoryBase + 0x100;
|
|
const ulong recordBufferAddress = memoryBase + 0x200;
|
|
var memory = new FakeCpuMemory(memoryBase, 0x1000);
|
|
var context = new CpuContext(memory, Generation.Gen5);
|
|
var modern = CreateManagerWithExport("V7GQTEeUfhw", "sceAmprCommandBufferWaitOnAddress");
|
|
var legacy = CreateManagerWithExport("DLfoNxTFNVk", "sceAmprCommandBufferWaitOnAddress_04_00");
|
|
var comparisons = new (uint Mode, ulong Value, ulong Reference, bool Legacy)[]
|
|
{
|
|
(0, 10, 10, false),
|
|
(1, 11, 10, false),
|
|
(2, 9, 10, false),
|
|
(3, 11, 10, false),
|
|
(4, 0, ulong.MaxValue, true),
|
|
(5, 1, 0, true),
|
|
(6, ulong.MaxValue, 0, true),
|
|
};
|
|
|
|
context[CpuRegister.Rdi] = commandBufferAddress;
|
|
context[CpuRegister.Rsi] = recordBufferAddress;
|
|
context[CpuRegister.Rdx] = 0x200;
|
|
Assert.Equal(0, AmprExports.CommandBufferConstructor(context));
|
|
Assert.Equal(0, AmprExports.CommandBufferSetBuffer(context));
|
|
|
|
for (var index = 0; index < comparisons.Length; index++)
|
|
{
|
|
var comparison = comparisons[index];
|
|
var address = memoryBase + 0x800 + (ulong)index * sizeof(ulong);
|
|
Assert.True(context.TryWriteUInt64(address, comparison.Value));
|
|
context[CpuRegister.Rdi] = commandBufferAddress;
|
|
context[CpuRegister.Rsi] = address;
|
|
context[CpuRegister.Rdx] = comparison.Reference;
|
|
context[CpuRegister.Rcx] = comparison.Mode;
|
|
context[CpuRegister.R8] = 0;
|
|
var manager = comparison.Legacy ? legacy : modern;
|
|
var nid = comparison.Legacy ? "DLfoNxTFNVk" : "V7GQTEeUfhw";
|
|
Assert.Equal(OrbisGen2Result.ORBIS_GEN2_OK, manager.Dispatch(nid, context));
|
|
}
|
|
|
|
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK,
|
|
AmprExports.CompleteCommandBuffer(context, commandBufferAddress));
|
|
}
|
|
|
|
[Fact]
|
|
public void CommandBufferWaitOnAddress0400_RegistersLegacyVariant()
|
|
{
|
|
const string nid = "DLfoNxTFNVk";
|
|
var manager = CreateManagerWithExport(nid, "sceAmprCommandBufferWaitOnAddress_04_00");
|
|
|
|
Assert.True(manager.TryGetExport(nid, out var export));
|
|
Assert.Equal("libSceAmpr", export.LibraryName);
|
|
Assert.Equal(Generation.Gen5, export.Target);
|
|
}
|
|
|
|
private static ulong Measure(
|
|
ModuleManager manager,
|
|
CpuContext context,
|
|
string nid,
|
|
ulong address,
|
|
ulong reference,
|
|
ulong compare,
|
|
ulong flush)
|
|
{
|
|
context[CpuRegister.Rdi] = address;
|
|
context[CpuRegister.Rsi] = reference;
|
|
context[CpuRegister.Rdx] = compare;
|
|
context[CpuRegister.Rcx] = flush;
|
|
Assert.Equal(OrbisGen2Result.ORBIS_GEN2_OK, manager.Dispatch(nid, context));
|
|
return context[CpuRegister.Rax];
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|