181 lines
8.4 KiB
C#
181 lines
8.4 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using System.Buffers.Binary;
|
|
using SharpEmu.HLE;
|
|
using Xunit;
|
|
|
|
namespace SharpEmu.Libs.Tests.VideoOut;
|
|
|
|
public sealed class VideoOutOutputSupportTests
|
|
{
|
|
private const string OpenNid = "Up36PTk687E";
|
|
private const string CloseNid = "uquVH4-Du78";
|
|
private const string OutputSupportNid = "Nv8c-Kb+DUM";
|
|
private const string OutputStatusNid = "utPrVdxio-8";
|
|
private const string ConfigureOutputNid = "w0hLuNarQxY";
|
|
private const ulong MemoryBase = 0x1_0000_0000;
|
|
private const ulong OptionsAddress = MemoryBase + 0x100;
|
|
private static readonly ulong InvalidValue = unchecked((ulong)(int)0x80290001);
|
|
private static readonly ulong InvalidHandle = unchecked((ulong)(int)0x8029000B);
|
|
private static readonly ulong UnsupportedOutputMode = unchecked((ulong)(int)0x80290016);
|
|
private static readonly ulong InvalidOption = unchecked((ulong)(int)0x8029001A);
|
|
private static readonly ulong MemoryFault =
|
|
unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
|
|
|
[Theory]
|
|
[InlineData(Generation.Gen4)]
|
|
[InlineData(Generation.Gen5)]
|
|
public void OutputStatusUsesRefreshRateCodeAndPreservesBufferBoundaries(Generation generation)
|
|
{
|
|
var manager = new ModuleManager();
|
|
manager.RegisterExports(SharpEmu.Generated.SysAbiExportRegistry.CreateExports(generation));
|
|
var memory = new FakeCpuMemory(MemoryBase, 0x1000);
|
|
var context = new CpuContext(memory, generation);
|
|
Assert.True(manager.TryDispatch(OpenNid, context, out _));
|
|
var handle = context[CpuRegister.Rax];
|
|
Assert.InRange(handle, 1UL, (ulong)int.MaxValue);
|
|
try
|
|
{
|
|
var buffer = Enumerable.Repeat((byte)0xA5, 0x32).ToArray();
|
|
Assert.True(memory.TryWrite(OptionsAddress, buffer));
|
|
context[CpuRegister.Rdi] = handle;
|
|
context[CpuRegister.Rsi] = OptionsAddress + 1;
|
|
Assert.True(manager.TryDispatch(OutputStatusNid, context, out _));
|
|
Assert.Equal(0UL, context[CpuRegister.Rax]);
|
|
Assert.True(memory.TryRead(OptionsAddress, buffer));
|
|
|
|
var expected = new byte[0x32];
|
|
expected[0] = 0xA5;
|
|
expected[^1] = 0xA5;
|
|
BinaryPrimitives.WriteUInt32LittleEndian(expected.AsSpan(1), 1);
|
|
BinaryPrimitives.WriteUInt32LittleEndian(expected.AsSpan(5), 1);
|
|
BinaryPrimitives.WriteUInt64LittleEndian(expected.AsSpan(9), 3);
|
|
Assert.Equal(expected, buffer);
|
|
|
|
context[CpuRegister.Rsi] = 0;
|
|
Assert.True(manager.TryDispatch(OutputStatusNid, context, out _));
|
|
Assert.Equal(unchecked((ulong)(int)0x80290002), context[CpuRegister.Rax]);
|
|
context[CpuRegister.Rsi] = MemoryBase + 0xFF0;
|
|
Assert.True(manager.TryDispatch(OutputStatusNid, context, out _));
|
|
Assert.Equal(MemoryFault, context[CpuRegister.Rax]);
|
|
context[CpuRegister.Rsi] = OptionsAddress + 1;
|
|
context[CpuRegister.Rdi] = ulong.MaxValue;
|
|
Assert.True(manager.TryDispatch(OutputStatusNid, context, out _));
|
|
Assert.Equal(InvalidHandle, context[CpuRegister.Rax]);
|
|
}
|
|
finally
|
|
{
|
|
context[CpuRegister.Rdi] = handle;
|
|
Assert.True(manager.TryDispatch(CloseNid, context, out _));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Gen5QueryReportsCapabilitiesAndValidatesArguments()
|
|
{
|
|
var gen4Manager = new ModuleManager();
|
|
gen4Manager.RegisterExports(
|
|
SharpEmu.Generated.SysAbiExportRegistry.CreateExports(Generation.Gen4));
|
|
Assert.False(gen4Manager.TryGetExport(OutputSupportNid, out _));
|
|
|
|
var manager = new ModuleManager();
|
|
manager.RegisterExports(
|
|
SharpEmu.Generated.SysAbiExportRegistry.CreateExports(Generation.Gen5));
|
|
Assert.True(manager.TryGetExport(OutputSupportNid, out var export));
|
|
Assert.Equal("sceVideoOutIsOutputSupported", export.Name);
|
|
Assert.Equal("libSceVideoOut", export.LibraryName);
|
|
Assert.Equal(Generation.Gen5, export.Target);
|
|
|
|
var memory = new FakeCpuMemory(MemoryBase, 0x1000);
|
|
var context = new CpuContext(memory, Generation.Gen5);
|
|
context[CpuRegister.Rdi] = 0;
|
|
context[CpuRegister.Rsi] = 0;
|
|
context[CpuRegister.Rdx] = 0;
|
|
context[CpuRegister.Rcx] = 0;
|
|
Assert.True(manager.TryDispatch(OpenNid, context, out _));
|
|
var handle = context[CpuRegister.Rax];
|
|
Assert.NotEqual(0UL, handle);
|
|
|
|
try
|
|
{
|
|
Assert.Equal(1UL, DispatchOutputSupport(manager, context, handle, 1));
|
|
Assert.Equal(0UL, DispatchOutputSupport(manager, context, handle, 15));
|
|
Assert.Equal(
|
|
InvalidHandle,
|
|
DispatchOutputSupport(manager, context, ulong.MaxValue, 1));
|
|
Assert.Equal(
|
|
InvalidValue,
|
|
DispatchOutputSupport(manager, context, handle, 1, reservedPointer: 1));
|
|
Assert.Equal(
|
|
InvalidValue,
|
|
DispatchOutputSupport(manager, context, handle, 1, reserved: 1));
|
|
Assert.Equal(
|
|
1UL,
|
|
DispatchOutputSupport(manager, context, handle, 1, OptionsAddress));
|
|
Assert.Equal(
|
|
MemoryFault,
|
|
DispatchOutputSupport(manager, context, handle, 1, MemoryBase + 0x1000));
|
|
|
|
Assert.True(memory.TryWrite(OptionsAddress, new byte[] { 1 }));
|
|
Assert.Equal(
|
|
InvalidOption,
|
|
DispatchOutputSupport(manager, context, handle, 1, OptionsAddress));
|
|
Assert.Equal(
|
|
UnsupportedOutputMode,
|
|
DispatchOutputSupport(manager, context, handle, 2));
|
|
|
|
// Configuration must use the same validation as the capability query.
|
|
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
|
|
Assert.Equal(UnsupportedOutputMode, context[CpuRegister.Rax]);
|
|
DispatchOutputSupport(manager, context, handle, 1, OptionsAddress);
|
|
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
|
|
Assert.Equal(InvalidOption, context[CpuRegister.Rax]);
|
|
Assert.True(memory.TryWrite(OptionsAddress, new byte[0x40]));
|
|
DispatchOutputSupport(manager, context, handle, 1, OptionsAddress);
|
|
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
|
|
Assert.Equal(0UL, context[CpuRegister.Rax]);
|
|
DispatchOutputSupport(manager, context, handle, 15);
|
|
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
|
|
Assert.Equal(unchecked((ulong)(int)0x80290019), context[CpuRegister.Rax]);
|
|
DispatchOutputSupport(manager, context, handle, 1, reserved: 1);
|
|
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
|
|
Assert.Equal(InvalidValue, context[CpuRegister.Rax]);
|
|
DispatchOutputSupport(manager, context, handle, 1, reservedPointer: 1);
|
|
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
|
|
Assert.Equal(InvalidValue, context[CpuRegister.Rax]);
|
|
DispatchOutputSupport(manager, context, handle, 1, MemoryBase + 0x1000);
|
|
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
|
|
Assert.Equal(MemoryFault, context[CpuRegister.Rax]);
|
|
DispatchOutputSupport(manager, context, ulong.MaxValue, 1);
|
|
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
|
|
Assert.Equal(InvalidHandle, context[CpuRegister.Rax]);
|
|
}
|
|
finally
|
|
{
|
|
context[CpuRegister.Rdi] = handle;
|
|
_ = manager.TryDispatch(CloseNid, context, out _);
|
|
}
|
|
}
|
|
|
|
private static ulong DispatchOutputSupport(
|
|
ModuleManager manager,
|
|
CpuContext context,
|
|
ulong handle,
|
|
ulong mode,
|
|
ulong optionsAddress = 0,
|
|
ulong reservedPointer = 0,
|
|
ulong reserved = 0)
|
|
{
|
|
context[CpuRegister.Rdi] = handle;
|
|
context[CpuRegister.Rsi] = mode;
|
|
context[CpuRegister.Rdx] = optionsAddress;
|
|
context[CpuRegister.Rcx] = reservedPointer;
|
|
context[CpuRegister.R8] = reserved;
|
|
context[CpuRegister.R9] = 0x1FC;
|
|
|
|
Assert.True(manager.TryDispatch(OutputSupportNid, context, out _));
|
|
return context[CpuRegister.Rax];
|
|
}
|
|
}
|