// Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later using SharpEmu.HLE; using SharpEmu.Core.Memory; using SharpEmu.HLE.Host; using SharpEmu.HLE.GpuMemory; using SharpEmu.Libs.Kernel; using SharpEmu.Libs.Tests.Memory.GpuMemory; using System.Globalization; using System.Text; using Xunit; namespace SharpEmu.Libs.Tests.Kernel; [CollectionDefinition(KernelMemoryCompatStateCollection.Name, DisableParallelization = true)] public sealed class KernelMemoryCompatStateCollection { public const string Name = "KernelMemoryCompatState"; } [Collection(KernelMemoryCompatStateCollection.Name)] public sealed class KernelMemoryCompatExportsTests { private const ulong GuestMemoryBase = 0x1_0000_0000; private const ulong AllocationOutAddress = GuestMemoryBase + 0x100; private const ulong SpanStartOutAddress = GuestMemoryBase + 0x108; private const ulong SpanSizeOutAddress = GuestMemoryBase + 0x110; [Theory] [InlineData(false)] [InlineData(true)] public void UppercaseStringConversion_ConsumesWideArgumentBetweenNarrowStrings(bool useVaList) { var memory = new FakeCpuMemory(GuestMemoryBase, 0x2000); var context = new CpuContext(memory, Generation.Gen5); memory.WriteCString(GuestMemoryBase + 0x100, "assets"); Assert.True(memory.TryWrite(GuestMemoryBase + 0x200, Encoding.Unicode.GetBytes("armor\0"))); memory.WriteCString(GuestMemoryBase + 0x300, "body"); const string format = "%s/%S_%s.bin"; string rendered; if (useVaList) { var arguments = GuestMemoryBase + 0x400; var savedRegisters = GuestMemoryBase + 0x500; Assert.True(context.TryWriteUInt64(arguments, 48UL << 32)); Assert.True(context.TryWriteUInt64(arguments + 8, GuestMemoryBase + 0x600)); Assert.True(context.TryWriteUInt64(arguments + 16, savedRegisters)); Assert.True(context.TryWriteUInt64(savedRegisters, GuestMemoryBase + 0x100)); Assert.True(context.TryWriteUInt64(savedRegisters + 8, GuestMemoryBase + 0x200)); Assert.True(context.TryWriteUInt64(savedRegisters + 16, GuestMemoryBase + 0x300)); Assert.True(KernelMemoryCompatExports.TryFormatStringFromVaList(context, format, arguments, out rendered)); } else { context[CpuRegister.Rcx] = GuestMemoryBase + 0x100; context[CpuRegister.R8] = GuestMemoryBase + 0x200; context[CpuRegister.R9] = GuestMemoryBase + 0x300; rendered = KernelMemoryCompatExports.FormatStringFromVarArgs(context, format, 3); } Assert.Equal("assets/armor_body.bin", rendered); } [Theory] [InlineData("%8.3S", " arm")] [InlineData("%-8.3S", "arm ")] public void UppercaseStringConversion_UsesExistingWideStringFormatting(string format, string expected) { var memory = new FakeCpuMemory(GuestMemoryBase, 0x1000); var context = new CpuContext(memory, Generation.Gen5); Assert.True(memory.TryWrite(GuestMemoryBase + 0x100, Encoding.Unicode.GetBytes("armor\0"))); context[CpuRegister.Rcx] = GuestMemoryBase + 0x100; Assert.Equal(expected, KernelMemoryCompatExports.FormatStringFromVarArgs(context, format, 3)); } [Theory] [InlineData("a/b/c.xml", '/', 1, 3)] [InlineData("a/b/c.xml", '.', 5, 5)] [InlineData("a/b/c.xml", 'z', -1, -1)] [InlineData("a/b/c.xml", '\0', 9, 9)] [InlineData("", 'a', -1, -1)] public void StrchrStrrchr_MatchNativeLibc(string text, char needle, int firstIndex, int lastIndex) { const ulong address = GuestMemoryBase + 0x100; var memory = new FakeCpuMemory(GuestMemoryBase, 0x1000); memory.WriteCString(address, text); Assert.Equal(ExpectedMatch(address, firstIndex), ScanString(memory, address, needle, last: false)); Assert.Equal(ExpectedMatch(address, lastIndex), ScanString(memory, address, needle, last: true)); } [Fact] public void StrchrStrrchr_ScanAcrossPagesAndPastChunkSize() { // Starts just before a page boundary and runs well past one 4 KiB chunk. const ulong address = GuestMemoryBase + 0xFF0; var text = "<" + new string('x', 6000) + ">" + new string('y', 100) + ">"; var memory = new FakeCpuMemory(GuestMemoryBase, 0x3000); memory.WriteCString(address, text); Assert.Equal(address + 6001, ScanString(memory, address, '>', last: false)); Assert.Equal(address + (ulong)text.Length - 1, ScanString(memory, address, '>', last: true)); Assert.Equal(address + (ulong)text.Length, ScanString(memory, address, '\0', last: false)); } [Fact] public void StrchrStrrchr_StringEndingAtEndOfMappedMemory() { // The first chunk would read past the mapping, so the byte-wise fallback // must still find the terminator that lies inside it. var memory = new FakeCpuMemory(GuestMemoryBase, 0x1000); const ulong address = GuestMemoryBase + 0x1000 - 6; memory.WriteCString(address, "a/b/c"); Assert.Equal(address + 1, ScanString(memory, address, '/', last: false)); Assert.Equal(address + 3, ScanString(memory, address, '/', last: true)); } [Fact] public void StrchrStrrchr_UnterminatedStringAtEndOfMemoryFaults() { var memory = new FakeCpuMemory(GuestMemoryBase, 0x1000); const ulong address = GuestMemoryBase + 0x1000 - 4; Assert.True(memory.TryWrite(address, "abcd"u8)); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = address; context[CpuRegister.Rsi] = 'z'; Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT, KernelMemoryCompatExports.Strchr(context)); Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT, KernelMemoryCompatExports.Strrchr(context)); } [Fact] public void StrchrStrrchr_ScanMappedMemoryInPlace() { using var memory = new PhysicalVirtualMemory(); Assert.True(memory.TryAllocateGuestMemory(0x4000, 0x1000, out var baseAddress)); // Crosses a page boundary and runs past one 4 KiB page. var address = baseAddress + 0xFF0; var text = "<" + new string('x', 6000) + ">" + new string('y', 100) + ">"; Assert.True(memory.TryWrite(address, Encoding.ASCII.GetBytes(text + "\0"))); Assert.True(memory.TryScanCString(address, (byte)'>', findLast: false, 1_048_576, out var first)); Assert.Equal(address + 6001, first); Assert.True(memory.TryScanCString(address, (byte)'>', findLast: true, 1_048_576, out var last)); Assert.Equal(address + (ulong)text.Length - 1, last); Assert.True(memory.TryScanCString(address, 0, findLast: true, 1_048_576, out var terminator)); Assert.Equal(address + (ulong)text.Length, terminator); Assert.True(memory.TryScanCString(address, (byte)'z', findLast: false, 1_048_576, out var missing)); Assert.Equal(0UL, missing); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = address; context[CpuRegister.Rsi] = '>'; Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, KernelMemoryCompatExports.Strrchr(context)); Assert.Equal(address + (ulong)text.Length - 1, context[CpuRegister.Rax]); } [Fact] public void StrchrStrrchr_UnmappedMemoryIsNotScannedInPlace() { using var memory = new PhysicalVirtualMemory(); Assert.False(memory.TryScanCString(0x7_0000_0000, (byte)'a', findLast: false, 1_048_576, out _)); } private static ulong ExpectedMatch(ulong address, int index) => index < 0 ? 0 : address + (ulong)index; private static ulong ScanString(FakeCpuMemory memory, ulong address, char needle, bool last) { var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = address; context[CpuRegister.Rsi] = needle; var result = last ? KernelMemoryCompatExports.Strrchr(context) : KernelMemoryCompatExports.Strchr(context); Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, result); return context[CpuRegister.Rax]; } [Fact] public void PosixStat_MissingFileReturnsMinusOne() { const ulong memoryBase = 0x1_0000_0000; const ulong pathAddress = memoryBase + 0x100; const ulong statAddress = memoryBase + 0x400; var memory = new FakeCpuMemory(memoryBase, 0x1000); var context = new CpuContext(memory, Generation.Gen5); memory.WriteCString(pathAddress, "/__sharpemu_test_missing__/shader.cache"); context[CpuRegister.Rdi] = pathAddress; context[CpuRegister.Rsi] = statAddress; var result = KernelMemoryCompatExports.PosixStat(context); Assert.Equal(-1, result); Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]); } [Fact] public void UnderscoreOpen_MissingFileUsesPosixFailureAbi() { var memory = new FakeCpuMemory(GuestMemoryBase, 0x1000); var context = new CpuContext(memory, Generation.Gen5) { FsBase = GuestMemoryBase + 0x800 }; memory.WriteCString(GuestMemoryBase + 0x100, "/__sharpemu_test_missing__/resource_level_high.bin"); context[CpuRegister.Rdi] = GuestMemoryBase + 0x100; Assert.Equal(-1, KernelMemoryCompatExports.KernelOpenUnderscore(context)); Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]); Assert.True(context.TryReadUInt32(context.FsBase + 0x40, out var errno)); Assert.Equal(2u, errno); Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND, KernelExports.KernelOpen(context)); } [Theory] [InlineData("/dev/random")] [InlineData("/dev/urandom")] public void PosixRandomDevice_OpenReadFstatAndClose(string path) { var memory = new FakeCpuMemory(GuestMemoryBase, 0x1000); var context = new CpuContext(memory, Generation.Gen5); memory.WriteCString(GuestMemoryBase + 0x100, path); context[CpuRegister.Rdi] = GuestMemoryBase + 0x100; Assert.Equal(0, KernelMemoryCompatExports.KernelOpenUnderscore(context)); var fd = context[CpuRegister.Rax]; Assert.True(fd >= 3); try { context[CpuRegister.Rdi] = fd; context[CpuRegister.Rsi] = GuestMemoryBase + 0x200; context[CpuRegister.Rdx] = 32; Assert.Equal(0, KernelMemoryCompatExports.PosixRead(context)); Assert.Equal(32UL, context[CpuRegister.Rax]); var randomBytes = new byte[32]; Assert.True(memory.TryRead(GuestMemoryBase + 0x200, randomBytes)); Assert.Contains(randomBytes, value => value != 0); context[CpuRegister.Rsi] = GuestMemoryBase + 0x400; Assert.Equal(0, KernelMemoryCompatExports.PosixFstat(context)); } finally { context[CpuRegister.Rdi] = fd; Assert.Equal(0, KernelMemoryCompatExports.PosixClose(context)); } context[CpuRegister.Rsi] = GuestMemoryBase + 0x200; context[CpuRegister.Rdx] = 1; Assert.Equal(-1, KernelMemoryCompatExports.PosixRead(context)); } [Fact] public void PosixOpen_MissingFileReturnsMinusOne() { const ulong memoryBase = 0x1_0000_0000; const ulong pathAddress = memoryBase + 0x100; var memory = new FakeCpuMemory(memoryBase, 0x1000); var context = new CpuContext(memory, Generation.Gen5); memory.WriteCString(pathAddress, "/__sharpemu_test_missing__/il2cpp.usym"); context[CpuRegister.Rdi] = pathAddress; context[CpuRegister.Rsi] = 0; // O_RDONLY var result = KernelMemoryCompatExports.PosixOpen(context); // A libc open() failure must be -1, not the raw 0x8002xxxx sentinel the // guest would otherwise store as a valid fd and later dereference. Assert.Equal(-1, result); Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]); } [Fact] public void KernelMkdir_GuestRootReturnsAlreadyExists() { const ulong memoryBase = 0x1_0000_0000; const ulong pathAddress = memoryBase + 0x100; var memory = new FakeCpuMemory(memoryBase, 0x1000); var context = new CpuContext(memory, Generation.Gen5); memory.WriteCString(pathAddress, "/"); context[CpuRegister.Rdi] = pathAddress; var result = KernelMemoryCompatExports.KernelMkdir(context); Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS, result); } [Fact] public void PosixFstat_BadDescriptorReturnsMinusOne() { const ulong memoryBase = 0x1_0000_0000; const ulong statAddress = memoryBase + 0x400; var memory = new FakeCpuMemory(memoryBase, 0x1000); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = 0x80020002; // the not-found sentinel misused as an fd context[CpuRegister.Rsi] = statAddress; var result = KernelMemoryCompatExports.PosixFstat(context); Assert.Equal(-1, result); Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]); } [Fact] public void PosixClose_BadDescriptorReturnsMinusOne() { const ulong memoryBase = 0x1_0000_0000; var memory = new FakeCpuMemory(memoryBase, 0x1000); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = 0x80020002; // never-opened / sentinel fd var result = KernelMemoryCompatExports.PosixClose(context); Assert.Equal(-1, result); Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]); } [Fact] public void PosixRead_BadDescriptorReturnsMinusOne() { 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; // never-opened / sentinel fd context[CpuRegister.Rsi] = bufferAddress; context[CpuRegister.Rdx] = 0x40; var result = KernelMemoryCompatExports.PosixRead(context); Assert.Equal(-1, result); Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]); } [Fact] public void PosixWrite_BadDescriptorReturnsMinusOne() { const ulong memoryBase = 0x1_0000_0000; const ulong bufferAddress = memoryBase + 0x200; var memory = new FakeCpuMemory(memoryBase, 0x1000); var context = new CpuContext(memory, Generation.Gen5); memory.WriteCString(bufferAddress, "payload"); context[CpuRegister.Rdi] = 0x80020002; // never-opened / sentinel fd context[CpuRegister.Rsi] = bufferAddress; context[CpuRegister.Rdx] = 0x7; var result = KernelMemoryCompatExports.PosixWrite(context); Assert.Equal(-1, result); Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]); } [Fact] public void Sprintf_ReadsVariadicDoubleFromXmmRegister() { const ulong memoryBase = 0x1_0000_0000; const ulong destinationAddress = memoryBase + 0x100; const ulong formatAddress = memoryBase + 0x200; var memory = new FakeCpuMemory(memoryBase, 0x1000); var context = new CpuContext(memory, Generation.Gen5); memory.WriteCString(formatAddress, "%.4f"); context[CpuRegister.Rdi] = destinationAddress; context[CpuRegister.Rsi] = formatAddress; context.SetXmmRegister( 0, unchecked((ulong)BitConverter.DoubleToInt64Bits(0.5576)), 0); var previousCulture = CultureInfo.CurrentCulture; try { CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("es-ES"); var result = KernelMemoryCompatExports.Sprintf(context); Assert.Equal(0, result); Assert.Equal(6UL, context[CpuRegister.Rax]); Span output = stackalloc byte[7]; Assert.True(memory.TryRead(destinationAddress, output)); Assert.Equal("0.5576\0", Encoding.UTF8.GetString(output)); } finally { CultureInfo.CurrentCulture = previousCulture; } } [Fact] public void AvailableDirectMemorySize_FragmentedRangeReturnsLargestAlignedSpan() { const ulong firstAllocationStart = 0x0020_0000; const ulong firstAllocationLength = 0x0020_0000; const ulong secondAllocationStart = 0x00C0_0000; const ulong secondAllocationLength = 0x0040_0000; var context = new CpuContext(new FakeCpuMemory(GuestMemoryBase, 0x1000), Generation.Gen5); try { AllocateDirectMemory(context, firstAllocationStart, firstAllocationLength); AllocateDirectMemory(context, secondAllocationStart, secondAllocationLength); QueryAvailableDirectMemory(context, 0, 0x0100_0000, 0x4000); Assert.True(context.TryReadUInt64(SpanStartOutAddress, out var spanStart)); Assert.True(context.TryReadUInt64(SpanSizeOutAddress, out var spanSize)); Assert.Equal(0x0040_0000UL, spanStart); Assert.Equal(0x0080_0000UL, spanSize); } finally { ReleaseDirectMemory(context, firstAllocationStart, firstAllocationLength); ReleaseDirectMemory(context, secondAllocationStart, secondAllocationLength); } } [Fact] public void AvailableDirectMemorySize_AppliesAlignmentBeforeComparingSpans() { const ulong allocationStart = 0x0070_0000; const ulong allocationLength = 0x0010_0000; var context = new CpuContext(new FakeCpuMemory(GuestMemoryBase, 0x1000), Generation.Gen5); try { AllocateDirectMemory(context, allocationStart, allocationLength); QueryAvailableDirectMemory(context, 0x0010_0000, 0x00C0_0000, 0x0040_0000); Assert.True(context.TryReadUInt64(SpanStartOutAddress, out var spanStart)); Assert.True(context.TryReadUInt64(SpanSizeOutAddress, out var spanSize)); Assert.Equal(0x0080_0000UL, spanStart); Assert.Equal(0x0040_0000UL, spanSize); } finally { ReleaseDirectMemory(context, allocationStart, allocationLength); } } [Fact] public void AvailableDirectMemorySize_FullRangeClearsOutputsAndReportsNoSpace() { const ulong allocationStart = 0; const ulong allocationLength = 0x0040_0000; var context = new CpuContext(new FakeCpuMemory(GuestMemoryBase, 0x1000), Generation.Gen5); try { AllocateDirectMemory(context, allocationStart, allocationLength); Assert.True(context.TryWriteUInt64(SpanStartOutAddress, 0xDEAD_BEEF)); Assert.True(context.TryWriteUInt64(SpanSizeOutAddress, 0xDEAD_BEEF)); context[CpuRegister.Rdi] = allocationStart; context[CpuRegister.Rsi] = allocationStart + allocationLength; context[CpuRegister.Rdx] = 0; context[CpuRegister.Rcx] = SpanStartOutAddress; context[CpuRegister.R8] = SpanSizeOutAddress; Assert.Equal(unchecked((int)0x8002000C), KernelMemoryCompatExports.KernelAvailableDirectMemorySize(context)); Assert.True(context.TryReadUInt64(SpanStartOutAddress, out var spanStart)); Assert.True(context.TryReadUInt64(SpanSizeOutAddress, out var spanSize)); Assert.Equal(0UL, spanStart); Assert.Equal(0UL, spanSize); } finally { ReleaseDirectMemory(context, allocationStart, allocationLength); } } [Fact] public void AvailableDirectMemorySize_EmptySearchRangeReportsNoSpace() { var context = new CpuContext(new FakeCpuMemory(GuestMemoryBase, 0x1000), Generation.Gen5); context[CpuRegister.Rdi] = 0x0010_0000; context[CpuRegister.Rsi] = 0x0010_0000; context[CpuRegister.Rdx] = 0; context[CpuRegister.Rcx] = SpanStartOutAddress; context[CpuRegister.R8] = SpanSizeOutAddress; Assert.Equal(unchecked((int)0x8002000C), KernelMemoryCompatExports.KernelAvailableDirectMemorySize(context)); } private static void AllocateDirectMemory(CpuContext context, ulong start, ulong length, ulong outputAddress = AllocationOutAddress) { context[CpuRegister.Rdi] = start; context[CpuRegister.Rsi] = start + length; context[CpuRegister.Rdx] = length; context[CpuRegister.Rcx] = 0x4000; context[CpuRegister.R8] = 0; context[CpuRegister.R9] = outputAddress; Assert.Equal(0, KernelMemoryCompatExports.KernelAllocateDirectMemory(context)); Assert.True(context.TryReadUInt64(outputAddress, out var allocatedAddress)); Assert.Equal(start, allocatedAddress); } private static void QueryAvailableDirectMemory( CpuContext context, ulong searchStart, ulong searchEnd, ulong alignment) { context[CpuRegister.Rdi] = searchStart; context[CpuRegister.Rsi] = searchEnd; context[CpuRegister.Rdx] = alignment; context[CpuRegister.Rcx] = SpanStartOutAddress; context[CpuRegister.R8] = SpanSizeOutAddress; Assert.Equal(0, KernelMemoryCompatExports.KernelAvailableDirectMemorySize(context)); } private static void ReleaseDirectMemory(CpuContext context, ulong start, ulong length) { context[CpuRegister.Rdi] = start; context[CpuRegister.Rsi] = length; Assert.Equal(0, KernelMemoryCompatExports.KernelReleaseDirectMemory(context)); } [Fact] public void MapNamedFlexibleMemory_NullInOutPointerReturnsInvalidArgument() { var memory = new FakeCpuMemory(0x1_0000_0000, 0x1000); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = 0; context[CpuRegister.Rsi] = 0x1000; context[CpuRegister.Rdx] = 0x03; // CPU read|write context[CpuRegister.Rcx] = 0; var result = KernelMemoryCompatExports.KernelMapNamedFlexibleMemory(context); Assert.Equal(unchecked((int)0x80020016), result); } [Fact] public void MapNamedFlexibleMemory_ZeroLengthReturnsInvalidArgument() { const ulong memoryBase = 0x1_0000_0000; const ulong inOutAddress = memoryBase + 0x100; var memory = new FakeCpuMemory(memoryBase, 0x1000); var context = new CpuContext(memory, Generation.Gen5); memory.TryWrite(inOutAddress, BitConverter.GetBytes(0UL)); context[CpuRegister.Rdi] = inOutAddress; context[CpuRegister.Rsi] = 0; context[CpuRegister.Rdx] = 0x03; context[CpuRegister.Rcx] = 0; var result = KernelMemoryCompatExports.KernelMapNamedFlexibleMemory(context); Assert.Equal(unchecked((int)0x80020016), result); } [Fact] public void MapNamedFlexibleMemory_UnreadableInOutPointerReturnsMemoryFault() { // The in-out pointer points outside the FakeCpuMemory backing store, so // the first TryReadUInt64 must fail before any reservation is attempted. const ulong memoryBase = 0x1_0000_0000; const ulong unreachableInOut = memoryBase + 0x10_0000; var memory = new FakeCpuMemory(memoryBase, 0x1000); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = unreachableInOut; context[CpuRegister.Rsi] = 0x4000; context[CpuRegister.Rdx] = 0x03; context[CpuRegister.Rcx] = 0; var result = KernelMemoryCompatExports.KernelMapNamedFlexibleMemory(context); Assert.Equal(unchecked((int)0x8002000E), result); } [Fact] public void VirtualQuery_PreservesReservationPastFixedCommitAtSameBase() { const ulong reservedLength = 0x1_0000; const ulong committedLength = 0x4000; using var test = new BackedKernelMemory(); var context = test.Context; var infoAddress = test.Output + 0x100; var memoryBase = test.Reserve(reservedLength); test.Allocate(0, committedLength); Assert.Equal(memoryBase, test.Map(0, committedLength, memoryBase)); context[CpuRegister.Rdi] = memoryBase + 0x8000; context[CpuRegister.Rsi] = 0; context[CpuRegister.Rdx] = infoAddress; context[CpuRegister.Rcx] = 0x48; Assert.Equal(0, KernelMemoryCompatExports.KernelVirtualQuery(context)); Assert.True(context.TryReadUInt64(infoAddress, out var regionStart)); Assert.True(context.TryReadUInt64(infoAddress + 8, out var regionEnd)); Assert.Equal(memoryBase + committedLength, regionStart); Assert.Equal(memoryBase + reservedLength, regionEnd); } [Fact] public void Mprotect_ZeroAddressReturnsInvalidArgument() { var memory = new FakeCpuMemory(0x1_0000_0000, 0x1000); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = 0; context[CpuRegister.Rsi] = 0x4000; context[CpuRegister.Rdx] = 0x03; var result = KernelMemoryCompatExports.KernelMprotect(context); Assert.Equal(unchecked((int)0x80020016), result); } [Fact] public void Mprotect_ZeroLengthReturnsInvalidArgument() { const ulong memoryBase = 0x1_0000_0000; var memory = new FakeCpuMemory(memoryBase, 0x1000); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = memoryBase; context[CpuRegister.Rsi] = 0; context[CpuRegister.Rdx] = 0x03; var result = KernelMemoryCompatExports.KernelMprotect(context); Assert.Equal(unchecked((int)0x80020016), result); } [Fact] public void Mprotect_UnmappedRangeReturnsNotFound() { const ulong unmappedAddress = 0x2_0000_0000; var memory = new FakeCpuMemory(0x1_0000_0000, 0x1000); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = unmappedAddress; context[CpuRegister.Rsi] = 0x4000; context[CpuRegister.Rdx] = 0x03; var result = KernelMemoryCompatExports.KernelMprotect(context); Assert.Equal(unchecked((int)0x80020002), result); } [Fact] public void Munmap_ZeroAddressReturnsAccessDenied() { var memory = new FakeCpuMemory(0x1_0000_0000, 0x1000); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = 0; context[CpuRegister.Rsi] = 0x4000; var result = KernelMemoryCompatExports.KernelMunmap(context); Assert.Equal(unchecked((int)0x8002000D), result); } [Fact] public void Munmap_OverflowRangeReturnsInvalidArgument() { // address + length would overflow; KernelMunmap guards this explicitly // before touching any region accounting. var memory = new FakeCpuMemory(0x1_0000_0000, 0x1000); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = ulong.MaxValue - 0x10; context[CpuRegister.Rsi] = 0x20; var result = KernelMemoryCompatExports.KernelMunmap(context); Assert.Equal(unchecked((int)0x80020016), result); } [Fact] public void Munmap_UnmappedRangeReturnsAccessDenied() { const ulong unmappedAddress = 0x2_0000_0000; var memory = new FakeCpuMemory(0x1_0000_0000, 0x1000); var context = new CpuContext(memory, Generation.Gen5); context[CpuRegister.Rdi] = unmappedAddress; context[CpuRegister.Rsi] = 0x4000; var result = KernelMemoryCompatExports.KernelMunmap(context); Assert.Equal(unchecked((int)0x8002000D), result); } [Fact] public void MapDirectMemory_RegistersAndMunmapUnregistersTheGpuSpan() { const ulong directStart = 0x0300_0000; const ulong length = 0x0001_0000; const ulong requestedAddress = 0x2_0000_0000; using var memory = new PhysicalVirtualMemory(viewHost: HostViewMemory.Create(), backingBytes: 128UL * 1024 * 1024); var outputAddress = memory.AllocateAt(0, 0x4000, false); var context = new CpuContext(memory, Generation.Gen5); var gpuMemory = new GuestGpuMemory(new RecordingAddressSpace()); GuestGpuMemoryHook.Attach(gpuMemory); var mappedAddress = 0UL; try { AllocateDirectMemory(context, directStart, length, outputAddress); Assert.True(context.TryWriteUInt64(outputAddress, requestedAddress)); context[CpuRegister.Rdi] = outputAddress; context[CpuRegister.Rsi] = length; context[CpuRegister.Rdx] = 0x33; // CPU read|write, GPU read|write context[CpuRegister.Rcx] = 0; context[CpuRegister.R8] = directStart; context[CpuRegister.R9] = 0; Assert.Equal(0, KernelMemoryCompatExports.KernelMapDirectMemory(context)); Assert.True(context.TryReadUInt64(outputAddress, out mappedAddress)); Assert.True(gpuMemory.Covers(mappedAddress, length)); context[CpuRegister.Rdi] = mappedAddress; context[CpuRegister.Rsi] = length; Assert.Equal(0, KernelMemoryCompatExports.KernelMunmap(context)); Assert.False(gpuMemory.Covers(mappedAddress, length)); mappedAddress = 0; } finally { if (mappedAddress != 0) { context[CpuRegister.Rdi] = mappedAddress; context[CpuRegister.Rsi] = length; _ = KernelMemoryCompatExports.KernelMunmap(context); } ReleaseDirectMemory(context, directStart, length); GuestGpuMemoryHook.Attach(null); gpuMemory.Dispose(); KernelMemoryCompatExports.ResetBackingMappings(memory); } } [Fact] public void ReleaseDirectMemory_FailedReleaseKeepsTheMappedGpuSpan() { const ulong directStart = 0x0400_0000; const ulong length = 0x0001_0000; const ulong unallocatedStart = 0x0500_0000; const ulong requestedAddress = 0x2_1000_0000; using var memory = new PhysicalVirtualMemory(viewHost: HostViewMemory.Create(), backingBytes: 128UL * 1024 * 1024); var outputAddress = memory.AllocateAt(0, 0x4000, false); var context = new CpuContext(memory, Generation.Gen5); var gpuMemory = new GuestGpuMemory(new RecordingAddressSpace()); GuestGpuMemoryHook.Attach(gpuMemory); var mappedAddress = 0UL; try { AllocateDirectMemory(context, directStart, length, outputAddress); Assert.True(context.TryWriteUInt64(outputAddress, requestedAddress)); context[CpuRegister.Rdi] = outputAddress; context[CpuRegister.Rsi] = length; context[CpuRegister.Rdx] = 0x33; // CPU read|write, GPU read|write context[CpuRegister.Rcx] = 0; context[CpuRegister.R8] = directStart; context[CpuRegister.R9] = 0; Assert.Equal(0, KernelMemoryCompatExports.KernelMapDirectMemory(context)); Assert.True(context.TryReadUInt64(outputAddress, out mappedAddress)); Assert.True(gpuMemory.Covers(mappedAddress, length)); context[CpuRegister.Rdi] = unallocatedStart; context[CpuRegister.Rsi] = length; Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND, KernelMemoryCompatExports.KernelCheckedReleaseDirectMemory(context)); Assert.True(gpuMemory.Covers(mappedAddress, length)); // Overlaps the second half of the allocation and runs past its end. context[CpuRegister.Rdi] = directStart + length / 2; context[CpuRegister.Rsi] = length; Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND, KernelMemoryCompatExports.KernelCheckedReleaseDirectMemory(context)); Assert.True(gpuMemory.Covers(mappedAddress, length)); context[CpuRegister.Rdi] = directStart + length / 2; context[CpuRegister.Rsi] = length; Assert.Equal(0, KernelMemoryCompatExports.KernelReleaseDirectMemory(context)); Assert.True(gpuMemory.Covers(mappedAddress, length)); ReleaseDirectMemory(context, directStart, length); Assert.False(gpuMemory.Covers(mappedAddress, length)); } finally { if (mappedAddress != 0) { context[CpuRegister.Rdi] = mappedAddress; context[CpuRegister.Rsi] = length; _ = KernelMemoryCompatExports.KernelMunmap(context); } GuestGpuMemoryHook.Attach(null); gpuMemory.Dispose(); KernelMemoryCompatExports.ResetBackingMappings(memory); } } }