mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-31 06:59:45 +08:00
revert: restore state before huge regression
This commit is contained in:
@@ -1,293 +0,0 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Buffers.Binary;
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Libs.Agc;
|
||||
using SharpEmu.ShaderCompiler;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Libs.Tests.Agc;
|
||||
|
||||
/// <summary>
|
||||
/// Coverage for AGC attrib-table → BufferFormat merge and semantic indexing.
|
||||
/// </summary>
|
||||
public sealed class AgcVertexMetadataTests
|
||||
{
|
||||
[Fact]
|
||||
public void BuildVertexResources_UsesSemanticNotHardwareMappingAsAttribIndex()
|
||||
{
|
||||
// input_semantics[0]: semantic=1, hardware_mapping=4, size=2
|
||||
// If hardware_mapping were wrongly used as the attrib index, we'd read
|
||||
// attrib[4] instead of attrib[1] and get the wrong format/offset.
|
||||
const ulong memoryBase = 0x1_0000_0000;
|
||||
var memory = new FakeCpuMemory(memoryBase, 0x2000);
|
||||
var ctx = new CpuContext(memory, Generation.Gen5);
|
||||
|
||||
const ulong semanticsAddress = memoryBase + 0x100;
|
||||
const ulong attribTable = memoryBase + 0x200;
|
||||
const ulong bufferTable = memoryBase + 0x300;
|
||||
const ulong sharpBase = memoryBase + 0x800;
|
||||
|
||||
// ShaderSemantic word: semantic=1, hw_mapping=4, size_in_elements=2
|
||||
WriteUInt32(memory, semanticsAddress, 1u | (4u << 8) | (2u << 16));
|
||||
|
||||
// attrib[0] unused garbage
|
||||
WriteUInt32(memory, attribTable, 0xDEAD_BEEFu);
|
||||
// attrib[1]: buffer=0, format=k16_16Float(29), offset=8, fetch=0
|
||||
WriteUInt32(memory, attribTable + 4, 0u | (29u << 5) | (8u << 14));
|
||||
|
||||
// V# at buffer table[0]: base=sharpBase, stride=16
|
||||
WriteUInt32(memory, bufferTable, (uint)(sharpBase & 0xFFFF_FFFFUL));
|
||||
WriteUInt32(
|
||||
memory,
|
||||
bufferTable + 4,
|
||||
(uint)(sharpBase >> 32) | (16u << 16));
|
||||
|
||||
var scalars = new uint[32];
|
||||
scalars[8] = (uint)(attribTable & 0xFFFF_FFFFUL);
|
||||
scalars[9] = (uint)(attribTable >> 32);
|
||||
scalars[10] = (uint)(bufferTable & 0xFFFF_FFFFUL);
|
||||
scalars[11] = (uint)(bufferTable >> 32);
|
||||
|
||||
var tables = new AgcVertexMetadata.VertexTableRegisters(
|
||||
VertexBufferReg: 10,
|
||||
VertexAttribReg: 8,
|
||||
InputSemanticsCount: 1,
|
||||
InputSemanticsAddress: semanticsAddress);
|
||||
|
||||
Assert.True(
|
||||
AgcVertexMetadata.TryBuildVertexResourcesFromMetadata(
|
||||
ctx,
|
||||
scalars,
|
||||
tables,
|
||||
out var resources));
|
||||
Assert.Single(resources);
|
||||
Assert.Equal(1u, resources[0].Semantic);
|
||||
Assert.Equal(4u, resources[0].HardwareMapping);
|
||||
Assert.Equal(8u, resources[0].OffsetBytes);
|
||||
Assert.Equal(5u, resources[0].DataFormat); // R16G16
|
||||
Assert.Equal(7u, resources[0].NumberFormat); // Float
|
||||
Assert.Equal(2u, resources[0].ComponentCount);
|
||||
Assert.Equal(sharpBase, resources[0].SharpBase);
|
||||
Assert.False(resources[0].PerInstance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergeVertexInputs_OverlaysFormatWithoutRebasingCapture()
|
||||
{
|
||||
const ulong memoryBase = 0x1_0000_0000;
|
||||
var memory = new FakeCpuMemory(memoryBase, 0x2000);
|
||||
var ctx = new CpuContext(memory, Generation.Gen5);
|
||||
|
||||
const ulong semanticsAddress = memoryBase + 0x100;
|
||||
const ulong attribTable = memoryBase + 0x200;
|
||||
const ulong bufferTable = memoryBase + 0x300;
|
||||
const ulong sharpBase = memoryBase + 0x800;
|
||||
|
||||
WriteUInt32(memory, semanticsAddress, 0u | (0u << 8) | (4u << 16));
|
||||
// format k8_8_8_8UNorm(56), offset=12
|
||||
WriteUInt32(memory, attribTable, 0u | (56u << 5) | (12u << 14));
|
||||
WriteUInt32(memory, bufferTable, (uint)(sharpBase & 0xFFFF_FFFFUL));
|
||||
WriteUInt32(memory, bufferTable + 4, (uint)(sharpBase >> 32) | (16u << 16));
|
||||
|
||||
var scalars = new uint[32];
|
||||
scalars[4] = (uint)(attribTable & 0xFFFF_FFFFUL);
|
||||
scalars[5] = (uint)(attribTable >> 32);
|
||||
scalars[6] = (uint)(bufferTable & 0xFFFF_FFFFUL);
|
||||
scalars[7] = (uint)(bufferTable >> 32);
|
||||
|
||||
var tables = new AgcVertexMetadata.VertexTableRegisters(
|
||||
VertexBufferReg: 6,
|
||||
VertexAttribReg: 4,
|
||||
InputSemanticsCount: 1,
|
||||
InputSemanticsAddress: semanticsAddress);
|
||||
|
||||
var data = new byte[64];
|
||||
var discovered = new[]
|
||||
{
|
||||
new Gen5VertexInputBinding(
|
||||
Pc: 0x40,
|
||||
Location: 0,
|
||||
ComponentCount: 4,
|
||||
DataFormat: 14, // wrong IR guess
|
||||
NumberFormat: 7,
|
||||
BaseAddress: sharpBase,
|
||||
Stride: 16,
|
||||
OffsetBytes: 0,
|
||||
Data: data,
|
||||
DataLength: data.Length,
|
||||
DataPooled: false),
|
||||
};
|
||||
|
||||
var merged = AgcVertexMetadata.MergeVertexInputsFromMetadata(
|
||||
ctx,
|
||||
scalars,
|
||||
tables,
|
||||
discovered);
|
||||
Assert.Single(merged);
|
||||
Assert.Equal(0u, merged[0].Location);
|
||||
Assert.Equal(sharpBase, merged[0].BaseAddress);
|
||||
Assert.Same(data, merged[0].Data);
|
||||
Assert.Equal(10u, merged[0].DataFormat); // RGBA8
|
||||
Assert.Equal(0u, merged[0].NumberFormat); // Unorm
|
||||
Assert.Equal(12u, merged[0].OffsetBytes);
|
||||
Assert.Equal(0x40u, merged[0].Pc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergeVertexInputs_AcceptsVertexAttribFormatEnums()
|
||||
{
|
||||
// Attrib tables store VertexAttribFormat (227 = rgba8 unorm), not
|
||||
// BufferFormat (56). Without conversion the format patch is a no-op.
|
||||
const ulong memoryBase = 0x1_0000_0000;
|
||||
var memory = new FakeCpuMemory(memoryBase, 0x2000);
|
||||
var ctx = new CpuContext(memory, Generation.Gen5);
|
||||
|
||||
const ulong semanticsAddress = memoryBase + 0x100;
|
||||
const ulong attribTable = memoryBase + 0x200;
|
||||
const ulong bufferTable = memoryBase + 0x300;
|
||||
const ulong sharpBase = memoryBase + 0x800;
|
||||
|
||||
WriteUInt32(memory, semanticsAddress, 0u | (0u << 8) | (4u << 16));
|
||||
WriteUInt32(memory, attribTable, 0u | (227u << 5) | (12u << 14)); // VertexAttribFormat
|
||||
WriteUInt32(memory, bufferTable, (uint)(sharpBase & 0xFFFF_FFFFUL));
|
||||
WriteUInt32(memory, bufferTable + 4, (uint)(sharpBase >> 32) | (16u << 16));
|
||||
|
||||
var scalars = new uint[32];
|
||||
scalars[4] = (uint)(attribTable & 0xFFFF_FFFFUL);
|
||||
scalars[5] = (uint)(attribTable >> 32);
|
||||
scalars[6] = (uint)(bufferTable & 0xFFFF_FFFFUL);
|
||||
scalars[7] = (uint)(bufferTable >> 32);
|
||||
|
||||
var tables = new AgcVertexMetadata.VertexTableRegisters(
|
||||
VertexBufferReg: 6,
|
||||
VertexAttribReg: 4,
|
||||
InputSemanticsCount: 1,
|
||||
InputSemanticsAddress: semanticsAddress);
|
||||
|
||||
var data = new byte[64];
|
||||
var discovered = new[]
|
||||
{
|
||||
new Gen5VertexInputBinding(
|
||||
0x40, 0, 4, 14, 7, sharpBase, 16, 12, data, data.Length, false),
|
||||
};
|
||||
|
||||
var merged = AgcVertexMetadata.MergeVertexInputsFromMetadata(
|
||||
ctx,
|
||||
scalars,
|
||||
tables,
|
||||
discovered);
|
||||
Assert.Equal(10u, merged[0].DataFormat);
|
||||
Assert.Equal(0u, merged[0].NumberFormat);
|
||||
Assert.Equal(12u, merged[0].OffsetBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergeVertexInputs_MatchesInterleavedAttrsByOffsetNotBareBase()
|
||||
{
|
||||
// Both attributes share SharpBase. Matching by base alone would assign
|
||||
// the color format to position (video/UI regression).
|
||||
const ulong memoryBase = 0x1_0000_0000;
|
||||
var memory = new FakeCpuMemory(memoryBase, 0x2000);
|
||||
var ctx = new CpuContext(memory, Generation.Gen5);
|
||||
|
||||
const ulong semanticsAddress = memoryBase + 0x100;
|
||||
const ulong attribTable = memoryBase + 0x200;
|
||||
const ulong bufferTable = memoryBase + 0x300;
|
||||
const ulong sharpBase = memoryBase + 0x800;
|
||||
|
||||
// semantic0 → pos float4 @0; semantic1 → color rgba8 @12
|
||||
WriteUInt32(memory, semanticsAddress, 0u | (0u << 8) | (4u << 16));
|
||||
WriteUInt32(memory, semanticsAddress + 4, 1u | (4u << 8) | (4u << 16));
|
||||
WriteUInt32(memory, attribTable, 0u | (77u << 5) | (0u << 14)); // k32_32_32_32Float
|
||||
WriteUInt32(memory, attribTable + 4, 0u | (56u << 5) | (12u << 14)); // rgba8unorm @12
|
||||
WriteUInt32(memory, bufferTable, (uint)(sharpBase & 0xFFFF_FFFFUL));
|
||||
WriteUInt32(memory, bufferTable + 4, (uint)(sharpBase >> 32) | (16u << 16));
|
||||
|
||||
var scalars = new uint[32];
|
||||
scalars[4] = (uint)(attribTable & 0xFFFF_FFFFUL);
|
||||
scalars[5] = (uint)(attribTable >> 32);
|
||||
scalars[6] = (uint)(bufferTable & 0xFFFF_FFFFUL);
|
||||
scalars[7] = (uint)(bufferTable >> 32);
|
||||
|
||||
var tables = new AgcVertexMetadata.VertexTableRegisters(
|
||||
VertexBufferReg: 6,
|
||||
VertexAttribReg: 4,
|
||||
InputSemanticsCount: 2,
|
||||
InputSemanticsAddress: semanticsAddress);
|
||||
|
||||
var data = new byte[64];
|
||||
var discovered = new[]
|
||||
{
|
||||
new Gen5VertexInputBinding(
|
||||
0x40, 0, 4, 14, 7, sharpBase, 16, 0, data, data.Length, false),
|
||||
new Gen5VertexInputBinding(
|
||||
0x80, 1, 4, 14, 7, sharpBase, 16, 12, data, data.Length, false),
|
||||
};
|
||||
|
||||
var merged = AgcVertexMetadata.MergeVertexInputsFromMetadata(
|
||||
ctx,
|
||||
scalars,
|
||||
tables,
|
||||
discovered);
|
||||
Assert.Equal(2, merged.Count);
|
||||
Assert.Equal(0u, merged[0].OffsetBytes);
|
||||
Assert.Equal(12u, merged[1].OffsetBytes);
|
||||
Assert.Equal(0u, merged[1].NumberFormat); // Unorm color, not float
|
||||
Assert.Equal(10u, merged[1].DataFormat); // RGBA8
|
||||
Assert.Equal(sharpBase, merged[0].BaseAddress);
|
||||
Assert.Equal(sharpBase, merged[1].BaseAddress);
|
||||
Assert.Same(data, merged[0].Data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollectFetchPrologPcs_FindsSBufferLoadsFromTableRegisters()
|
||||
{
|
||||
var tables = new AgcVertexMetadata.VertexTableRegisters(
|
||||
VertexBufferReg: 10,
|
||||
VertexAttribReg: 8,
|
||||
InputSemanticsCount: 1,
|
||||
InputSemanticsAddress: 1);
|
||||
|
||||
var program = new Gen5ShaderProgram(
|
||||
0,
|
||||
[
|
||||
new Gen5ShaderInstruction(
|
||||
0x10,
|
||||
Gen5ShaderEncoding.Smem,
|
||||
"SBufferLoadDword",
|
||||
Words: [],
|
||||
Sources: [Gen5Operand.Scalar(8)],
|
||||
Destinations: [Gen5Operand.Scalar(20)],
|
||||
new Gen5ScalarMemoryControl(1, 0, null)),
|
||||
new Gen5ShaderInstruction(
|
||||
0x20,
|
||||
Gen5ShaderEncoding.Smem,
|
||||
"SBufferLoadDword",
|
||||
Words: [],
|
||||
Sources: [Gen5Operand.Scalar(12)],
|
||||
Destinations: [Gen5Operand.Scalar(24)],
|
||||
new Gen5ScalarMemoryControl(1, 0, null)),
|
||||
new Gen5ShaderInstruction(
|
||||
0x30,
|
||||
Gen5ShaderEncoding.Sopp,
|
||||
"SEndpgm",
|
||||
Words: [],
|
||||
Sources: [],
|
||||
Destinations: [],
|
||||
null),
|
||||
]);
|
||||
|
||||
var pcs = AgcVertexMetadata.CollectFetchPrologPcs(program, tables);
|
||||
Assert.Contains(0x10u, pcs);
|
||||
Assert.DoesNotContain(0x20u, pcs);
|
||||
}
|
||||
|
||||
private static void WriteUInt32(FakeCpuMemory memory, ulong address, uint value)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[4];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(bytes, value);
|
||||
Assert.True(memory.TryWrite(address, bytes));
|
||||
}
|
||||
}
|
||||
@@ -105,92 +105,6 @@ public sealed class AprStreamingContractTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveFilepathsWithPrefixToIdsAndFileSizes_CombinesPrefixAndResolvesRealFile()
|
||||
{
|
||||
// Resource streamers call WithPrefix to join a directory prefix with a
|
||||
// relative asset path. Without HLE every call returned NOT_FOUND and no
|
||||
// asset received a real file id/size.
|
||||
const ulong memoryBase = 0x1_0000_0000;
|
||||
const ulong prefixAddress = memoryBase + 0x80;
|
||||
const ulong pathListAddress = memoryBase + 0x100;
|
||||
const ulong pathAddress = memoryBase + 0x200;
|
||||
const ulong idsAddress = memoryBase + 0x800;
|
||||
const ulong sizesAddress = memoryBase + 0x880;
|
||||
byte[] fileContents = [1, 2, 3, 4, 5, 6];
|
||||
var mountRoot = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
$"sharpemu-apr-prefix-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(mountRoot);
|
||||
var mountPoint = $"/sharpemu_apr_prefix_mnt_{Guid.NewGuid():N}";
|
||||
const string fileName = "asset.bin";
|
||||
var hostPath = Path.Combine(mountRoot, fileName);
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(hostPath, fileContents);
|
||||
KernelMemoryCompatExports.RegisterGuestPathMount(mountPoint, mountRoot);
|
||||
var memory = new FakeCpuMemory(memoryBase, 0x4000);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
memory.WriteCString(prefixAddress, mountPoint);
|
||||
memory.WriteCString(pathAddress, fileName);
|
||||
WriteUInt64(memory, pathListAddress, pathAddress);
|
||||
|
||||
context[CpuRegister.Rdi] = prefixAddress;
|
||||
context[CpuRegister.Rsi] = pathListAddress;
|
||||
context[CpuRegister.Rdx] = 1;
|
||||
context[CpuRegister.Rcx] = idsAddress;
|
||||
context[CpuRegister.R8] = sizesAddress;
|
||||
context[CpuRegister.R9] = 0;
|
||||
|
||||
Assert.Equal(
|
||||
(int)OrbisGen2Result.ORBIS_GEN2_OK,
|
||||
KernelMemoryCompatExports.KernelAprResolveFilepathsWithPrefixToIdsAndFileSizes(context));
|
||||
Assert.NotEqual(uint.MaxValue, ReadUInt32(memory, idsAddress));
|
||||
Assert.Equal((ulong)fileContents.Length, ReadUInt64(memory, sizesAddress));
|
||||
}
|
||||
finally
|
||||
{
|
||||
KernelMemoryCompatExports.UnregisterGuestPathMount(mountPoint);
|
||||
if (Directory.Exists(mountRoot))
|
||||
{
|
||||
Directory.Delete(mountRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveFilepathsWithPrefixToIdsAndFileSizes_MissingFile_FailsFastWithErrorIndex()
|
||||
{
|
||||
const ulong memoryBase = 0x1_0000_0000;
|
||||
const ulong prefixAddress = memoryBase + 0x80;
|
||||
const ulong pathListAddress = memoryBase + 0x100;
|
||||
const ulong pathAddress = memoryBase + 0x200;
|
||||
const ulong idsAddress = memoryBase + 0x800;
|
||||
const ulong sizesAddress = memoryBase + 0x880;
|
||||
const ulong errorIndexAddress = memoryBase + 0x8F0;
|
||||
var memory = new FakeCpuMemory(memoryBase, 0x4000);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
memory.WriteCString(prefixAddress, "/does-not-exist-prefix");
|
||||
memory.WriteCString(pathAddress, $"missing-{Guid.NewGuid():N}.bin");
|
||||
WriteUInt64(memory, pathListAddress, pathAddress);
|
||||
|
||||
context[CpuRegister.Rdi] = prefixAddress;
|
||||
context[CpuRegister.Rsi] = pathListAddress;
|
||||
context[CpuRegister.Rdx] = 1;
|
||||
context[CpuRegister.Rcx] = idsAddress;
|
||||
context[CpuRegister.R8] = sizesAddress;
|
||||
context[CpuRegister.R9] = errorIndexAddress;
|
||||
|
||||
Assert.Equal(
|
||||
-1,
|
||||
KernelMemoryCompatExports.KernelAprResolveFilepathsWithPrefixToIdsAndFileSizes(context));
|
||||
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
|
||||
Assert.Equal(uint.MaxValue, ReadUInt32(memory, idsAddress));
|
||||
Assert.Equal(0ul, ReadUInt64(memory, sizesAddress));
|
||||
Assert.Equal(0u, ReadUInt32(memory, errorIndexAddress));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveFilepathsToIdsAndFileSizes_MissingFile_FailsFastWithErrorIndex()
|
||||
{
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
// 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 AudioOut2PortGetStateExportsTests
|
||||
{
|
||||
private const ulong MemoryBase = 0x1_0000_0000;
|
||||
private const ulong StateAddress = MemoryBase + 0x100;
|
||||
|
||||
private static CpuContext CreateContext(out FakeCpuMemory memory)
|
||||
{
|
||||
memory = new FakeCpuMemory(MemoryBase, 0x1000);
|
||||
return new CpuContext(memory, Generation.Gen5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PortGetState_WritesFixedSizeIgnoringPollutedR9()
|
||||
{
|
||||
var ctx = CreateContext(out var memory);
|
||||
// Paint the buffer so we can see the write footprint.
|
||||
Span<byte> paint = stackalloc byte[0x100];
|
||||
paint.Fill(0xAB);
|
||||
Assert.True(memory.TryWrite(StateAddress, paint));
|
||||
|
||||
ctx[CpuRegister.Rdi] = 0xDE1FF6800001UL;
|
||||
ctx[CpuRegister.Rsi] = StateAddress;
|
||||
ctx[CpuRegister.Rdx] = StateAddress + 0x200;
|
||||
// Polluted GetSize leftover — must NOT enlarge the write.
|
||||
ctx[CpuRegister.R9] = 0x180;
|
||||
|
||||
var result = AudioOut2Exports.AudioOut2PortGetState(ctx);
|
||||
|
||||
Assert.Equal(0, result);
|
||||
Span<byte> state = stackalloc byte[0x100];
|
||||
Assert.True(memory.TryRead(StateAddress, state));
|
||||
Assert.Equal(1, BinaryPrimitives.ReadUInt16LittleEndian(state));
|
||||
Assert.Equal(2, state[2]);
|
||||
// Bytes past the fixed 0x20 header must remain untouched.
|
||||
Assert.Equal(0xAB, state[0x20]);
|
||||
Assert.Equal(0xAB, state[0x7F]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PortGetState_SkipsGuestStackOutBuffer()
|
||||
{
|
||||
var ctx = CreateContext(out _);
|
||||
const ulong stackOut = 0x00007FFFDE1FF688UL;
|
||||
ctx[CpuRegister.Rdi] = 0xDE1FF688004DUL;
|
||||
ctx[CpuRegister.Rsi] = stackOut;
|
||||
ctx[CpuRegister.Rdx] = 0;
|
||||
|
||||
var result = AudioOut2Exports.AudioOut2PortGetState(ctx);
|
||||
|
||||
Assert.Equal(0, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSpeakerInfo_WritesFixedSizeToRdiNotRsiTypeFlag()
|
||||
{
|
||||
var ctx = CreateContext(out var memory);
|
||||
Span<byte> paint = stackalloc byte[0x80];
|
||||
paint.Fill(0xCD);
|
||||
Assert.True(memory.TryWrite(StateAddress, paint));
|
||||
|
||||
ctx[CpuRegister.Rdi] = StateAddress;
|
||||
ctx[CpuRegister.Rsi] = 1;
|
||||
ctx[CpuRegister.Rdx] = StateAddress + 0x200;
|
||||
ctx[CpuRegister.R8] = 0x840;
|
||||
ctx[CpuRegister.R9] = 0x10C;
|
||||
|
||||
var result = AudioOut2Exports.AudioOut2GetSpeakerInfo(ctx);
|
||||
|
||||
Assert.Equal(0, result);
|
||||
Span<byte> info = stackalloc byte[0x80];
|
||||
Assert.True(memory.TryRead(StateAddress, info));
|
||||
Assert.Equal(2u, BinaryPrimitives.ReadUInt32LittleEndian(info));
|
||||
Assert.Equal(48000u, BinaryPrimitives.ReadUInt32LittleEndian(info[4..]));
|
||||
Assert.Equal(0xCD, info[0x20]);
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
// 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 AudioOut2SpeakerArrayExportsTests
|
||||
{
|
||||
private const ulong MemoryBase = 0x1_0000_0000;
|
||||
private const ulong OutHandleAddress = MemoryBase + 0x100;
|
||||
private const ulong ReservedAddress = MemoryBase + 0x120;
|
||||
private const ulong ParamAddress = MemoryBase + 0x200;
|
||||
private const ulong SpeakerMemoryAddress = MemoryBase + 0x400;
|
||||
|
||||
private static CpuContext CreateContext(out FakeCpuMemory memory)
|
||||
{
|
||||
memory = new FakeCpuMemory(MemoryBase, 0x2000);
|
||||
return new CpuContext(memory, Generation.Gen5);
|
||||
}
|
||||
|
||||
private static void WriteU64(FakeCpuMemory memory, ulong address, ulong value)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[8];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(bytes, value);
|
||||
Assert.True(memory.TryWrite(address, bytes));
|
||||
}
|
||||
|
||||
private static ulong ReadU64(FakeCpuMemory memory, ulong address)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[8];
|
||||
Assert.True(memory.TryRead(address, bytes));
|
||||
return BinaryPrimitives.ReadUInt64LittleEndian(bytes);
|
||||
}
|
||||
|
||||
private static uint ReadU32(FakeCpuMemory memory, ulong address)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[4];
|
||||
Assert.True(memory.TryRead(address, bytes));
|
||||
return BinaryPrimitives.ReadUInt32LittleEndian(bytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSpeakerArrayMemorySize_NeverReturnsTheNotFoundSentinel()
|
||||
{
|
||||
var ctx = CreateContext(out _);
|
||||
ctx[CpuRegister.Rdi] = 8;
|
||||
|
||||
var result = AudioOut2Exports.AudioOut2GetSpeakerArrayMemorySize(ctx);
|
||||
|
||||
Assert.NotEqual((int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND, result);
|
||||
Assert.Equal(0x40 + 8 * 0x100 + 0x400, result);
|
||||
Assert.Equal((ulong)result, ctx[CpuRegister.Rax]);
|
||||
Assert.True(result < 0x10000);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSpeakerArrayMemorySize_TwoChannelsIsExactChannelScaledSize()
|
||||
{
|
||||
var ctx = CreateContext(out _);
|
||||
ctx[CpuRegister.Rdi] = 2;
|
||||
|
||||
var result = AudioOut2Exports.AudioOut2GetSpeakerArrayMemorySize(ctx);
|
||||
|
||||
Assert.Equal(0x40 + 2 * 0x100 + 0x400, result);
|
||||
Assert.Equal(0x640UL, ctx[CpuRegister.Rax]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpeakerArrayCreate_PublishesObjectPointerAndLeavesReservedSizeAlone()
|
||||
{
|
||||
var ctx = CreateContext(out var memory);
|
||||
// Stage a size in the reserved slot the way callers do before Create.
|
||||
WriteU64(memory, ReservedAddress, 0x100);
|
||||
ctx[CpuRegister.Rdi] = ParamAddress;
|
||||
ctx[CpuRegister.Rsi] = OutHandleAddress;
|
||||
ctx[CpuRegister.Rdx] = ReservedAddress;
|
||||
ctx[CpuRegister.Rcx] = 2;
|
||||
|
||||
var result = AudioOut2Exports.AudioOut2SpeakerArrayCreate(ctx);
|
||||
|
||||
Assert.Equal(0, result);
|
||||
Assert.NotEqual(0UL, ctx[CpuRegister.Rax]);
|
||||
Assert.NotEqual(0x100UL, ctx[CpuRegister.Rax]);
|
||||
Assert.Equal(ctx[CpuRegister.Rax], ReadU64(memory, OutHandleAddress));
|
||||
// Reserved/size slot must remain untouched — writing it corrupted canaries.
|
||||
Assert.Equal(0x100UL, ReadU64(memory, ReservedAddress));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpeakerArrayCreate_PublishesHandleForTypicalCallShape()
|
||||
{
|
||||
var ctx = CreateContext(out _);
|
||||
ctx[CpuRegister.Rdi] = ParamAddress;
|
||||
ctx[CpuRegister.Rsi] = OutHandleAddress;
|
||||
ctx[CpuRegister.Rdx] = ReservedAddress;
|
||||
ctx[CpuRegister.Rcx] = 2;
|
||||
|
||||
var result = AudioOut2Exports.AudioOut2SpeakerArrayCreate(ctx);
|
||||
|
||||
Assert.Equal(0, result);
|
||||
Assert.NotEqual(0UL, ctx[CpuRegister.Rax]);
|
||||
Assert.NotEqual(0x10000UL, ctx[CpuRegister.Rax]);
|
||||
Assert.NotEqual((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpeakerArrayCreate_IgnoresCorruptedParamBufferFields()
|
||||
{
|
||||
var ctx = CreateContext(out var memory);
|
||||
// Simulate PortGetState having overwritten param+0x18 (size) with a
|
||||
// state blob — Create must NOT adopt that as an in-place buffer.
|
||||
WriteU64(memory, ParamAddress + 0x10, SpeakerMemoryAddress);
|
||||
WriteU64(memory, ParamAddress + 0x18, 0x100);
|
||||
ctx[CpuRegister.Rdi] = ParamAddress;
|
||||
ctx[CpuRegister.Rsi] = OutHandleAddress;
|
||||
ctx[CpuRegister.Rdx] = ReservedAddress;
|
||||
ctx[CpuRegister.Rcx] = 2;
|
||||
|
||||
var result = AudioOut2Exports.AudioOut2SpeakerArrayCreate(ctx);
|
||||
|
||||
Assert.Equal(0, result);
|
||||
Assert.NotEqual(SpeakerMemoryAddress, ctx[CpuRegister.Rax]);
|
||||
Assert.NotEqual(0x100UL, ctx[CpuRegister.Rax]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpeakerArrayDestroy_UnknownHandleStillSucceeds()
|
||||
{
|
||||
var ctx = CreateContext(out _);
|
||||
ctx[CpuRegister.Rdi] = 0xDEAD_BEEF;
|
||||
|
||||
var result = AudioOut2Exports.AudioOut2SpeakerArrayDestroy(ctx);
|
||||
|
||||
Assert.Equal(0, result);
|
||||
}
|
||||
}
|
||||
@@ -7,18 +7,15 @@ namespace SharpEmu.Libs.Tests;
|
||||
|
||||
// A single contiguous guest region backed by a byte[]. Enough to hand C strings and small
|
||||
// structures to HLE exports under test without a live guest.
|
||||
internal sealed class FakeCpuMemory : ICpuMemory, IGuestMemoryAllocator
|
||||
internal sealed class FakeCpuMemory : ICpuMemory
|
||||
{
|
||||
private readonly ulong _base;
|
||||
private readonly byte[] _storage;
|
||||
private ulong _allocBump;
|
||||
|
||||
public FakeCpuMemory(ulong baseAddress, int size)
|
||||
{
|
||||
_base = baseAddress;
|
||||
_storage = new byte[size];
|
||||
// Bump from the top so test fixtures at low offsets stay intact.
|
||||
_allocBump = baseAddress + (ulong)size;
|
||||
}
|
||||
|
||||
public bool TryRead(ulong virtualAddress, Span<byte> destination)
|
||||
@@ -43,33 +40,6 @@ internal sealed class FakeCpuMemory : ICpuMemory, IGuestMemoryAllocator
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryAllocateGuestMemory(ulong size, ulong alignment, out ulong address)
|
||||
{
|
||||
address = 0;
|
||||
if (size == 0 || alignment == 0 || (alignment & (alignment - 1)) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var alignedSize = (size + alignment - 1) & ~(alignment - 1);
|
||||
if (alignedSize > _allocBump - _base)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var next = (_allocBump - alignedSize) & ~(alignment - 1);
|
||||
if (next < _base)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_allocBump = next;
|
||||
address = next;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryFreeGuestMemory(ulong address) => false;
|
||||
|
||||
public ulong WriteCString(ulong virtualAddress, string text)
|
||||
{
|
||||
var bytes = System.Text.Encoding.UTF8.GetBytes(text);
|
||||
|
||||
@@ -125,125 +125,6 @@ public sealed class KernelMemoryCompatExportsTests
|
||||
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
|
||||
}
|
||||
|
||||
// FreeBSD/PS4 EBADF; PosixFailure maps ORBIS NOT_FOUND on fd calls to this.
|
||||
private const int Ebadf = 9;
|
||||
// FreeBSD/PS4 EACCES; PosixFailure maps ORBIS PERMISSION_DENIED to this.
|
||||
private const int Eacces = 13;
|
||||
// TLS slot used by KernelRuntimeCompatExports.TrySetErrno (FsBase + 0x40).
|
||||
private const ulong TlsErrnoOffset = 0x40;
|
||||
|
||||
[Fact]
|
||||
public void PosixLseek_BadDescriptorReturnsMinusOneWithEbadf()
|
||||
{
|
||||
const ulong memoryBase = 0x1_0000_0000;
|
||||
const ulong fsBase = memoryBase + 0x100;
|
||||
var memory = new FakeCpuMemory(memoryBase, 0x1000);
|
||||
var context = new CpuContext(memory, Generation.Gen5)
|
||||
{
|
||||
FsBase = fsBase,
|
||||
};
|
||||
context[CpuRegister.Rdi] = 0x80020002; // never-opened / sentinel fd
|
||||
context[CpuRegister.Rsi] = 0;
|
||||
context[CpuRegister.Rdx] = 0; // SEEK_SET
|
||||
|
||||
var result = KernelMemoryCompatExports.PosixLseek(context);
|
||||
|
||||
Assert.Equal(-1, result);
|
||||
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
|
||||
Assert.Equal(Ebadf, ReadErrno(memory, fsBase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PosixPread_BadDescriptorReturnsMinusOneWithEbadf()
|
||||
{
|
||||
const ulong memoryBase = 0x1_0000_0000;
|
||||
const ulong bufferAddress = memoryBase + 0x200;
|
||||
const ulong fsBase = memoryBase + 0x100;
|
||||
var memory = new FakeCpuMemory(memoryBase, 0x1000);
|
||||
var context = new CpuContext(memory, Generation.Gen5)
|
||||
{
|
||||
FsBase = fsBase,
|
||||
};
|
||||
context[CpuRegister.Rdi] = 0x80020002; // never-opened / sentinel fd
|
||||
context[CpuRegister.Rsi] = bufferAddress;
|
||||
context[CpuRegister.Rdx] = 0x40;
|
||||
context[CpuRegister.Rcx] = 0; // offset
|
||||
|
||||
var result = KernelMemoryCompatExports.PosixPread(context);
|
||||
|
||||
Assert.Equal(-1, result);
|
||||
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
|
||||
Assert.Equal(Ebadf, ReadErrno(memory, fsBase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KernelPread_BadDescriptorStillReturnsOrbisNotFound()
|
||||
{
|
||||
// sceKernel* entry points keep the raw Orbis ABI; only Posix* maps to -1/errno.
|
||||
const ulong memoryBase = 0x1_0000_0000;
|
||||
const ulong bufferAddress = memoryBase + 0x200;
|
||||
var memory = new FakeCpuMemory(memoryBase, 0x1000);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
context[CpuRegister.Rdi] = 0x80020002;
|
||||
context[CpuRegister.Rsi] = bufferAddress;
|
||||
context[CpuRegister.Rdx] = 0x40;
|
||||
context[CpuRegister.Rcx] = 0;
|
||||
|
||||
var result = KernelMemoryCompatExports.KernelPread(context);
|
||||
|
||||
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PosixOpen_MutatingApp0ReturnsMinusOneWithEacces()
|
||||
{
|
||||
// /app0 is read-only for mutating opens (retail semantics). That path
|
||||
// returns PERMISSION_DENIED which PosixFailure maps to EACCES - the same
|
||||
// errno UnauthorizedAccessException open failures now produce.
|
||||
var tempRoot = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
$"sharpemu-posix-open-eacces-{Guid.NewGuid():N}");
|
||||
var app0Root = Path.Combine(tempRoot, "app0");
|
||||
Directory.CreateDirectory(app0Root);
|
||||
KernelMemoryCompatExports.RegisterGuestPathMount("/app0", app0Root);
|
||||
|
||||
try
|
||||
{
|
||||
const ulong memoryBase = 0x1_0000_0000;
|
||||
const ulong pathAddress = memoryBase + 0x200;
|
||||
const ulong fsBase = memoryBase + 0x100;
|
||||
var memory = new FakeCpuMemory(memoryBase, 0x1000);
|
||||
var context = new CpuContext(memory, Generation.Gen5)
|
||||
{
|
||||
FsBase = fsBase,
|
||||
};
|
||||
memory.WriteCString(pathAddress, "/app0/readonly-create.bin");
|
||||
context[CpuRegister.Rdi] = pathAddress;
|
||||
context[CpuRegister.Rsi] = 0x0201; // O_WRONLY | O_CREAT
|
||||
|
||||
var result = KernelMemoryCompatExports.PosixOpen(context);
|
||||
|
||||
Assert.Equal(-1, result);
|
||||
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
|
||||
Assert.Equal(Eacces, ReadErrno(memory, fsBase));
|
||||
}
|
||||
finally
|
||||
{
|
||||
KernelMemoryCompatExports.UnregisterGuestPathMount("/app0");
|
||||
if (Directory.Exists(tempRoot))
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int ReadErrno(FakeCpuMemory memory, ulong fsBase)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[sizeof(int)];
|
||||
Assert.True(memory.TryRead(fsBase + TlsErrnoOffset, bytes));
|
||||
return BitConverter.ToInt32(bytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Sprintf_ReadsVariadicDoubleFromXmmRegister()
|
||||
{
|
||||
|
||||
@@ -24,53 +24,13 @@ public sealed unsafe class GuestImageWriteTrackerTests
|
||||
// spilling onto neighbouring heap pages.
|
||||
private const nuint TrackedByteCount = 4096;
|
||||
private const nuint HostPageAlignment = 16384;
|
||||
private const uint MemCommit = 0x1000;
|
||||
private const uint MemReserve = 0x2000;
|
||||
private const uint MemRelease = 0x8000;
|
||||
private const uint PageReadWrite = 0x04;
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern nint VirtualAlloc(
|
||||
nint lpAddress,
|
||||
nuint dwSize,
|
||||
uint flAllocationType,
|
||||
uint flProtect);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern int VirtualFree(nint lpAddress, nuint dwSize, uint dwFreeType);
|
||||
|
||||
private static ulong AllocateTrackedPages(out void* allocation)
|
||||
{
|
||||
// VirtualProtect (Windows) / mprotect (POSIX) must target
|
||||
// VirtualAlloc/mmap pages. Protecting CRT heap pages poisons
|
||||
// neighbouring allocator metadata and crashes the test host.
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
var windowsAllocation = VirtualAlloc(
|
||||
0,
|
||||
HostPageAlignment,
|
||||
MemCommit | MemReserve,
|
||||
PageReadWrite);
|
||||
Assert.NotEqual(nint.Zero, windowsAllocation);
|
||||
allocation = (void*)windowsAllocation;
|
||||
return (ulong)windowsAllocation;
|
||||
}
|
||||
|
||||
allocation = NativeMemory.AlignedAlloc(2 * HostPageAlignment, HostPageAlignment);
|
||||
return (ulong)allocation;
|
||||
}
|
||||
|
||||
private static void FreeTrackedPages(void* allocation)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
_ = VirtualFree((nint)allocation, 0, MemRelease);
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMemory.Free(allocation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerationSurvivesDirtyConsume()
|
||||
{
|
||||
@@ -98,7 +58,7 @@ public sealed unsafe class GuestImageWriteTrackerTests
|
||||
finally
|
||||
{
|
||||
GuestImageWriteTracker.Untrack(address);
|
||||
FreeTrackedPages(allocation);
|
||||
NativeMemory.Free(allocation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +89,7 @@ public sealed unsafe class GuestImageWriteTrackerTests
|
||||
finally
|
||||
{
|
||||
GuestImageWriteTracker.Untrack(address);
|
||||
FreeTrackedPages(allocation);
|
||||
NativeMemory.Free(allocation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +118,7 @@ public sealed unsafe class GuestImageWriteTrackerTests
|
||||
finally
|
||||
{
|
||||
GuestImageWriteTracker.Untrack(address);
|
||||
FreeTrackedPages(allocation);
|
||||
NativeMemory.Free(allocation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using SharpEmu.Core.Cpu.Native;
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Libs.Kernel;
|
||||
using SharpEmu.Libs.LibcStdio;
|
||||
using SharpEmu.Libs.Messenger;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Libs.Tests;
|
||||
|
||||
public sealed class MessengerCompatExportsTests
|
||||
{
|
||||
[Fact]
|
||||
public void Cosf_UsesScalarXmmArgumentAndReturn()
|
||||
{
|
||||
const ulong memoryBase = 0x1_0000_0000;
|
||||
var context = new CpuContext(new FakeCpuMemory(memoryBase, 0x1000), Generation.Gen5);
|
||||
var input = 0.5f;
|
||||
var inputBits = unchecked((uint)BitConverter.SingleToInt32Bits(input));
|
||||
context[CpuRegister.Rdi] = 0xDEAD_BEEF; // Must not be used as the argument.
|
||||
context.SetXmmRegister(0, 0xAABB_CCDD_0000_0000UL | inputBits, 0x1122_3344_5566_7788UL);
|
||||
|
||||
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, MessengerCompatExports.Cosf(context));
|
||||
|
||||
context.GetXmmRegister(0, out var low, out var high);
|
||||
var expectedBits = unchecked((uint)BitConverter.SingleToInt32Bits(MathF.Cos(input)));
|
||||
Assert.Equal(expectedBits, unchecked((uint)low));
|
||||
Assert.Equal(0xAABB_CCDDUL, low >> 32);
|
||||
Assert.Equal(0x1122_3344_5566_7788UL, high);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CtypeCaseTables_MapAsciiCharacters()
|
||||
{
|
||||
var context = new CpuContext(new FakeCpuMemory(0x1_0000_0000, 0x1000), Generation.Gen5);
|
||||
|
||||
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, LibcStdioExports.GetPtolower(context));
|
||||
var lower = unchecked((nint)(long)context[CpuRegister.Rax]);
|
||||
Assert.Equal((short)'a', Marshal.ReadInt16(lower + ('A' * sizeof(short))));
|
||||
Assert.Equal((short)'z', Marshal.ReadInt16(lower + ('z' * sizeof(short))));
|
||||
Assert.Equal((short)-1, Marshal.ReadInt16(lower - sizeof(short)));
|
||||
|
||||
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, LibcStdioExports.GetPtoupper(context));
|
||||
var upper = unchecked((nint)(long)context[CpuRegister.Rax]);
|
||||
Assert.Equal((short)'A', Marshal.ReadInt16(upper + ('a' * sizeof(short))));
|
||||
Assert.Equal((short)'Z', Marshal.ReadInt16(upper + ('Z' * sizeof(short))));
|
||||
Assert.Equal((short)-1, Marshal.ReadInt16(upper - sizeof(short)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Il2CppLookup_ReturnsPointerInRaxWithoutWritingRsi()
|
||||
{
|
||||
const ulong memoryBase = 0x1_0000_0000;
|
||||
const ulong nameAddress = memoryBase + 0x100;
|
||||
const ulong outputAddress = memoryBase + 0x200;
|
||||
const ulong resolvedAddress = 0x2_0000_0000;
|
||||
var memory = new FakeCpuMemory(memoryBase, 0x1000);
|
||||
memory.WriteCString(nameAddress, "il2cpp_test");
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
context[CpuRegister.Rdi] = nameAddress;
|
||||
context[CpuRegister.Rsi] = outputAddress; // Live caller state, not an output pointer.
|
||||
Assert.True(context.TryWriteUInt64(outputAddress, 0xCAFE_BABE));
|
||||
|
||||
var result = Il2CppApiLookupAbi.SetResult(context, resolved: true, resolvedAddress);
|
||||
|
||||
Assert.Equal(OrbisGen2Result.ORBIS_GEN2_OK, result);
|
||||
Assert.Equal(resolvedAddress, context[CpuRegister.Rax]);
|
||||
Assert.True(context.TryReadUInt64(outputAddress, out var output));
|
||||
Assert.Equal(0xCAFE_BABEUL, output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Il2CppLookup_MissingApiReturnsNullWithoutWritingRsi()
|
||||
{
|
||||
const ulong memoryBase = 0x1_0000_0000;
|
||||
const ulong nameAddress = memoryBase + 0x100;
|
||||
const ulong outputAddress = memoryBase + 0x200;
|
||||
var memory = new FakeCpuMemory(memoryBase, 0x1000);
|
||||
memory.WriteCString(nameAddress, "il2cpp_missing");
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
context[CpuRegister.Rdi] = nameAddress;
|
||||
context[CpuRegister.Rsi] = outputAddress;
|
||||
Assert.True(context.TryWriteUInt64(outputAddress, 0xCAFE_BABE));
|
||||
|
||||
var result = Il2CppApiLookupAbi.SetResult(context, resolved: false, address: 0);
|
||||
|
||||
Assert.Equal(OrbisGen2Result.ORBIS_GEN2_OK, result);
|
||||
Assert.Equal(0UL, context[CpuRegister.Rax]);
|
||||
Assert.True(context.TryReadUInt64(outputAddress, out var output));
|
||||
Assert.Equal(0xCAFE_BABEUL, output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TrackedLibcHeapFallback_ReadsAndWritesHostAllocation()
|
||||
{
|
||||
var context = new CpuContext(new FakeCpuMemory(0x1_0000_0000, 0x1000), Generation.Gen5);
|
||||
context[CpuRegister.Rdi] = 16;
|
||||
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, KernelMemoryCompatExports.Malloc(context));
|
||||
var address = context[CpuRegister.Rax];
|
||||
Assert.NotEqual(0UL, address);
|
||||
|
||||
try
|
||||
{
|
||||
Assert.True(KernelMemoryCompatExports.TryWriteUInt64Compat(context, address, 0x1234_5678_9ABC_DEF0UL));
|
||||
Span<byte> bytes = stackalloc byte[8];
|
||||
Assert.True(KernelMemoryCompatExports.TryReadTrackedLibcHeap(address, bytes));
|
||||
Assert.True(KernelMemoryCompatExports.TryReadUInt64Compat(context, address, out var value));
|
||||
Assert.Equal(0x1234_5678_9ABC_DEF0UL, value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
context[CpuRegister.Rdi] = address;
|
||||
_ = KernelMemoryCompatExports.Free(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Libs.Remoteplay;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Libs.Tests.Remoteplay;
|
||||
|
||||
public sealed class RemoteplayExportsTests
|
||||
{
|
||||
private const ulong MemoryBase = 0x1_0000_0000;
|
||||
private const ulong StatusAddress = MemoryBase + 0x100;
|
||||
|
||||
private static CpuContext CreateContext(out FakeCpuMemory memory)
|
||||
{
|
||||
memory = new FakeCpuMemory(MemoryBase, 0x1000);
|
||||
return new CpuContext(memory, Generation.Gen5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initialize_Succeeds()
|
||||
{
|
||||
var ctx = CreateContext(out _);
|
||||
|
||||
var result = RemoteplayExports.RemoteplayInitialize(ctx);
|
||||
|
||||
Assert.Equal(0, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetConnectionStatus_WritesDisconnectedStatus()
|
||||
{
|
||||
var ctx = CreateContext(out var memory);
|
||||
ctx[CpuRegister.Rdi] = 0x1000_0000;
|
||||
ctx[CpuRegister.Rsi] = StatusAddress;
|
||||
memory.TryWrite(StatusAddress, stackalloc byte[] { 0xFF, 0xFF, 0xFF, 0xFF });
|
||||
|
||||
var result = RemoteplayExports.RemoteplayGetConnectionStatus(ctx);
|
||||
|
||||
Assert.Equal(0, result);
|
||||
var status = new byte[4];
|
||||
Assert.True(ctx.Memory.TryRead(StatusAddress, status));
|
||||
Assert.Equal(0, status[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetConnectionStatus_NullOutPointerStillSucceeds()
|
||||
{
|
||||
var ctx = CreateContext(out _);
|
||||
ctx[CpuRegister.Rdi] = 0x1000_0000;
|
||||
ctx[CpuRegister.Rsi] = 0;
|
||||
|
||||
var result = RemoteplayExports.RemoteplayGetConnectionStatus(ctx);
|
||||
|
||||
Assert.Equal(0, result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user