[Tests] Cover controller ports, user exports, trials and open-file renames
Co-authored-by: Sekai <140012992+SekaixX@users.noreply.github.com>
This commit is contained in:
parent
f945215935
commit
9d530f63f9
|
|
@ -0,0 +1,102 @@
|
|||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Libs.Kernel;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Libs.Tests.Kernel;
|
||||
|
||||
[Collection(KernelMemoryCompatStateCollection.Name)]
|
||||
public sealed class KernelFileRenameTests
|
||||
{
|
||||
private const ulong MemoryBase = 0x1_0000_0000;
|
||||
private const ulong SourcePathAddress = MemoryBase + 0x100;
|
||||
private const ulong DestinationPathAddress = MemoryBase + 0x300;
|
||||
private const ulong ReadBufferAddress = MemoryBase + 0x500;
|
||||
|
||||
[Fact]
|
||||
public void KernelRename_ReplacesDestinationWhileSourceDescriptorRemainsOpen()
|
||||
{
|
||||
var identity = Guid.NewGuid().ToString("N");
|
||||
var mountPoint = $"/rename-{identity}";
|
||||
var hostRoot = Path.Combine(Path.GetTempPath(), $"sharpemu-rename-{identity}");
|
||||
var sourceHostPath = Path.Combine(hostRoot, "STEMP000.DAT");
|
||||
var destinationHostPath = Path.Combine(hostRoot, "SDATA000.DAT");
|
||||
var sourceGuestPath = $"{mountPoint}/STEMP000.DAT";
|
||||
var destinationGuestPath = $"{mountPoint}/SDATA000.DAT";
|
||||
byte[] payload = [0x10, 0x20, 0x30, 0x40];
|
||||
var fileDescriptor = -1;
|
||||
|
||||
Directory.CreateDirectory(hostRoot);
|
||||
File.WriteAllBytes(sourceHostPath, payload);
|
||||
File.WriteAllBytes(destinationHostPath, [0xFF]);
|
||||
KernelMemoryCompatExports.RegisterGuestPathMount(mountPoint, hostRoot);
|
||||
|
||||
var memory = new FakeCpuMemory(MemoryBase, 0x1000);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
memory.WriteCString(SourcePathAddress, sourceGuestPath);
|
||||
memory.WriteCString(DestinationPathAddress, destinationGuestPath);
|
||||
|
||||
try
|
||||
{
|
||||
context[CpuRegister.Rdi] = SourcePathAddress;
|
||||
context[CpuRegister.Rsi] = 0x2;
|
||||
Assert.Equal(
|
||||
(int)OrbisGen2Result.ORBIS_GEN2_OK,
|
||||
KernelExports.KernelOpen(context));
|
||||
fileDescriptor = unchecked((int)context[CpuRegister.Rax]);
|
||||
Assert.True(fileDescriptor >= 3);
|
||||
|
||||
context[CpuRegister.Rdi] = SourcePathAddress;
|
||||
context[CpuRegister.Rsi] = DestinationPathAddress;
|
||||
Assert.Equal(
|
||||
(int)OrbisGen2Result.ORBIS_GEN2_OK,
|
||||
KernelMemoryCompatExports.KernelRename(context));
|
||||
Assert.False(File.Exists(sourceHostPath));
|
||||
var renamedPayload = new byte[payload.Length];
|
||||
using (var destinationStream = new FileStream(
|
||||
destinationHostPath,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.ReadWrite | FileShare.Delete))
|
||||
{
|
||||
destinationStream.ReadExactly(renamedPayload);
|
||||
}
|
||||
Assert.Equal(payload, renamedPayload);
|
||||
|
||||
context[CpuRegister.Rdi] = unchecked((uint)fileDescriptor);
|
||||
context[CpuRegister.Rsi] = ReadBufferAddress;
|
||||
context[CpuRegister.Rdx] = unchecked((uint)payload.Length);
|
||||
Assert.Equal(
|
||||
(int)OrbisGen2Result.ORBIS_GEN2_OK,
|
||||
KernelMemoryCompatExports.KernelReadUnderscore(context));
|
||||
Assert.Equal((ulong)payload.Length, context[CpuRegister.Rax]);
|
||||
|
||||
Span<byte> actual = stackalloc byte[payload.Length];
|
||||
Assert.True(memory.TryRead(ReadBufferAddress, actual));
|
||||
Assert.True(actual.SequenceEqual(payload));
|
||||
|
||||
context[CpuRegister.Rdi] = unchecked((uint)fileDescriptor);
|
||||
Assert.Equal(
|
||||
(int)OrbisGen2Result.ORBIS_GEN2_OK,
|
||||
KernelMemoryCompatExports.KernelClose(context));
|
||||
fileDescriptor = -1;
|
||||
Assert.Equal(payload, File.ReadAllBytes(destinationHostPath));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fileDescriptor >= 0)
|
||||
{
|
||||
context[CpuRegister.Rdi] = unchecked((uint)fileDescriptor);
|
||||
_ = KernelMemoryCompatExports.KernelClose(context);
|
||||
}
|
||||
|
||||
_ = KernelMemoryCompatExports.UnregisterGuestPathMount(mountPoint);
|
||||
if (Directory.Exists(hostRoot))
|
||||
{
|
||||
Directory.Delete(hostRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,29 @@ public sealed class NpEntitlementAccessExportsTests
|
|||
{
|
||||
private const ulong MemoryBase = 0x1_0000_0000;
|
||||
|
||||
[Fact]
|
||||
public void GetGameTrialsFlagReportsAFullGameAndOnlyWritesOneWord()
|
||||
{
|
||||
var memory = new FakeCpuMemory(MemoryBase, 0x100);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
Assert.True(memory.TryWrite(MemoryBase, new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }));
|
||||
context[CpuRegister.Rdi] = MemoryBase;
|
||||
Assert.Equal(0, NpEntitlementAccessExports.NpEntitlementAccessGetGameTrialsFlag(context));
|
||||
Span<byte> actual = stackalloc byte[8];
|
||||
Assert.True(memory.TryRead(MemoryBase, actual));
|
||||
Assert.Equal(new byte[] { 0, 0, 0, 0, 5, 6, 7, 8 }, actual.ToArray());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0UL, unchecked((int)0x817D0002))]
|
||||
[InlineData(0x2_0000_0000UL, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT)]
|
||||
public void GetGameTrialsFlagValidatesOutputMemory(ulong address, int expected)
|
||||
{
|
||||
var context = new CpuContext(new FakeCpuMemory(MemoryBase, 0x100), Generation.Gen5);
|
||||
context[CpuRegister.Rdi] = address;
|
||||
Assert.Equal(expected, NpEntitlementAccessExports.NpEntitlementAccessGetGameTrialsFlag(context));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GHOST2APP0000000")]
|
||||
[InlineData("GHOST2BASE000000")]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using Xunit;
|
|||
|
||||
namespace SharpEmu.Libs.Tests.Pad;
|
||||
|
||||
public sealed class PadExportsTests
|
||||
public sealed class PadExportsTests : IDisposable
|
||||
{
|
||||
private const ulong Base = 0x1_0000_0000;
|
||||
private const int InvalidHandle = unchecked((int)0x80920003);
|
||||
|
|
@ -17,29 +17,42 @@ public sealed class PadExportsTests
|
|||
|
||||
public PadExportsTests()
|
||||
{
|
||||
PadExports.ResetOpenedPadForTests();
|
||||
_ctx = new CpuContext(_memory, Generation.Gen5);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
public void Dispose() => PadExports.ResetOpenedPadForTests();
|
||||
|
||||
[NativeX64Theory]
|
||||
[InlineData(0, 0)]
|
||||
[InlineData(1, 0)]
|
||||
[InlineData(2, InvalidHandle)]
|
||||
[InlineData(-1, InvalidHandle)]
|
||||
public void SetTiltCorrectionState_ValidatesHandle(int handle, int expected)
|
||||
{
|
||||
if (handle == 1)
|
||||
{
|
||||
Assert.Equal(1, OpenPad(0x10000000, 0));
|
||||
}
|
||||
|
||||
_ctx[CpuRegister.Rdi] = unchecked((ulong)handle);
|
||||
Assert.Equal(expected, PadExports.PadSetTiltCorrectionState(_ctx));
|
||||
}
|
||||
|
||||
// ABI: int scePadResetOrientation(int32_t handle) — handle only, no out
|
||||
// parameter, so the only failure mode is a bad handle.
|
||||
[Theory]
|
||||
[NativeX64Theory]
|
||||
[InlineData(0, 0)]
|
||||
[InlineData(1, 0)]
|
||||
[InlineData(2, InvalidHandle)]
|
||||
[InlineData(-1, InvalidHandle)]
|
||||
public void ResetOrientation_ValidatesHandle(int handle, int expected)
|
||||
{
|
||||
if (handle == 1)
|
||||
{
|
||||
Assert.Equal(1, OpenPad(0x10000000, 0));
|
||||
}
|
||||
|
||||
_ctx[CpuRegister.Rdi] = unchecked((ulong)handle);
|
||||
Assert.Equal(expected, PadExports.PadResetOrientation(_ctx));
|
||||
Assert.Equal(unchecked((ulong)expected), _ctx[CpuRegister.Rax]);
|
||||
|
|
@ -132,4 +145,78 @@ public sealed class PadExportsTests
|
|||
PadExports.ResetOpenedPadForTests();
|
||||
}
|
||||
}
|
||||
|
||||
[NativeX64Fact]
|
||||
public void SpecialAndRemotePortsHaveIndependentLiveHandles()
|
||||
{
|
||||
const int noHandle = unchecked((int)0x80920008);
|
||||
const int alreadyOpened = unchecked((int)0x80920004);
|
||||
const ulong dataAddress = Base + 0x200;
|
||||
|
||||
Assert.Equal(0, PadExports.PadInit(_ctx));
|
||||
_ctx[CpuRegister.Rdi] = 0xFF;
|
||||
_ctx[CpuRegister.Rsi] = 16;
|
||||
_ctx[CpuRegister.Rdx] = 0;
|
||||
Assert.Equal(noHandle, PadExports.PadGetHandle(_ctx));
|
||||
|
||||
var standard = OpenPad(0x10000000, 0);
|
||||
var special = OpenPad(0x10000000, 2);
|
||||
var remote = OpenPad(0xFF, 16);
|
||||
Assert.Equal(1, standard);
|
||||
Assert.Equal(2, special);
|
||||
Assert.Equal(3, remote);
|
||||
Assert.Equal(alreadyOpened, OpenPad(0xFF, 16));
|
||||
Assert.Equal(remote, PadExports.PadGetHandle(_ctx));
|
||||
|
||||
foreach (var handle in new[] { standard, special, remote })
|
||||
{
|
||||
_ctx[CpuRegister.Rdi] = unchecked((ulong)handle);
|
||||
_ctx[CpuRegister.Rsi] = dataAddress;
|
||||
Assert.Equal(0, PadExports.PadGetControllerInformation(_ctx));
|
||||
Assert.Equal(0, PadExports.PadReadState(_ctx));
|
||||
}
|
||||
|
||||
_ctx[CpuRegister.Rdi] = unchecked((ulong)remote);
|
||||
Assert.Equal(0, PadExports.PadClose(_ctx));
|
||||
Assert.Equal(InvalidHandle, PadExports.PadReadState(_ctx));
|
||||
Assert.Equal(InvalidHandle, PadExports.PadClose(_ctx));
|
||||
|
||||
_ctx[CpuRegister.Rdi] = 0xFF;
|
||||
_ctx[CpuRegister.Rsi] = 16;
|
||||
_ctx[CpuRegister.Rdx] = 0;
|
||||
Assert.Equal(noHandle, PadExports.PadGetHandle(_ctx));
|
||||
_ctx[CpuRegister.Rdi] = 0x10000000;
|
||||
_ctx[CpuRegister.Rsi] = 0;
|
||||
Assert.Equal(standard, PadExports.PadGetHandle(_ctx));
|
||||
_ctx[CpuRegister.Rsi] = 2;
|
||||
Assert.Equal(special, PadExports.PadGetHandle(_ctx));
|
||||
Assert.True(OpenPad(0xFF, 16) > remote);
|
||||
}
|
||||
|
||||
[NativeX64Theory]
|
||||
[InlineData(-1, 0, 0, unchecked((int)0x80920008))]
|
||||
[InlineData(0x10000001, 0, 0, unchecked((int)0x80920007))]
|
||||
[InlineData(0x10000000, 1, 0, unchecked((int)0x80920007))]
|
||||
[InlineData(0x10000000, 16, 0, unchecked((int)0x80920007))]
|
||||
[InlineData(0xFF, 0, 0, unchecked((int)0x80920007))]
|
||||
[InlineData(0xFF, 16, 1, unchecked((int)0x80920007))]
|
||||
public void Open_RejectsUnsupportedTuples(int userId, int type, int index, int expected)
|
||||
{
|
||||
PadExports.PadInit(_ctx);
|
||||
_ctx[CpuRegister.Rdi] = unchecked((ulong)userId);
|
||||
_ctx[CpuRegister.Rsi] = unchecked((ulong)type);
|
||||
_ctx[CpuRegister.Rdx] = unchecked((ulong)index);
|
||||
_ctx[CpuRegister.Rcx] = 0;
|
||||
Assert.Equal(expected, PadExports.PadOpen(_ctx));
|
||||
}
|
||||
|
||||
private int OpenPad(int userId, int type)
|
||||
{
|
||||
PadExports.PadInit(_ctx);
|
||||
_ctx[CpuRegister.Rdi] = unchecked((ulong)userId);
|
||||
_ctx[CpuRegister.Rsi] = unchecked((ulong)type);
|
||||
_ctx[CpuRegister.Rdx] = 0;
|
||||
_ctx[CpuRegister.Rcx] = 0;
|
||||
return PadExports.PadOpen(_ctx);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Libs.UserService;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Libs.Tests.UserService;
|
||||
|
||||
public sealed class UserServiceExportsTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0x10000000)]
|
||||
[InlineData(1)]
|
||||
public void GetUserNumberWritesOneBasedSlotWithoutOverwritingAdjacentData(int userId)
|
||||
{
|
||||
const ulong address = 0x1_0000_0000;
|
||||
var memory = new FakeCpuMemory(address, 0x100);
|
||||
var ctx = new CpuContext(memory, Generation.Gen5);
|
||||
Assert.True(memory.TryWrite(address, new byte[] { 0xEE, 0xEE, 0xEE, 0xEE, 0xAA, 0xBB, 0xCC, 0xDD }));
|
||||
ctx[CpuRegister.Rdi] = unchecked((ulong)userId);
|
||||
ctx[CpuRegister.Rsi] = address;
|
||||
Assert.Equal(0, UserServiceExports.UserServiceGetUserNumber(ctx));
|
||||
Span<byte> actual = stackalloc byte[8];
|
||||
Assert.True(memory.TryRead(address, actual));
|
||||
Assert.Equal(new byte[] { 1, 0, 0, 0, 0xAA, 0xBB, 0xCC, 0xDD }, actual.ToArray());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0x10000001, 0x1_0000_0000UL, unchecked((int)0x80960009))]
|
||||
[InlineData(0x10000000, 0UL, unchecked((int)0x80960005))]
|
||||
[InlineData(0x10000000, 0x2_0000_0000UL, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT)]
|
||||
public void GetUserNumberValidatesUserAndOutput(int userId, ulong address, int expected)
|
||||
{
|
||||
var ctx = new CpuContext(new FakeCpuMemory(0x1_0000_0000, 0x100), Generation.Gen5);
|
||||
ctx[CpuRegister.Rdi] = unchecked((ulong)userId);
|
||||
ctx[CpuRegister.Rsi] = address;
|
||||
Assert.Equal(expected, UserServiceExports.UserServiceGetUserNumber(ctx));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TerminateResetsTheLoginEvent()
|
||||
{
|
||||
const ulong address = 0x1_0000_0000;
|
||||
var memory = new FakeCpuMemory(address, 0x100);
|
||||
var ctx = new CpuContext(memory, Generation.Gen5);
|
||||
Assert.Equal(0, UserServiceExports.UserServiceTerminate(ctx));
|
||||
Assert.Equal(0, UserServiceExports.UserServiceInitialize(ctx));
|
||||
ctx[CpuRegister.Rdi] = address;
|
||||
Assert.Equal(0, UserServiceExports.UserServiceGetEvent(ctx));
|
||||
Assert.Equal(unchecked((int)0x80960007), UserServiceExports.UserServiceGetEvent(ctx));
|
||||
Assert.Equal(0, UserServiceExports.UserServiceTerminate(ctx));
|
||||
Assert.Equal(0UL, ctx[CpuRegister.Rax]);
|
||||
Assert.Equal(0, UserServiceExports.UserServiceInitialize(ctx));
|
||||
Assert.Equal(0, UserServiceExports.UserServiceGetEvent(ctx));
|
||||
Assert.Equal(0, UserServiceExports.UserServiceTerminate(ctx));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue