sharpemu/tests/SharpEmu.Libs.Tests/Np/NpCommerceExportsTests.cs

213 lines
8.7 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Np;
using System.Buffers.Binary;
using Xunit;
namespace SharpEmu.Libs.Tests.Np;
[CollectionDefinition("NpCommerce", DisableParallelization = true)]
public sealed class NpCommerceCollection;
[Collection("NpCommerce")]
public sealed class NpCommerceExportsTests : IDisposable
{
private const ulong MemoryBase = 0x1_0000_0000;
private readonly FakeCpuMemory _memory = new(MemoryBase, 0x1_0000);
private readonly CpuContext _context;
public NpCommerceExportsTests()
{
NpCommerceDialogState.ResetForTests();
_context = new CpuContext(_memory, Generation.Gen5);
}
public void Dispose() => NpCommerceDialogState.ResetForTests();
[Fact]
public void OfflineLifecycle_ReturnsCanceledResultAndPreservesUserData()
{
Assert.Equal(0, NpCommerceExports.NpCommerceDialogInitialize(_context));
Assert.Equal(
NpCommerceDialogState.ErrorAlreadyInitialized,
NpCommerceExports.NpCommerceDialogInitialize(_context));
const ulong userData = 0x1_1234_5678;
var parameter = WriteRequest(
MemoryBase + 0x100,
size: 0x80,
mode: 5,
userData,
features: 0x55,
targets: ["plus", "subscription"]);
_context[CpuRegister.Rdi] = parameter;
Assert.Equal(0, NpCommerceExports.NpCommerceDialogOpen(_context));
Assert.Equal(NpCommerceDialogStatus.Running, NpCommerceDialogState.Status);
var request = Assert.IsType<NpCommerceDialogRequest>(NpCommerceDialogState.RequestForTests);
Assert.False(request.IsOpen2);
Assert.Equal(9, request.UserId);
Assert.Equal(5, request.Mode);
Assert.Equal(3U, request.ServiceLabel);
Assert.Equal(0x55UL, request.Features);
Assert.Equal(userData, request.UserData);
Assert.Equal(["plus", "subscription"], request.Targets);
Assert.Equal(3, NpCommerceExports.NpCommerceDialogUpdateStatus(_context));
Assert.Equal(NpCommerceDialogStatus.Finished, NpCommerceDialogState.Status);
var resultAddress = MemoryBase + 0x800;
_context[CpuRegister.Rdi] = resultAddress;
Assert.Equal(0, NpCommerceExports.NpCommerceDialogGetResult(_context));
Span<byte> result = stackalloc byte[0x30];
Assert.True(_memory.TryRead(resultAddress, result));
Assert.Equal(NpCommerceDialogState.ResultUserCanceled, BinaryPrimitives.ReadInt32LittleEndian(result));
Assert.Equal(0, result[0x04]);
Assert.Equal(userData, BinaryPrimitives.ReadUInt64LittleEndian(result[0x08..]));
Assert.Equal(0, NpCommerceExports.NpCommerceDialogTerminate(_context));
Assert.Equal(0, NpCommerceExports.NpCommerceDialogUpdateStatus(_context));
Assert.Equal(
NpCommerceDialogState.ErrorNotInitialized,
NpCommerceExports.NpCommerceDialogTerminate(_context));
}
[Fact]
public void Open2_AcceptsExtendedStructureAndIgnoresFeaturesOutsidePlusMode()
{
Assert.Equal(0, NpCommerceExports.NpCommerceDialogInitialize(_context));
_context[CpuRegister.Rdi] = WriteRequest(
MemoryBase + 0x1000,
size: 0x88,
mode: 1,
userData: 0xAABB,
features: 0xFFFF,
targets: ["product-label"]);
Assert.Equal(0, NpCommerceExports.NpCommerceDialogOpen2(_context));
var request = Assert.IsType<NpCommerceDialogRequest>(NpCommerceDialogState.RequestForTests);
Assert.True(request.IsOpen2);
Assert.Equal(0UL, request.Features);
Assert.Equal("product-label", Assert.Single(request.Targets));
}
[Fact]
public void FinishedDialog_CanOpenAgain()
{
Assert.Equal(0, NpCommerceExports.NpCommerceDialogInitialize(_context));
_context[CpuRegister.Rdi] = WriteRequest(MemoryBase + 0x2000, 0x80, 0, 1);
Assert.Equal(0, NpCommerceExports.NpCommerceDialogOpen(_context));
Assert.Equal(3, NpCommerceExports.NpCommerceDialogUpdateStatus(_context));
_context[CpuRegister.Rdi] = WriteRequest(MemoryBase + 0x2200, 0x80, 2, 2);
Assert.Equal(0, NpCommerceExports.NpCommerceDialogOpen(_context));
Assert.Equal(NpCommerceDialogStatus.Running, NpCommerceDialogState.Status);
Assert.Equal(2UL, NpCommerceDialogState.RequestForTests!.UserData);
}
[Fact]
public void InvalidStatePointersAndParameters_ReturnDefinedErrors()
{
_context[CpuRegister.Rdi] = 0xDEAD_BEEF;
Assert.Equal(
NpCommerceDialogState.ErrorNotInitialized,
NpCommerceExports.NpCommerceDialogOpen(_context));
Assert.Equal(0, NpCommerceExports.NpCommerceDialogInitialize(_context));
_context[CpuRegister.Rdi] = 0;
Assert.Equal(
NpCommerceDialogState.ErrorArgumentNull,
NpCommerceExports.NpCommerceDialogOpen(_context));
_context[CpuRegister.Rdi] = WriteRequest(MemoryBase + 0x3000, 0x70, 0, 0);
Assert.Equal(
NpCommerceDialogState.ErrorParameterInvalid,
NpCommerceExports.NpCommerceDialogOpen(_context));
_context[CpuRegister.Rdi] = WriteRequest(MemoryBase + 0x3200, 0x80, 9, 0x99);
Assert.Equal(
NpCommerceDialogState.ErrorParameterInvalid,
NpCommerceExports.NpCommerceDialogOpen(_context));
Assert.Equal(NpCommerceDialogStatus.Finished, NpCommerceDialogState.Status);
_context[CpuRegister.Rdi] = 0;
Assert.Equal(
NpCommerceDialogState.ErrorArgumentNull,
NpCommerceExports.NpCommerceDialogGetResult(_context));
}
[Fact]
public void GetResultBeforeCompletionAndBadGuestMemory_ReturnErrors()
{
Assert.Equal(0, NpCommerceExports.NpCommerceDialogInitialize(_context));
_context[CpuRegister.Rdi] = MemoryBase + 0x4000;
Assert.Equal(
NpCommerceDialogState.ErrorNotFinished,
NpCommerceExports.NpCommerceDialogGetResult(_context));
_context[CpuRegister.Rdi] = 0xDEAD_BEEF;
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT,
NpCommerceExports.NpCommerceDialogOpen(_context));
_context[CpuRegister.Rdi] = WriteRequest(MemoryBase + 0x4200, 0x80, 0, 0);
Assert.Equal(0, NpCommerceExports.NpCommerceDialogOpen(_context));
Assert.Equal(3, NpCommerceExports.NpCommerceDialogUpdateStatus(_context));
_context[CpuRegister.Rdi] = 0xDEAD_BEEF;
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT,
NpCommerceExports.NpCommerceDialogGetResult(_context));
}
[Fact]
public void ConcurrentInitialize_HasOneWinner()
{
var results = new int[64];
Parallel.For(0, results.Length, index =>
{
var context = new CpuContext(_memory, Generation.Gen5);
results[index] = NpCommerceExports.NpCommerceDialogInitialize(context);
});
Assert.Single(results, result => result == 0);
Assert.Equal(results.Length - 1, results.Count(result => result == NpCommerceDialogState.ErrorAlreadyInitialized));
}
private ulong WriteRequest(
ulong address,
int size,
int mode,
ulong userData,
ulong features = 0,
IReadOnlyList<string>? targets = null)
{
Span<byte> parameter = stackalloc byte[0x88];
parameter.Clear();
BinaryPrimitives.WriteUInt64LittleEndian(parameter, 0x30);
BinaryPrimitives.WriteInt32LittleEndian(parameter[0x30..], size);
BinaryPrimitives.WriteInt32LittleEndian(parameter[0x34..], 9);
BinaryPrimitives.WriteInt32LittleEndian(parameter[0x38..], mode);
BinaryPrimitives.WriteUInt32LittleEndian(parameter[0x3C..], 3);
BinaryPrimitives.WriteUInt64LittleEndian(parameter[0x50..], features);
BinaryPrimitives.WriteUInt64LittleEndian(parameter[0x58..], userData);
if (targets is { Count: > 0 })
{
var pointerArray = address + 0x200;
BinaryPrimitives.WriteUInt64LittleEndian(parameter[0x40..], pointerArray);
BinaryPrimitives.WriteUInt32LittleEndian(parameter[0x48..], checked((uint)targets.Count));
for (var index = 0; index < targets.Count; index++)
{
var textAddress = address + 0x300 + checked((ulong)(index * 0x100));
_memory.WriteCString(textAddress, targets[index]);
Assert.True(_context.TryWriteUInt64(pointerArray + checked((ulong)(index * sizeof(ulong))), textAddress));
}
}
Assert.True(_memory.TryWrite(address, parameter));
return address;
}
}