// Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later using System.Buffers.Binary; using SharpEmu.HLE; using SharpEmu.Libs.Fiber; using Xunit; namespace SharpEmu.Libs.Tests.Fiber; /// /// Contract tests for the libSceFiber HLE exports. These pin the current /// validation and layout behaviour of ; they do not /// exercise a live guest thread scheduler. /// public sealed class FiberExportsTests { private const ulong Base = 0x3_0000_0000UL; private const int RegionSize = 0x2000; private const int ErrorNull = unchecked((int)0x80590001); private const int ErrorAlignment = unchecked((int)0x80590002); private const int ErrorRange = unchecked((int)0x80590003); private const int ErrorInvalid = unchecked((int)0x80590004); private const int ErrorPermission = unchecked((int)0x80590005); private const uint SignatureStart = 0xDEF1649Cu; private const uint SignatureEnd = 0xB37592A0u; private const ulong StackSignature = 0x7149F2CA7149F2CAUL; private const uint StateIdle = 2; private const ulong FiberAddress = Base; private const ulong NameAddress = Base + 0x200; private const ulong ContextAddress = Base + 0x400; private const ulong EntryAddress = 0x4_0000_1000UL; private const ulong InfoAddress = Base + 0x800; public FiberExportsTests() { FiberExports.ResetRuntimeState(); } [Fact] public void OptParamInitialize_NullParam_ReturnsNullError() { var memory = new FakeCpuMemory(Base, RegionSize); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = 0; var result = FiberExports.FiberOptParamInitialize(context); Assert.Equal(ErrorNull, result); } [Fact] public void GetSelf_NullOutAddress_ReturnsNullError() { var memory = new FakeCpuMemory(Base, RegionSize); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = 0; var result = FiberExports.FiberGetSelf(context); Assert.Equal(ErrorNull, result); } [Fact] public void GetSelf_OutsideFiberContext_ReturnsPermissionError() { var memory = new FakeCpuMemory(Base, RegionSize); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = Base + 0x100; var result = FiberExports.FiberGetSelf(context); Assert.Equal(ErrorPermission, result); } [Fact] public void GetInfo_NullInfo_ReturnsNullError() { var memory = new FakeCpuMemory(Base, RegionSize); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = FiberAddress; context[CpuRegister.Rsi] = 0; var result = FiberExports.FiberGetInfo(context); Assert.Equal(ErrorNull, result); } [Fact] public void Initialize_NullFiber_ReturnsNullError() { var memory = new FakeCpuMemory(Base, RegionSize); var context = new CpuContext(memory, Generation.Gen5); WriteCString(memory, NameAddress, "F"); context[CpuRegister.Rdi] = 0; context[CpuRegister.Rsi] = NameAddress; context[CpuRegister.Rdx] = EntryAddress; var result = FiberExports.FiberInitialize(context); Assert.Equal(ErrorNull, result); } [Fact] public void Initialize_NullName_ReturnsNullError() { var memory = new FakeCpuMemory(Base, RegionSize); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = FiberAddress; context[CpuRegister.Rsi] = 0; context[CpuRegister.Rdx] = EntryAddress; var result = FiberExports.FiberInitialize(context); Assert.Equal(ErrorNull, result); } [Fact] public void Initialize_NullEntry_ReturnsNullError() { var memory = new FakeCpuMemory(Base, RegionSize); var context = new CpuContext(memory, Generation.Gen5); WriteCString(memory, NameAddress, "F"); context[CpuRegister.Rdi] = FiberAddress; context[CpuRegister.Rsi] = NameAddress; context[CpuRegister.Rdx] = 0; var result = FiberExports.FiberInitialize(context); Assert.Equal(ErrorNull, result); } [Fact] public void Initialize_MisalignedFiber_ReturnsAlignmentError() { var memory = new FakeCpuMemory(Base, RegionSize); var context = new CpuContext(memory, Generation.Gen5); WriteCString(memory, NameAddress, "F"); context[CpuRegister.Rdi] = FiberAddress + 4; context[CpuRegister.Rsi] = NameAddress; context[CpuRegister.Rdx] = EntryAddress; var result = FiberExports.FiberInitialize(context); Assert.Equal(ErrorAlignment, result); } [Fact] public void Initialize_TooSmallContextSize_ReturnsRangeError() { var memory = new FakeCpuMemory(Base, RegionSize); var context = new CpuContext(memory, Generation.Gen5); WriteCString(memory, NameAddress, "F"); context[CpuRegister.Rdi] = FiberAddress; context[CpuRegister.Rsi] = NameAddress; context[CpuRegister.Rdx] = EntryAddress; context[CpuRegister.R8] = ContextAddress; context[CpuRegister.R9] = 256; var result = FiberExports.FiberInitialize(context); Assert.Equal(ErrorRange, result); } [Fact] public void Initialize_ContextAddressWithoutSize_ReturnsInvalidError() { var memory = new FakeCpuMemory(Base, RegionSize); var context = new CpuContext(memory, Generation.Gen5); WriteCString(memory, NameAddress, "F"); context[CpuRegister.Rdi] = FiberAddress; context[CpuRegister.Rsi] = NameAddress; context[CpuRegister.Rdx] = EntryAddress; context[CpuRegister.R8] = ContextAddress; context[CpuRegister.R9] = 0; var result = FiberExports.FiberInitialize(context); Assert.Equal(ErrorInvalid, result); } [Fact] public void Initialize_Valid_WritesExpectedLayout() { var memory = new FakeCpuMemory(Base, RegionSize); var context = new CpuContext(memory, Generation.Gen5); const string name = "TestFiber"; WriteCString(memory, NameAddress, name); const ulong argOnInitialize = 0xDEADUL; const ulong contextSize = 512UL; context[CpuRegister.Rdi] = FiberAddress; context[CpuRegister.Rsi] = NameAddress; context[CpuRegister.Rdx] = EntryAddress; context[CpuRegister.Rcx] = argOnInitialize; context[CpuRegister.R8] = ContextAddress; context[CpuRegister.R9] = contextSize; // RSP defaults to 0 (unmapped); ReadStackArg64 falls back to 0 -> // optParam = 0, buildVersion = 0. ApplyInitializationFlags(0, 0, false) == 0. var result = FiberExports.FiberInitialize(context); Assert.Equal(0, result); Assert.Equal(0UL, context[CpuRegister.Rax]); Assert.Equal(SignatureStart, ReadUInt32(memory, FiberAddress + 0)); Assert.Equal(StateIdle, ReadUInt32(memory, FiberAddress + 4)); Assert.Equal(EntryAddress, ReadUInt64(memory, FiberAddress + 8)); Assert.Equal(argOnInitialize, ReadUInt64(memory, FiberAddress + 16)); Assert.Equal(ContextAddress, ReadUInt64(memory, FiberAddress + 24)); Assert.Equal(contextSize, ReadUInt64(memory, FiberAddress + 32)); AssertInlineName(memory, FiberAddress + 40, name); Assert.Equal(0UL, ReadUInt64(memory, FiberAddress + 72)); Assert.Equal(0u, ReadUInt32(memory, FiberAddress + 80)); Assert.Equal(ContextAddress, ReadUInt64(memory, FiberAddress + 88)); Assert.Equal(ContextAddress + contextSize, ReadUInt64(memory, FiberAddress + 96)); Assert.Equal(SignatureEnd, ReadUInt32(memory, FiberAddress + 104)); Assert.Equal(StackSignature, ReadUInt64(memory, ContextAddress)); } [Fact] public void GetInfo_AfterInitialize_RoundTripsFields() { var memory = new FakeCpuMemory(Base, RegionSize); var context = new CpuContext(memory, Generation.Gen5); const string name = "RoundTrip"; WriteCString(memory, NameAddress, name); const ulong argOnInitialize = 0xCAFEUL; const ulong contextSize = 512UL; context[CpuRegister.Rdi] = FiberAddress; context[CpuRegister.Rsi] = NameAddress; context[CpuRegister.Rdx] = EntryAddress; context[CpuRegister.Rcx] = argOnInitialize; context[CpuRegister.R8] = ContextAddress; context[CpuRegister.R9] = contextSize; Assert.Equal(0, FiberExports.FiberInitialize(context)); WriteUInt64(memory, InfoAddress, 128); context[CpuRegister.Rdi] = FiberAddress; context[CpuRegister.Rsi] = InfoAddress; var result = FiberExports.FiberGetInfo(context); Assert.Equal(0, result); Assert.Equal(EntryAddress, ReadUInt64(memory, InfoAddress + 8)); Assert.Equal(argOnInitialize, ReadUInt64(memory, InfoAddress + 16)); Assert.Equal(ContextAddress, ReadUInt64(memory, InfoAddress + 24)); Assert.Equal(contextSize, ReadUInt64(memory, InfoAddress + 32)); AssertInlineName(memory, InfoAddress + 40, name); Assert.Equal(ulong.MaxValue, ReadUInt64(memory, InfoAddress + 72)); } [Fact] public void GetInfo_WrongSize_ReturnsInvalidError() { var memory = new FakeCpuMemory(Base, RegionSize); var context = new CpuContext(memory, Generation.Gen5); WriteCString(memory, NameAddress, "F"); context[CpuRegister.Rdi] = FiberAddress; context[CpuRegister.Rsi] = NameAddress; context[CpuRegister.Rdx] = EntryAddress; context[CpuRegister.R8] = ContextAddress; context[CpuRegister.R9] = 512; Assert.Equal(0, FiberExports.FiberInitialize(context)); WriteUInt64(memory, InfoAddress, 64); context[CpuRegister.Rdi] = FiberAddress; context[CpuRegister.Rsi] = InfoAddress; var result = FiberExports.FiberGetInfo(context); Assert.Equal(ErrorInvalid, result); } private static void WriteCString(FakeCpuMemory memory, ulong address, string text) { memory.WriteCString(address, text); } private static void WriteUInt64(FakeCpuMemory memory, ulong address, ulong value) { Span buffer = stackalloc byte[sizeof(ulong)]; BinaryPrimitives.WriteUInt64LittleEndian(buffer, value); Assert.True(memory.TryWrite(address, buffer)); } private static uint ReadUInt32(FakeCpuMemory memory, ulong address) { Span buffer = stackalloc byte[sizeof(uint)]; Assert.True(memory.TryRead(address, buffer)); return BinaryPrimitives.ReadUInt32LittleEndian(buffer); } private static ulong ReadUInt64(FakeCpuMemory memory, ulong address) { Span buffer = stackalloc byte[sizeof(ulong)]; Assert.True(memory.TryRead(address, buffer)); return BinaryPrimitives.ReadUInt64LittleEndian(buffer); } private static void AssertInlineName(FakeCpuMemory memory, ulong address, string expected) { Span buffer = stackalloc byte[32]; Assert.True(memory.TryRead(address, buffer)); var length = buffer.IndexOf((byte)0); if (length < 0) { length = buffer.Length; } Assert.Equal(expected, System.Text.Encoding.UTF8.GetString(buffer[..length])); } }