[Tests] Cover the AudioOut2, save data, Http2 and dialog exports

Co-authored-by: Sekai <140012992+igettingit@users.noreply.github.com>
This commit is contained in:
ParantezTech 2026-10-07 02:43:11 +03:00
parent edc2d17cf6
commit 810ff6a606
10 changed files with 974 additions and 6 deletions

View File

@ -0,0 +1,92 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using SharpEmu.HLE;
using SharpEmu.Libs.Audio;
using Xunit;
namespace SharpEmu.Libs.Tests.Audio;
public sealed class AudioOut2ContextGetQueueLevelExportsTests
{
private const ulong MemoryBase = 0x1_0000_0000;
private const ulong ParamAddress = MemoryBase + 0x100;
private const ulong ContextMemoryAddress = MemoryBase + 0x200;
private const ulong OutContextAddress = MemoryBase + 0x300;
private const ulong LevelAddress = MemoryBase + 0x400;
private const ulong AvailableAddress = MemoryBase + 0x404;
private const uint QueueDepth = 7;
private static (CpuContext Context, FakeCpuMemory Memory, ulong Handle) CreateContext()
{
var memory = new FakeCpuMemory(MemoryBase, 0x2000);
var ctx = new CpuContext(memory, Generation.Gen5);
Span<byte> param = stackalloc byte[0x40];
param.Clear();
BinaryPrimitives.WriteUInt32LittleEndian(param[0x0C..], QueueDepth);
BinaryPrimitives.WriteUInt32LittleEndian(param[0x10..], 512);
Assert.True(memory.TryWrite(ParamAddress, param));
ctx[CpuRegister.Rdi] = ParamAddress;
ctx[CpuRegister.Rsi] = ContextMemoryAddress;
ctx[CpuRegister.Rdx] = 0x1000;
ctx[CpuRegister.Rcx] = OutContextAddress;
Assert.Equal(0, AudioOut2Exports.AudioOut2ContextCreate(ctx));
Span<byte> handleBytes = stackalloc byte[8];
Assert.True(memory.TryRead(OutContextAddress, handleBytes));
return (ctx, memory, BinaryPrimitives.ReadUInt64LittleEndian(handleBytes));
}
private static uint ReadU32(FakeCpuMemory memory, ulong address)
{
Span<byte> value = stackalloc byte[4];
Assert.True(memory.TryRead(address, value));
return BinaryPrimitives.ReadUInt32LittleEndian(value);
}
[Fact]
public void ContextGetQueueLevel_AvailableOnlyWritesQueueCapacity()
{
var (ctx, memory, handle) = CreateContext();
ctx[CpuRegister.Rdi] = handle;
ctx[CpuRegister.Rsi] = 0;
ctx[CpuRegister.Rdx] = AvailableAddress;
var result = AudioOut2Exports.AudioOut2ContextGetQueueLevel(ctx);
Assert.Equal(0, result);
Assert.Equal(QueueDepth, ReadU32(memory, AvailableAddress));
}
[Fact]
public void ContextGetQueueLevel_LevelOnlyWritesZero()
{
var (ctx, memory, handle) = CreateContext();
ctx[CpuRegister.Rdi] = handle;
ctx[CpuRegister.Rsi] = LevelAddress;
ctx[CpuRegister.Rdx] = 0;
var result = AudioOut2Exports.AudioOut2ContextGetQueueLevel(ctx);
Assert.Equal(0, result);
Assert.Equal(0u, ReadU32(memory, LevelAddress));
}
[Fact]
public void ContextGetQueueLevel_BothOutputsRemainDistinct()
{
var (ctx, memory, handle) = CreateContext();
ctx[CpuRegister.Rdi] = handle;
ctx[CpuRegister.Rsi] = LevelAddress;
ctx[CpuRegister.Rdx] = AvailableAddress;
var result = AudioOut2Exports.AudioOut2ContextGetQueueLevel(ctx);
Assert.Equal(0, result);
Assert.Equal(0u, ReadU32(memory, LevelAddress));
Assert.Equal(QueueDepth, ReadU32(memory, AvailableAddress));
}
}

View File

@ -0,0 +1,68 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using SharpEmu.HLE;
using SharpEmu.Libs.Audio;
using Xunit;
namespace SharpEmu.Libs.Tests.Audio;
public sealed class AudioOut2ContextQueryMemoryExportsTests
{
private const int InvalidPointer = unchecked((int)0x8026_800C);
private const ulong MemoryBase = 0x10_0000_0000;
private const ulong ParamAddress = MemoryBase + 0x100;
private const ulong StackPointer = MemoryBase + 0x400;
private const ulong MemorySizeAddress = StackPointer + 0x10;
private const ulong StaleThirdArgument = MemoryBase + 0x800;
[Fact]
public void ContextQueryMemory_WritesOnlyOneSizeTAndIgnoresRdx()
{
var memory = new FakeCpuMemory(MemoryBase, 0x1000);
var ctx = new CpuContext(memory, Generation.Gen5);
Span<byte> param = stackalloc byte[0x40];
param.Clear();
BinaryPrimitives.WriteUInt32LittleEndian(param[0x0C..], 4);
Assert.True(memory.TryWrite(ParamAddress, param));
Span<byte> guard = stackalloc byte[0x10];
guard.Fill(0xA5);
Assert.True(memory.TryWrite(MemorySizeAddress, guard));
Assert.True(memory.TryWrite(StaleThirdArgument, guard));
ctx[CpuRegister.Rdi] = ParamAddress;
ctx[CpuRegister.Rsi] = MemorySizeAddress;
ctx[CpuRegister.Rdx] = StaleThirdArgument;
ctx[CpuRegister.Rsp] = StackPointer;
ctx[CpuRegister.Rbp] = StackPointer + 0x48;
Assert.Equal(0, AudioOut2Exports.AudioOut2ContextQueryMemory(ctx));
Span<byte> output = stackalloc byte[0x10];
Assert.True(memory.TryRead(MemorySizeAddress, output));
Assert.Equal(0x11640UL, BinaryPrimitives.ReadUInt64LittleEndian(output));
Assert.Equal(new byte[] { 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5 }, output[8..].ToArray());
Span<byte> stale = stackalloc byte[0x10];
Assert.True(memory.TryRead(StaleThirdArgument, stale));
Assert.Equal(guard.ToArray(), stale.ToArray());
}
[Fact]
public void ContextQueryMemory_RejectsMissingSecondArgument()
{
var memory = new FakeCpuMemory(MemoryBase, 0x1000);
var ctx = new CpuContext(memory, Generation.Gen5);
ctx[CpuRegister.Rdi] = ParamAddress;
ctx[CpuRegister.Rsi] = 0;
ctx[CpuRegister.Rdx] = StaleThirdArgument;
Assert.Equal(
InvalidPointer,
AudioOut2Exports.AudioOut2ContextQueryMemory(ctx));
}
}

View File

@ -0,0 +1,40 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Audio;
using Xunit;
namespace SharpEmu.Libs.Tests.Audio;
public sealed class AudioOut2MasteringExportsTests
{
[Fact]
public void MasteringSetParam_AcceptsOpaqueParameters()
{
var memory = new FakeCpuMemory(0x1_0000_0000, 0x1000);
var ctx = new CpuContext(memory, Generation.Gen5);
ctx[CpuRegister.Rdi] = 0x1_0000_0100;
ctx[CpuRegister.Rsi] = 2;
ctx[CpuRegister.Rdx] = 1;
ctx[CpuRegister.Rax] = ulong.MaxValue;
var result = AudioOut2Exports.AudioOut2MasteringSetParam(ctx);
Assert.Equal(0, result);
Assert.Equal(0UL, ctx[CpuRegister.Rax]);
}
[Fact]
public void MasteringSetParam_RegistersForGen5()
{
var manager = new ModuleManager();
manager.RegisterExports(
SharpEmu.Generated.SysAbiExportRegistry.CreateExports(Generation.Gen5));
Assert.True(manager.TryGetExport("v8iOE+j8a5o", out var export));
Assert.Equal("sceAudioOut2MasteringSetParam", export.Name);
Assert.Equal("libSceAudioOut2", export.LibraryName);
Assert.Equal(Generation.Gen5, export.Target);
}
}

View File

@ -0,0 +1,255 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using SharpEmu.HLE;
using SharpEmu.HLE.Host;
using SharpEmu.Libs.Audio;
using Xunit;
namespace SharpEmu.Libs.Tests.Audio;
[Collection(AudioOutStateCollection.Name)]
public sealed class AudioOut2PcmSnapshotTests : IDisposable
{
private const ulong MemoryBase = 0x1_0000_0000;
private const ulong ContextParamAddress = MemoryBase + 0x100;
private const ulong ContextOutAddress = MemoryBase + 0x200;
private const ulong PortParamAddress = MemoryBase + 0x300;
private const ulong FirstPortOutAddress = MemoryBase + 0x400;
private const ulong SecondPortOutAddress = MemoryBase + 0x408;
private const ulong AttributeAddress = MemoryBase + 0x500;
private const ulong PcmAttributeValueAddress = MemoryBase + 0x600;
private const ulong ContextMemoryAddress = MemoryBase + 0x1000;
private const ulong SharedPcmAddress = MemoryBase + 0x4000;
private readonly FakeCpuMemory _memory = new(MemoryBase, 0x10000);
private readonly CpuContext _ctx;
public AudioOut2PcmSnapshotTests()
{
AudioOut2Exports.ResetForTests();
_ctx = new CpuContext(_memory, Generation.Gen5);
}
[Fact]
public void PortSetAttributes_SnapshotsPcmWhenPortsReuseGuestBuffer()
{
var context = CreateContext(grainSamples: 64);
var firstPort = CreateSigned16StereoPort(context, FirstPortOutAddress);
var secondPort = CreateSigned16StereoPort(context, SecondPortOutAddress);
WritePcmAttribute();
var firstPcm = Enumerable.Range(0, 64 * 2 * sizeof(short))
.Select(static value => unchecked((byte)(value + 1)))
.ToArray();
var secondPcm = Enumerable.Range(0, firstPcm.Length)
.Select(static value => unchecked((byte)(255 - value)))
.ToArray();
Assert.True(_memory.TryWrite(SharedPcmAddress, firstPcm));
SetPcmAttribute(firstPort);
Assert.True(_memory.TryWrite(SharedPcmAddress, secondPcm));
SetPcmAttribute(secondPort);
Assert.True(_memory.TryWrite(SharedPcmAddress, new byte[firstPcm.Length]));
Assert.Equal(firstPcm, AudioOut2Exports.GetPortPcmSnapshotForTests(firstPort));
Assert.Equal(secondPcm, AudioOut2Exports.GetPortPcmSnapshotForTests(secondPort));
Assert.Equal(firstPcm, AudioOut2Exports.TakePortPcmSnapshotForTests(firstPort));
Assert.Null(AudioOut2Exports.GetPortPcmSnapshotForTests(firstPort));
Assert.Null(AudioOut2Exports.TakePortPcmSnapshotForTests(firstPort));
Assert.Equal(secondPcm, AudioOut2Exports.TakePortPcmSnapshotForTests(secondPort));
Assert.Null(AudioOut2Exports.TakePortPcmSnapshotForTests(secondPort));
}
[Theory]
[InlineData((ushort)0x0100)]
[InlineData((ushort)6)]
public void ContextPush_DoesNotSubmitObjectOrVibrationPorts(ushort portType)
{
var stream = new RecordingAudioStream();
var openCalls = 0;
AudioOut2Exports.SetStreamFactoryForTests(_ =>
{
openCalls++;
return stream;
});
var context = CreateContext(grainSamples: 64);
var port = CreateSigned16StereoPort(context, FirstPortOutAddress, portType);
WritePcmAttribute();
var pcm = Enumerable.Range(0, 64 * 2 * sizeof(short))
.Select(static value => unchecked((byte)(value + 1)))
.ToArray();
Assert.True(_memory.TryWrite(SharedPcmAddress, pcm));
SetPcmAttribute(port);
_ctx[CpuRegister.Rdi] = context;
_ctx[CpuRegister.Rsi] = 1;
Assert.Equal(0, AudioOut2Exports.AudioOut2ContextPush(_ctx));
Assert.Equal(0, openCalls);
Assert.Empty(stream.Submissions);
Assert.Null(AudioOut2Exports.GetPortPcmSnapshotForTests(port));
}
[Fact]
public void ContextPush_SubmitsDevicePortSnapshotOnlyOnce()
{
var stream = new RecordingAudioStream();
AudioOut2Exports.SetStreamFactoryForTests(_ => stream);
var context = CreateContext(grainSamples: 64);
var port = CreateSigned16StereoPort(context, FirstPortOutAddress);
WritePcmAttribute();
Assert.True(_memory.TryWrite(
SharedPcmAddress,
Enumerable.Range(0, 64 * 2 * sizeof(short))
.Select(static value => unchecked((byte)(value + 1)))
.ToArray()));
SetPcmAttribute(port);
_ctx[CpuRegister.Rdi] = context;
_ctx[CpuRegister.Rsi] = 1;
Assert.Equal(0, AudioOut2Exports.AudioOut2ContextPush(_ctx));
Assert.Single(stream.Submissions);
Assert.Equal(64 * 2 * sizeof(short), stream.Submissions[0].Length);
Assert.Equal(0, AudioOut2Exports.AudioOut2ContextPush(_ctx));
Assert.Single(stream.Submissions);
}
[Fact]
public void ContextAdvance_SubmitsDevicePortSnapshotOnlyOnce()
{
var stream = new RecordingAudioStream();
AudioOut2Exports.SetStreamFactoryForTests(_ => stream);
var context = CreateContext(grainSamples: 64);
var port = CreateSigned16StereoPort(context, FirstPortOutAddress);
WritePcmAttribute();
Assert.True(_memory.TryWrite(
SharedPcmAddress,
Enumerable.Range(0, 64 * 2 * sizeof(short))
.Select(static value => unchecked((byte)(value + 1)))
.ToArray()));
SetPcmAttribute(port);
_ctx[CpuRegister.Rdi] = context;
Assert.Equal(0, AudioOut2Exports.AudioOut2ContextAdvance(_ctx));
Assert.Single(stream.Submissions);
Assert.Equal(64 * 2 * sizeof(short), stream.Submissions[0].Length);
Assert.Equal(0, AudioOut2Exports.AudioOut2ContextAdvance(_ctx));
Assert.Single(stream.Submissions);
}
[Fact]
public void ContextDestroy_RemovesOwnedPortsAndPendingSnapshots()
{
var firstContext = CreateContext(grainSamples: 64);
var secondContext = CreateContext(grainSamples: 64);
var firstPort = CreateSigned16StereoPort(firstContext, FirstPortOutAddress);
var secondPort = CreateSigned16StereoPort(secondContext, SecondPortOutAddress);
WritePcmAttribute();
byte[] firstPcm = new byte[64 * 2 * sizeof(short)];
byte[] secondPcm = Enumerable.Repeat((byte)0x5A, firstPcm.Length).ToArray();
Assert.True(_memory.TryWrite(SharedPcmAddress, firstPcm));
SetPcmAttribute(firstPort);
Assert.True(_memory.TryWrite(SharedPcmAddress, secondPcm));
SetPcmAttribute(secondPort);
_ctx[CpuRegister.Rdi] = firstContext;
Assert.Equal(0, AudioOut2Exports.AudioOut2ContextDestroy(_ctx));
Assert.False(AudioOut2Exports.HasPortForTests(firstPort));
Assert.Null(AudioOut2Exports.GetPortPcmSnapshotForTests(firstPort));
Assert.Null(AudioOut2Exports.TakePortPcmSnapshotForTests(firstPort));
Assert.True(AudioOut2Exports.HasPortForTests(secondPort));
Assert.Equal(secondPcm, AudioOut2Exports.GetPortPcmSnapshotForTests(secondPort));
}
public void Dispose() => AudioOut2Exports.ResetForTests();
private ulong CreateContext(uint grainSamples)
{
Span<byte> param = stackalloc byte[0x40];
param.Clear();
BinaryPrimitives.WriteUInt32LittleEndian(param[0x0C..], 2);
BinaryPrimitives.WriteUInt32LittleEndian(param[0x10..], grainSamples);
Assert.True(_memory.TryWrite(ContextParamAddress, param));
_ctx[CpuRegister.Rdi] = ContextParamAddress;
_ctx[CpuRegister.Rsi] = ContextMemoryAddress;
_ctx[CpuRegister.Rdx] = 0x2000;
_ctx[CpuRegister.Rcx] = ContextOutAddress;
Assert.Equal(0, AudioOut2Exports.AudioOut2ContextCreate(_ctx));
return ReadUInt64(ContextOutAddress);
}
private ulong CreateSigned16StereoPort(ulong context, ulong outAddress, ushort portType = 0)
{
Span<byte> param = stackalloc byte[0x40];
param.Clear();
BinaryPrimitives.WriteUInt16LittleEndian(param, portType);
BinaryPrimitives.WriteUInt32LittleEndian(param[0x04..], 0x201);
BinaryPrimitives.WriteUInt32LittleEndian(param[0x08..], 48000);
Assert.True(_memory.TryWrite(PortParamAddress, param));
_ctx[CpuRegister.Rdi] = context;
_ctx[CpuRegister.Rsi] = PortParamAddress;
_ctx[CpuRegister.Rdx] = outAddress;
_ctx[CpuRegister.Rcx] = 0;
Assert.Equal(0, AudioOut2Exports.AudioOut2PortCreate(_ctx));
return ReadUInt64(outAddress);
}
private void WritePcmAttribute()
{
Span<byte> pcm = stackalloc byte[8];
BinaryPrimitives.WriteUInt64LittleEndian(pcm, SharedPcmAddress);
Assert.True(_memory.TryWrite(PcmAttributeValueAddress, pcm));
Span<byte> attribute = stackalloc byte[0x18];
attribute.Clear();
BinaryPrimitives.WriteUInt32LittleEndian(attribute, 0);
BinaryPrimitives.WriteUInt64LittleEndian(attribute[0x08..], PcmAttributeValueAddress);
BinaryPrimitives.WriteUInt64LittleEndian(attribute[0x10..], 8);
Assert.True(_memory.TryWrite(AttributeAddress, attribute));
}
private void SetPcmAttribute(ulong port)
{
_ctx[CpuRegister.Rdi] = port;
_ctx[CpuRegister.Rsi] = AttributeAddress;
_ctx[CpuRegister.Rdx] = 1;
Assert.Equal(0, AudioOut2Exports.AudioOut2PortSetAttributes(_ctx));
}
private ulong ReadUInt64(ulong address)
{
Span<byte> value = stackalloc byte[8];
Assert.True(_memory.TryRead(address, value));
return BinaryPrimitives.ReadUInt64LittleEndian(value);
}
private sealed class RecordingAudioStream : IHostAudioStream
{
public List<byte[]> Submissions { get; } = [];
public bool Submit(ReadOnlySpan<byte> stereoPcm16)
{
Submissions.Add(stereoPcm16.ToArray());
return true;
}
public void Dispose()
{
}
}
}

View File

@ -42,9 +42,12 @@ public sealed class AudioOut2PortGetStateExportsTests
// SceAudioOut2PortState: output@0, numChannels@2, volume@4.
Assert.Equal(1, BinaryPrimitives.ReadUInt16LittleEndian(state));
Assert.Equal(2, state[2]);
Assert.Equal(-1, BinaryPrimitives.ReadInt16LittleEndian(state[4..]));
Assert.Equal(0, state[3]);
Assert.Equal(127, BinaryPrimitives.ReadInt16LittleEndian(state[4..]));
Assert.Equal(0, BinaryPrimitives.ReadUInt16LittleEndian(state[6..]));
Assert.Equal(0u, BinaryPrimitives.ReadUInt32LittleEndian(state[8..]));
// sizeof(SceAudioOut2PortState) is 0x40; nothing past it may be touched.
Assert.Equal(0u, BinaryPrimitives.ReadUInt32LittleEndian(state[0x0C..]));
Assert.All(state[0x10..0x40].ToArray(), value => Assert.Equal(0, value));
Assert.Equal(0xAB, state[0x40]);
Assert.Equal(0xAB, state[0x7F]);
}
@ -102,15 +105,19 @@ public sealed class AudioOut2PortGetStateExportsTests
Assert.Equal(0, result);
Span<byte> info = stackalloc byte[0x80];
Assert.True(memory.TryRead(StateAddress, info));
// type@0 = SPEAKER_TYPE_TV, availableBits@4 = FRONT_LEFT | FRONT_RIGHT.
Assert.Equal(0, info[0]);
Assert.Equal(3u, BinaryPrimitives.ReadUInt32LittleEndian(info[4..]));
Assert.Equal(0, info[1]);
Assert.Equal(0, BinaryPrimitives.ReadInt16LittleEndian(info[2..]));
Assert.Equal(0x3u, BinaryPrimitives.ReadUInt32LittleEndian(info[4..]));
Assert.Equal(0u, BinaryPrimitives.ReadUInt32LittleEndian(info[8..]));
// aSpeakerAngle[0..1] = { -30, 0 }, { +30, 0 } degrees.
Assert.Equal(0u, BinaryPrimitives.ReadUInt32LittleEndian(info[0x0C..]));
Assert.Equal(-30, BinaryPrimitives.ReadInt16LittleEndian(info[0x10..]));
Assert.Equal(0, BinaryPrimitives.ReadInt16LittleEndian(info[0x12..]));
Assert.Equal(30, BinaryPrimitives.ReadInt16LittleEndian(info[0x14..]));
// sizeof(SceAudioOut2SpeakerInfo) is 0x50.
Assert.Equal(0, BinaryPrimitives.ReadInt16LittleEndian(info[0x16..]));
Assert.All(info[0x18..0x50].ToArray(), value => Assert.Equal(0, value));
Assert.Equal(0xCD, info[0x50]);
Assert.Equal(0xCD, info[0x7F]);
}
[Fact]

View File

@ -0,0 +1,54 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.AvPlayer;
using SharpEmu.Libs.Tests.Kernel;
using Xunit;
namespace SharpEmu.Libs.Tests.AvPlayer;
[Collection(KernelMemoryCompatStateCollection.Name)]
public sealed class AvPlayerControlTests : IDisposable
{
private const int InvalidParameters = unchecked((int)0x806A0001);
private const ulong Handle = 0xA0_0000_2400;
private readonly CpuContext _context = new(
new FakeCpuMemory(0x1_0000_0000, 0x1000),
Generation.Gen5);
public AvPlayerControlTests()
{
AvPlayerExports.RegisterPlayerForTest(
Handle,
width: 16,
height: 16,
durationMilliseconds: 1000);
_context[CpuRegister.Rdi] = Handle;
}
[Fact]
public void AvailableBandwidthAcceptsAValidPlayerWithoutAStreamingSource()
{
_context[CpuRegister.Rsi] = 0;
Assert.Equal(0, AvPlayerExports.AvPlayerSetAvailableBandwidth(_context));
Assert.Equal(0UL, _context[CpuRegister.Rax]);
}
[Fact]
public void AvailableBandwidthRejectsAnUnknownPlayer()
{
_context[CpuRegister.Rdi] = Handle + 1;
_context[CpuRegister.Rsi] = 25_000_000;
Assert.Equal(
InvalidParameters,
AvPlayerExports.AvPlayerSetAvailableBandwidth(_context));
Assert.Equal(
unchecked((ulong)InvalidParameters),
_context[CpuRegister.Rax]);
}
public void Dispose() => AvPlayerExports.RemovePlayerForTest(Handle);
}

View File

@ -0,0 +1,65 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using SharpEmu.HLE;
using SharpEmu.Libs.CommonDialog;
using Xunit;
namespace SharpEmu.Libs.Tests.CommonDialog;
[CollectionDefinition("WebBrowserDialog", DisableParallelization = true)]
public sealed class WebBrowserDialogCollection;
[Collection("WebBrowserDialog")]
public sealed class WebBrowserDialogExportsTests : IDisposable
{
private const ulong MemoryBase = 0x1_0000_0000;
private readonly FakeCpuMemory _memory = new(MemoryBase, 0x1000);
private readonly CpuContext _context;
public WebBrowserDialogExportsTests()
{
WebBrowserDialogExports.ResetForTests();
_context = new CpuContext(_memory, Generation.Gen5);
}
public void Dispose() => WebBrowserDialogExports.ResetForTests();
[Fact]
public void HeadlessLifecycle_CompletesOnFirstStatusPoll()
{
Assert.Equal(WebBrowserDialogExports.StatusNone,
WebBrowserDialogExports.WebBrowserDialogGetStatus(_context));
Assert.Equal(0, WebBrowserDialogExports.WebBrowserDialogInitialize(_context));
Assert.Equal(WebBrowserDialogExports.StatusInitialized,
WebBrowserDialogExports.WebBrowserDialogGetStatus(_context));
_context[CpuRegister.Rdi] = MemoryBase + 0x100;
Assert.Equal(0, WebBrowserDialogExports.WebBrowserDialogOpen(_context));
Assert.Equal(WebBrowserDialogExports.StatusRunning, WebBrowserDialogExports.StatusForTests);
Assert.Equal(WebBrowserDialogExports.StatusFinished,
WebBrowserDialogExports.WebBrowserDialogGetStatus(_context));
var resultAddress = MemoryBase + 0x200;
_context[CpuRegister.Rdi] = resultAddress;
Assert.Equal(0, WebBrowserDialogExports.WebBrowserDialogGetResult(_context));
Span<byte> result = stackalloc byte[sizeof(int)];
Assert.True(_memory.TryRead(resultAddress, result));
Assert.Equal(0, BinaryPrimitives.ReadInt32LittleEndian(result));
Assert.Equal(0, WebBrowserDialogExports.WebBrowserDialogTerminate(_context));
Assert.Equal(WebBrowserDialogExports.StatusNone,
WebBrowserDialogExports.WebBrowserDialogGetStatus(_context));
}
[Fact]
public void Close_FinishesRunningDialogWithoutPolling()
{
Assert.Equal(0, WebBrowserDialogExports.WebBrowserDialogInitialize(_context));
_context[CpuRegister.Rdi] = MemoryBase + 0x100;
Assert.Equal(0, WebBrowserDialogExports.WebBrowserDialogOpenForPredeterminedContent(_context));
Assert.Equal(0, WebBrowserDialogExports.WebBrowserDialogClose(_context));
Assert.Equal(WebBrowserDialogExports.StatusFinished, WebBrowserDialogExports.StatusForTests);
}
}

View File

@ -26,6 +26,8 @@ public sealed class JsonExportRegistrationTests
("9KUZFjI1IxA", "_ZN3sce4Json6StringC1EPKc"),
("cG1VE2HMl6c", "_ZN3sce4Json6StringD1Ev"),
("+drDFyAS6u4", "_ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_"),
("GvGvswb0v34", "_ZN3sce4Json14InitParameter2C2Ev"),
("W72B9ylU2JA", "_ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv"),
};
private static ModuleManager CreateRegisteredManager()
@ -131,4 +133,36 @@ public sealed class JsonExportRegistrationTests
Assert.Equal(0x1_0000_0000UL, ctx[CpuRegister.Rax]);
Assert.Equal(JsonValueKind.Null, JsonObjectHeap.Values[0x1_0000_0000].Kind);
}
[Fact]
public void InitParameter2Aliases_InitializeAndWriteTheirDocumentedFields()
{
const ulong memoryBase = 0x1_0000_0000;
const ulong parameterAddress = memoryBase + 0x100;
const ulong allocatorAddress = 0x8_0012_3000;
const ulong userDataAddress = memoryBase + 0x500;
var memory = new FakeCpuMemory(memoryBase, 0x1000);
var manager = CreateRegisteredManager();
var ctx = new CpuContext(memory, Generation.Gen5);
Assert.True(memory.TryWrite(parameterAddress, Enumerable.Repeat((byte)0xA5, 0x28).ToArray()));
ctx[CpuRegister.Rdi] = parameterAddress;
Assert.True(manager.TryDispatch("GvGvswb0v34", ctx, out var constructorResult));
Assert.Equal(OrbisGen2Result.ORBIS_GEN2_OK, constructorResult);
var parameter = new byte[0x28];
Assert.True(memory.TryRead(parameterAddress, parameter));
Assert.All(parameter, value => Assert.Equal(0, value));
ctx[CpuRegister.Rdi] = parameterAddress;
ctx[CpuRegister.Rsi] = allocatorAddress;
ctx[CpuRegister.Rdx] = userDataAddress;
Assert.True(manager.TryDispatch("W72B9ylU2JA", ctx, out var allocatorResult));
Assert.Equal(OrbisGen2Result.ORBIS_GEN2_OK, allocatorResult);
Assert.True(memory.TryRead(parameterAddress, parameter));
Assert.Equal(allocatorAddress, BitConverter.ToUInt64(parameter, 0));
Assert.Equal(userDataAddress, BitConverter.ToUInt64(parameter, 8));
Assert.All(parameter[0x10..], value => Assert.Equal(0, value));
}
}

View File

@ -72,4 +72,276 @@ public sealed class Http2ExportsTests
Assert.Equal(unchecked((int)0x817B106A), Http2Exports.Http2CreateTemplate(ctx));
}
[Fact]
public void SetMinSslVersion_AcceptsLiveIdsAndRejectsDeletedIds()
{
Http2Exports.ResetRuntimeState();
var memory = new FakeCpuMemory(MemoryBase, 0x2000);
var ctx = new CpuContext(memory, Generation.Gen5);
ctx[CpuRegister.Rdi] = 1;
ctx[CpuRegister.Rsi] = 2;
ctx[CpuRegister.Rdx] = 0x1000;
ctx[CpuRegister.Rcx] = 2;
Assert.Equal(0, Http2Exports.Http2Init(ctx));
var contextId = ctx[CpuRegister.Rax];
ctx[CpuRegister.Rdi] = contextId;
ctx[CpuRegister.Rsi] = memory.WriteCString(MemoryBase + 0x100, "test");
ctx[CpuRegister.Rdx] = 3;
ctx[CpuRegister.Rcx] = 0;
Assert.True(Http2Exports.Http2CreateTemplate(ctx) > 0);
var templateId = ctx[CpuRegister.Rax];
ctx[CpuRegister.Rdi] = templateId;
ctx[CpuRegister.Rsi] = 4;
Assert.Equal(0, Http2Exports.Http2SetMinSslVersion(ctx));
ctx[CpuRegister.Rdi] = templateId;
ctx[CpuRegister.Rsi] = memory.WriteCString(MemoryBase + 0x200, "GET");
ctx[CpuRegister.Rdx] = memory.WriteCString(MemoryBase + 0x300, "https://example.invalid/");
ctx[CpuRegister.Rcx] = 0;
Assert.True(Http2Exports.Http2CreateRequestWithUrl(ctx) > 0);
var requestId = ctx[CpuRegister.Rax];
ctx[CpuRegister.Rdi] = requestId;
ctx[CpuRegister.Rsi] = 4;
Assert.Equal(0, Http2Exports.Http2SetMinSslVersion(ctx));
ctx[CpuRegister.Rdi] = templateId;
Assert.Equal(0, Http2Exports.Http2DeleteTemplate(ctx));
Assert.Equal(unchecked((int)0x817B1100), Http2Exports.Http2SetMinSslVersion(ctx));
ctx[CpuRegister.Rdi] = requestId;
Assert.Equal(unchecked((int)0x817B1100), Http2Exports.Http2SetMinSslVersion(ctx));
}
[Fact]
public void SetMinSslVersion_RegistersPs5Nid()
{
var manager = new ModuleManager();
manager.RegisterExports(SharpEmu.Generated.SysAbiExportRegistry.CreateExports(Generation.Gen5));
Assert.True(manager.TryGetExport("09tk+kIA1Ns", out var export));
Assert.Equal("sceHttp2SetMinSslVersion", export.Name);
Assert.Equal("libSceHttp2", export.LibraryName);
}
[Fact]
public void Options_AreStoredInheritedAndIndependentlyUpdated()
{
var (memory, ctx, templateId) = CreateTemplate();
ctx[CpuRegister.Rdi] = unchecked((uint)templateId);
ctx[CpuRegister.Rsi] = 0x0F;
Assert.Equal(0, Http2Exports.Http2SslEnableOption(ctx));
ctx[CpuRegister.Rsi] = 0x05;
Assert.Equal(0, Http2Exports.Http2SslDisableOption(ctx));
ctx[CpuRegister.Rsi] = 0x1234_5678;
ctx[CpuRegister.Rdx] = 0x8765_4321;
Assert.Equal(0, Http2Exports.Http2SetRedirectCallback(ctx));
ctx[CpuRegister.Rsi] = 0;
Assert.Equal(0, Http2Exports.Http2SetAutoRedirect(ctx));
ctx[CpuRegister.Rsi] = 101;
Assert.Equal(0, Http2Exports.Http2SetConnectTimeOut(ctx));
ctx[CpuRegister.Rsi] = 202;
Assert.Equal(0, Http2Exports.Http2SetRecvTimeOut(ctx));
ctx[CpuRegister.Rsi] = 303;
Assert.Equal(0, Http2Exports.Http2SetSendTimeOut(ctx));
ctx[CpuRegister.Rsi] = 7;
Assert.Equal(0, Http2Exports.Http2SetAuthEnabled(ctx));
ctx[CpuRegister.Rsi] = 0x1111_2222;
ctx[CpuRegister.Rdx] = 0x3333_4444;
Assert.Equal(0, Http2Exports.Http2SetSslCallback(ctx));
Assert.True(Http2Exports.TryGetOptionsForTests(templateId, out var templateOptions));
Assert.Equal(0x0Au, templateOptions.SslOptions);
Assert.Equal(0x1234_5678UL, templateOptions.RedirectCallback);
Assert.Equal(0x8765_4321UL, templateOptions.RedirectUserArgument);
Assert.False(templateOptions.AutoRedirect);
Assert.Equal(101u, templateOptions.ConnectTimeoutMicroseconds);
Assert.Equal(202u, templateOptions.ReceiveTimeoutMicroseconds);
Assert.Equal(303u, templateOptions.SendTimeoutMicroseconds);
Assert.True(templateOptions.AuthEnabled);
Assert.Equal(0x1111_2222UL, templateOptions.SslCallback);
Assert.Equal(0x3333_4444UL, templateOptions.SslUserArgument);
var requestId = CreateRequest(memory, ctx, templateId);
Assert.True(Http2Exports.TryGetOptionsForTests(requestId, out var inheritedOptions));
Assert.Equal(templateOptions, inheritedOptions);
ctx[CpuRegister.Rdi] = unchecked((uint)requestId);
ctx[CpuRegister.Rsi] = 404;
Assert.Equal(0, Http2Exports.Http2SetRecvTimeOut(ctx));
Assert.True(Http2Exports.TryGetOptionsForTests(requestId, out var requestOptions));
Assert.Equal(404u, requestOptions.ReceiveTimeoutMicroseconds);
Assert.True(Http2Exports.TryGetOptionsForTests(templateId, out var unchangedTemplateOptions));
Assert.Equal(202u, unchangedTemplateOptions.ReceiveTimeoutMicroseconds);
}
[Fact]
public void OptionSetters_RejectUnknownHandles()
{
Http2Exports.ResetRuntimeState();
var ctx = new CpuContext(new FakeCpuMemory(MemoryBase, 0x1000), Generation.Gen5);
ctx[CpuRegister.Rdi] = 0x7FFF_FFFF;
ctx[CpuRegister.Rsi] = 1;
ctx[CpuRegister.Rdx] = 2;
Func<CpuContext, int>[] setters =
[
Http2Exports.Http2SslDisableOption,
Http2Exports.Http2SslEnableOption,
Http2Exports.Http2SetRedirectCallback,
Http2Exports.Http2SetAutoRedirect,
Http2Exports.Http2SetCookieBox,
Http2Exports.Http2SetConnectTimeOut,
Http2Exports.Http2SetRecvTimeOut,
Http2Exports.Http2SetSendTimeOut,
Http2Exports.Http2SetAuthEnabled,
Http2Exports.Http2SetSslCallback,
];
foreach (var setter in setters)
{
Assert.Equal(unchecked((int)0x817B1100), setter(ctx));
}
}
[Fact]
public void AddRequestHeader_ValidatesPointersAndTracksGuestStrings()
{
var (memory, ctx, templateId) = CreateTemplate();
var requestId = CreateRequest(memory, ctx, templateId);
var nameAddress = memory.WriteCString(MemoryBase + 0x500, "X-SharpEmu-Test");
var valueAddress = memory.WriteCString(MemoryBase + 0x600, "enabled");
ctx[CpuRegister.Rdi] = unchecked((uint)requestId);
ctx[CpuRegister.Rsi] = nameAddress;
ctx[CpuRegister.Rdx] = valueAddress;
ctx[CpuRegister.Rcx] = 1;
Assert.Equal(0, Http2Exports.Http2AddRequestHeader(ctx));
var header = Assert.Single(Http2Exports.GetRequestHeadersForTests(requestId));
Assert.Equal("X-SharpEmu-Test", header.Name);
Assert.Equal("enabled", header.Value);
Assert.Equal(1u, header.Mode);
ctx[CpuRegister.Rsi] = 0;
Assert.Equal(unchecked((int)0x817B1225), Http2Exports.Http2AddRequestHeader(ctx));
ctx[CpuRegister.Rsi] = MemoryBase + 0x3000;
Assert.Equal(unchecked((int)0x817B11FE), Http2Exports.Http2AddRequestHeader(ctx));
ctx[CpuRegister.Rdi] = 0x7FFF_FFFF;
ctx[CpuRegister.Rsi] = nameAddress;
Assert.Equal(unchecked((int)0x817B1100), Http2Exports.Http2AddRequestHeader(ctx));
}
[Fact]
public void CookieBox_IsCreatedBoundInheritedAndCanBeDetached()
{
var (memory, ctx, templateId) = CreateTemplate();
ctx[CpuRegister.Rdi] = 0x80000; // Deliberately stale sceHttp2Init register state.
Assert.Equal(0x3001, Http2Exports.Http2CreateCookieBox(ctx));
var cookieBoxId = unchecked((int)ctx[CpuRegister.Rax]);
ctx[CpuRegister.Rdi] = unchecked((uint)templateId);
ctx[CpuRegister.Rsi] = unchecked((uint)cookieBoxId);
Assert.Equal(0, Http2Exports.Http2SetCookieBox(ctx));
Assert.True(Http2Exports.TryGetOptionsForTests(templateId, out var templateOptions));
Assert.Equal(cookieBoxId, templateOptions.CookieBoxId);
var requestId = CreateRequest(memory, ctx, templateId);
Assert.True(Http2Exports.TryGetOptionsForTests(requestId, out var inheritedOptions));
Assert.Equal(cookieBoxId, inheritedOptions.CookieBoxId);
ctx[CpuRegister.Rdi] = unchecked((uint)requestId);
ctx[CpuRegister.Rsi] = 0;
Assert.Equal(0, Http2Exports.Http2SetCookieBox(ctx));
Assert.True(Http2Exports.TryGetOptionsForTests(requestId, out var detachedOptions));
Assert.Equal(0, detachedOptions.CookieBoxId);
ctx[CpuRegister.Rsi] = 0x7FFF_FFFF;
Assert.Equal(unchecked((int)0x817B1100), Http2Exports.Http2SetCookieBox(ctx));
}
[Fact]
public void SetRequestContentLength_UpdatesOnlyLiveRequests()
{
var (memory, ctx, templateId) = CreateTemplate();
var requestId = CreateRequest(memory, ctx, templateId);
ctx[CpuRegister.Rdi] = unchecked((uint)requestId);
ctx[CpuRegister.Rsi] = 0x1_0000_0042;
Assert.Equal(0, Http2Exports.Http2SetRequestContentLength(ctx));
Assert.True(Http2Exports.TryGetRequestContentLengthForTests(requestId, out var contentLength));
Assert.Equal(0x1_0000_0042UL, contentLength);
ctx[CpuRegister.Rdi] = 0x7FFF_FFFF;
Assert.Equal(unchecked((int)0x817B1100), Http2Exports.Http2SetRequestContentLength(ctx));
}
[Theory]
[InlineData("B37SruheQ5Y", "sceHttp2SslDisableOption")]
[InlineData("EWcwMpbr5F8", "sceHttp2SslEnableOption")]
[InlineData("BJgi0CH7al4", "sceHttp2SetRedirectCallback")]
[InlineData("b9AvoIaOuHI", "sceHttp2SetAutoRedirect")]
[InlineData("N4UfjvWJsMw", "sceHttp2CreateCookieBox")]
[InlineData("jrVHsKCXA0g", "sceHttp2SetCookieBox")]
[InlineData("FSAFOzi0FpM", "sceHttp2SetRequestContentLength")]
[InlineData("-HIO4VT87v8", "sceHttp2SetConnectTimeOut")]
[InlineData("izvHhqgDt44", "sceHttp2SetRecvTimeOut")]
[InlineData("XPtW45xiLHk", "sceHttp2SetSendTimeOut")]
[InlineData("jjFahkBPCYs", "sceHttp2SetAuthEnabled")]
[InlineData("nrPfOE8TQu0", "sceHttp2AddRequestHeader")]
[InlineData("YrWX+DhPHQY", "sceHttp2SetSslCallback")]
public void CompatibilityExports_RegisterPs5Nids(string nid, string name)
{
var manager = new ModuleManager();
manager.RegisterExports(SharpEmu.Generated.SysAbiExportRegistry.CreateExports(Generation.Gen5));
Assert.True(manager.TryGetExport(nid, out var export));
Assert.Equal(name, export.Name);
Assert.Equal("libSceHttp2", export.LibraryName);
}
private static (FakeCpuMemory Memory, CpuContext Context, int TemplateId) CreateTemplate()
{
Http2Exports.ResetRuntimeState();
var memory = new FakeCpuMemory(MemoryBase, 0x2000);
var ctx = new CpuContext(memory, Generation.Gen5);
ctx[CpuRegister.Rdi] = 1;
ctx[CpuRegister.Rsi] = 2;
ctx[CpuRegister.Rdx] = 0x1000;
ctx[CpuRegister.Rcx] = 2;
Assert.Equal(0, Http2Exports.Http2Init(ctx));
var contextId = unchecked((int)ctx[CpuRegister.Rax]);
ctx[CpuRegister.Rdi] = unchecked((uint)contextId);
ctx[CpuRegister.Rsi] = memory.WriteCString(MemoryBase + 0x100, "test");
ctx[CpuRegister.Rdx] = 3;
ctx[CpuRegister.Rcx] = 0;
Assert.True(Http2Exports.Http2CreateTemplate(ctx) > 0);
return (memory, ctx, unchecked((int)ctx[CpuRegister.Rax]));
}
private static int CreateRequest(FakeCpuMemory memory, CpuContext ctx, int templateId)
{
ctx[CpuRegister.Rdi] = unchecked((uint)templateId);
ctx[CpuRegister.Rsi] = memory.WriteCString(MemoryBase + 0x200, "GET");
ctx[CpuRegister.Rdx] = memory.WriteCString(MemoryBase + 0x300, "https://example.invalid/");
ctx[CpuRegister.Rcx] = 0;
Assert.True(Http2Exports.Http2CreateRequestWithUrl(ctx) > 0);
return unchecked((int)ctx[CpuRegister.Rax]);
}
}

View File

@ -44,11 +44,18 @@ public sealed class SaveDataExportsTests : IDisposable
private const ulong SearchResult = Base + 0x1140;
private const ulong SearchDirNames = Base + 0x1180;
private const ulong SearchInfos = Base + 0x1200;
private const ulong BackupParam = Base + 0x1300;
private const ulong BackupTitleId = Base + 0x1380;
private const ulong BackupDirName = Base + 0x13C0;
private const ulong Unmapped = Base + 0x2_0000;
private const int NoEvent = unchecked((int)0x809F0008);
private const int ParameterError = unchecked((int)0x809F0000);
private const int MemoryFault = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
private const int ResourceBusy = unchecked((int)0x809F001B);
private const uint MountModeCreate = 1u << 2;
private const string BackupTitle = "PPSA10067";
private const string BackupDirectory = "SLOT_WITH_32_CHARACTERS_12345678";
private readonly FakeCpuMemory _memory = new(Base, 0x10000);
private readonly CpuContext _ctx;
@ -121,6 +128,65 @@ public sealed class SaveDataExportsTests : IDisposable
Assert.Equal(ParameterError, SaveDataExports.SaveDataGetEventResult(Reg(rsi: 0)));
}
[Fact]
public void Backup_QueuesCompleteBackupEndEvent_ThenDrains()
{
WriteAscii(BackupTitleId, BackupTitle);
WriteAscii(BackupDirName, BackupDirectory);
WriteBackupParam(BackupTitleId, BackupDirName);
Assert.Equal(0, SaveDataExports.SaveDataBackup(Reg(rdi: BackupParam)));
var initialized = new byte[0x70];
Array.Fill(initialized, (byte)0xCC);
Assert.True(_memory.TryWrite(EventOut, initialized));
Assert.Equal(0, SaveDataExports.SaveDataGetEventResult(Reg(rsi: EventOut)));
var ev = new byte[0x70];
Assert.True(_memory.TryRead(EventOut, ev));
Assert.Equal(2u, BinaryPrimitives.ReadUInt32LittleEndian(ev));
Assert.Equal(0, BinaryPrimitives.ReadInt32LittleEndian(ev.AsSpan(0x04)));
Assert.Equal(UserId, BinaryPrimitives.ReadInt32LittleEndian(ev.AsSpan(0x08)));
Assert.Equal(BackupTitle, Encoding.ASCII.GetString(ev, 0x10, 0x10).TrimEnd('\0'));
Assert.Equal(BackupDirectory, Encoding.ASCII.GetString(ev, 0x20, 0x20).TrimEnd('\0'));
Assert.All(ev[0x1A..0x20], value => Assert.Equal((byte)0, value));
Assert.All(ev[0x40..0x68], value => Assert.Equal((byte)0, value));
Assert.All(ev[0x68..0x70], value => Assert.Equal((byte)0xCC, value));
Assert.Equal(NoEvent, SaveDataExports.SaveDataGetEventResult(Reg(rsi: EventOut)));
}
[Fact]
public void Backup_NullParameterOrDirectory_ReturnsParameterWithoutEvent()
{
Assert.Equal(ParameterError, SaveDataExports.SaveDataBackup(Reg(rdi: 0)));
WriteBackupParam(titleIdAddress: 0, dirNameAddress: 0);
Assert.Equal(ParameterError, SaveDataExports.SaveDataBackup(Reg(rdi: BackupParam)));
Assert.Equal(NoEvent, SaveDataExports.SaveDataGetEventResult(Reg(rsi: EventOut)));
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void Backup_InvalidNestedPointer_ReturnsMemoryFaultWithoutEvent(bool invalidTitle)
{
WriteAscii(BackupTitleId, BackupTitle);
WriteAscii(BackupDirName, BackupDirectory);
WriteBackupParam(
invalidTitle ? Unmapped : BackupTitleId,
invalidTitle ? BackupDirName : Unmapped);
Assert.Equal(MemoryFault, SaveDataExports.SaveDataBackup(Reg(rdi: BackupParam)));
Assert.Equal(NoEvent, SaveDataExports.SaveDataGetEventResult(Reg(rsi: EventOut)));
}
[Fact]
public void Backup_InvalidStructurePointer_ReturnsMemoryFaultWithoutEvent()
{
Assert.Equal(MemoryFault, SaveDataExports.SaveDataBackup(Reg(rdi: Unmapped)));
Assert.Equal(NoEvent, SaveDataExports.SaveDataGetEventResult(Reg(rsi: EventOut)));
}
[Fact]
public void SyncSaveDataMemory_PostsSyncEndEvent_DrainedOnce()
{
@ -145,6 +211,11 @@ public sealed class SaveDataExportsTests : IDisposable
Assert.True(_ctx.TryReadInt32(EventOut + 0x04, out var errorCode));
Assert.Equal(0, errorCode);
var ev = new byte[0x68];
Assert.True(_memory.TryRead(EventOut, ev));
Assert.Equal(TitleId[..10], Encoding.ASCII.GetString(ev, 0x10, 0x10).TrimEnd('\0'));
Assert.Equal("sce_sdmemory", Encoding.ASCII.GetString(ev, 0x20, 0x20).TrimEnd('\0'));
Assert.Equal(NoEvent, SaveDataExports.SaveDataGetEventResult(Reg(rsi: EventOut)));
}
@ -397,4 +468,14 @@ public sealed class SaveDataExportsTests : IDisposable
0,
SaveDataExports.SaveDataPrepare(Reg(rdi: MountPointStr, rsi: PrepareParam)));
}
private void WriteBackupParam(ulong titleIdAddress, ulong dirNameAddress)
{
Span<byte> backup = stackalloc byte[0x40];
backup.Clear();
BinaryPrimitives.WriteInt32LittleEndian(backup, UserId);
BinaryPrimitives.WriteUInt64LittleEndian(backup[0x08..], titleIdAddress);
BinaryPrimitives.WriteUInt64LittleEndian(backup[0x10..], dirNameAddress);
Assert.True(_memory.TryWrite(BackupParam, backup));
}
}