Files
sharpemu/tests/SharpEmu.Libs.Tests/MessengerCompatExportsTests.cs
T
Kurt Himebauch 2764aaab3f feat: implement cosf, time, ctype tables, tracked heap access, IL2CPP lookup ABI paths (#542)
* fix: add Messenger CRT and AGC compatibility shims

* fix: keep Messenger IL2CPP bootstrap on HLE shims

* Fix Messenger compatibility ABI handling

* Make IL2CPP ABI regression tests portable
2026-07-23 15:35:59 +03:00

120 lines
5.1 KiB
C#

// 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);
}
}
}