Files
sharpemu/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs
ParantezTech 4d73f469bc initial commit
2026-03-11 15:48:28 +03:00

2420 lines
78 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
using System.Text;
using System.Threading;
using System.Reflection;
using System.Runtime.InteropServices;
namespace SharpEmu.Libs.Kernel;
public static class KernelMemoryCompatExports
{
private const int MaxGuestStringLength = 4096;
private const int MemsetChunkSize = 16 * 1024;
private const int TlsModuleBlockSize = 0x10000;
private const int O_WRONLY = 0x1;
private const int O_RDWR = 0x2;
private const int O_APPEND = 0x8;
private const int O_CREAT = 0x0200;
private const int O_TRUNC = 0x0400;
private const int O_DIRECTORY = 0x00020000;
private const int SeekSet = 0;
private const int SeekCur = 1;
private const int SeekEnd = 2;
private const ulong DirectMemorySizeBytes = 16384UL * 1024 * 1024;
private const ulong FlexibleMemorySizeBytes = 448UL * 1024 * 1024;
private const uint MemCommit = 0x1000;
private const uint PageNoAccess = 0x01;
private const uint PageReadOnly = 0x02;
private const uint PageReadWrite = 0x04;
private const uint PageWriteCopy = 0x08;
private const uint PageExecuteRead = 0x20;
private const uint PageExecuteReadWrite = 0x40;
private const uint PageExecuteWriteCopy = 0x80;
private const uint PageGuard = 0x100;
private static readonly object _fdGate = new();
private static readonly Dictionary<int, FileStream> _openFiles = new();
private static readonly Dictionary<int, OpenDirectory> _openDirectories = new();
private static readonly object _memoryGate = new();
private static readonly object _tlsGate = new();
private static readonly Dictionary<ulong, DirectAllocation> _directAllocations = new();
private static readonly Dictionary<ulong, MappedRegion> _mappedRegions = new();
private static readonly Dictionary<ulong, ulong> _tlsModuleBlocks = new();
private static long _nextFileDescriptor = 2;
private static ulong _nextPhysicalAddress;
private static ulong _nextVirtualAddress;
private static ulong _allocatedFlexibleBytes;
private static ulong _threadAtexitCountCallback;
private static ulong _threadAtexitReportCallback;
private static ulong _threadDtorsCallback;
private static int _nullMemsetRecoveryCount;
private static int _nonCanonicalMemsetRecoveryCount;
private static int _inaccessibleMemsetRecoveryCount;
private static int _hostMemoryWriteFallbackCount;
private static int _hostMemoryReadFallbackCount;
[StructLayout(LayoutKind.Sequential)]
private struct MemoryBasicInformation
{
public nint BaseAddress;
public nint AllocationBase;
public uint AllocationProtect;
public nuint RegionSize;
public uint State;
public uint Protect;
public uint Type;
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern nuint VirtualQuery(nint lpAddress, out MemoryBasicInformation lpBuffer, nuint dwLength);
private sealed class OpenDirectory
{
public required string Path { get; init; }
public required string[] Entries { get; init; }
public int NextIndex { get; set; }
}
private readonly record struct DirectAllocation(ulong Start, ulong Length, int MemoryType);
private readonly record struct MappedRegion(ulong Address, ulong Length, int Protection, bool IsFlexible, ulong DirectStart);
[SysAbiExport(
Nid = "8zTFvBIAIN8",
ExportName = "memset",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Memset(CpuContext ctx)
{
var destination = ctx[CpuRegister.Rdi];
var value = (byte)(ctx[CpuRegister.Rsi] & 0xFF);
var length = ctx[CpuRegister.Rdx];
if (length == 0)
{
ctx[CpuRegister.Rax] = destination;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (destination == 0)
{
if (length <= 0x20)
{
var recoveryIndex = Interlocked.Increment(ref _nullMemsetRecoveryCount);
if (recoveryIndex <= 8)
{
Console.Error.WriteLine(
$"[LOADER][WARNING] memset null-dst recovery#{recoveryIndex}: rip=0x{ctx.Rip:X16} len=0x{length:X} val=0x{value:X2}");
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
const ulong CanonicalUserUpper = 0x0000800000000000UL;
if (destination >= CanonicalUserUpper && length <= 0x40)
{
var recoveryIndex = Interlocked.Increment(ref _nonCanonicalMemsetRecoveryCount);
if (recoveryIndex <= 8)
{
Console.Error.WriteLine(
$"[LOADER][WARNING] memset non-canonical-dst recovery#{recoveryIndex}: rip=0x{ctx.Rip:X16} dst=0x{destination:X16} len=0x{length:X} val=0x{value:X2}");
}
ctx[CpuRegister.Rax] = destination;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
const ulong MaxSane = 512UL * 1024 * 1024;
if (destination < 0x1000 || destination >= CanonicalUserUpper || length > MaxSane)
{
Console.WriteLine("!!! CRITICAL: Bad Memset Call !!!");
Console.WriteLine($"Called from RIP: 0x{ctx.Rip:X}");
Console.WriteLine($"dst=0x{destination:X} val=0x{value:X2} len=0x{length:X}");
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var chunk = new byte[MemsetChunkSize];
Array.Fill(chunk, value);
var remaining = length;
var cursor = destination;
while (remaining > 0)
{
var take = (int)Math.Min((ulong)chunk.Length, remaining);
if (!TryWriteCompat(ctx, cursor, chunk.AsSpan(0, take)))
{
if (length <= 0x40)
{
var recoveryIndex = Interlocked.Increment(ref _inaccessibleMemsetRecoveryCount);
if (recoveryIndex <= 8)
{
Console.Error.WriteLine(
$"[LOADER][WARNING] memset inaccessible-dst recovery#{recoveryIndex}: rip=0x{ctx.Rip:X16} dst=0x{destination:X16} len=0x{length:X} val=0x{value:X2}");
}
ctx[CpuRegister.Rax] = destination;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
cursor += (ulong)take;
remaining -= (ulong)take;
}
ctx[CpuRegister.Rax] = destination;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "j4ViWNHEgww",
ExportName = "strlen",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Strlen(CpuContext ctx)
{
if (!TryReadCString(ctx, ctx[CpuRegister.Rdi], 1_048_576, out var bytes))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = unchecked((ulong)bytes.Length);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "5jNubw4vlAA",
ExportName = "strnlen",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Strnlen(CpuContext ctx)
{
var maxLength = ctx[CpuRegister.Rsi];
if (!TryReadCString(ctx, ctx[CpuRegister.Rdi], maxLength, out var bytes))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = unchecked((ulong)bytes.Length);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Ovb2dSJOAuE",
ExportName = "strcmp",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Strcmp(CpuContext ctx)
{
var left = ctx[CpuRegister.Rdi];
var right = ctx[CpuRegister.Rsi];
if (!TryCompareStrings(ctx, left, right, limit: ulong.MaxValue, out var compare))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = unchecked((ulong)compare);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "aesyjrHVWy4",
ExportName = "strncmp",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Strncmp(CpuContext ctx)
{
var left = ctx[CpuRegister.Rdi];
var right = ctx[CpuRegister.Rsi];
var limit = ctx[CpuRegister.Rdx];
if (!TryCompareStrings(ctx, left, right, limit, out var compare))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = unchecked((ulong)compare);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "eLdDw6l0-bU",
ExportName = "snprintf",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Snprintf(CpuContext ctx)
{
return SnprintfCore(ctx);
}
[SysAbiExport(
Nid = "Q2V+iqvjgC0",
ExportName = "vsnprintf",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Vsnprintf(CpuContext ctx)
{
return VsnprintfCore(ctx);
}
[SysAbiExport(
Nid = "GMpvxPFW924",
ExportName = "vprintf",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Vprintf(CpuContext ctx)
{
var formatAddress = ctx[CpuRegister.Rdi];
var vaListAddress = ctx[CpuRegister.Rsi];
if (!TryReadCString(ctx, formatAddress, 1_048_576, out var formatBytes))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var format = Encoding.UTF8.GetString(formatBytes);
string rendered;
if (!TryCreateVaListCursor(ctx, vaListAddress, out var vaCursor))
{
rendered = format;
}
else
{
ulong NextGpArg() => vaCursor.NextGpArg();
double NextFloatArg() => vaCursor.NextFloatArg();
rendered = FormatString(ctx, format, NextGpArg, NextFloatArg);
vaCursor.Commit();
}
Console.Write(rendered);
ctx[CpuRegister.Rax] = unchecked((ulong)Encoding.UTF8.GetByteCount(rendered));
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "kiZSXIWd9vg",
ExportName = "strcpy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Strcpy(CpuContext ctx)
{
var destination = ctx[CpuRegister.Rdi];
var source = ctx[CpuRegister.Rsi];
if (!TryReadCString(ctx, source, 1_048_576, out var bytes))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var payload = new byte[bytes.Length + 1];
bytes.CopyTo(payload.AsSpan());
if (!ctx.Memory.TryWrite(destination, payload))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = destination;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "6sJWiWSRuqk",
ExportName = "strncpy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Strncpy(CpuContext ctx)
{
var destination = ctx[CpuRegister.Rdi];
var source = ctx[CpuRegister.Rsi];
var count = (int)Math.Min(ctx[CpuRegister.Rdx], int.MaxValue);
if (count < 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var payload = new byte[count];
Span<byte> one = stackalloc byte[1];
var copied = 0;
while (copied < count)
{
if (!ctx.Memory.TryRead(source + (ulong)copied, one))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
payload[copied] = one[0];
copied++;
if (one[0] == 0)
{
break;
}
}
if (!ctx.Memory.TryWrite(destination, payload))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = destination;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Q3VBxCXhUHs",
ExportName = "memcpy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Memcpy(CpuContext ctx)
{
var destination = ctx[CpuRegister.Rdi];
var source = ctx[CpuRegister.Rsi];
var count = (int)Math.Min(ctx[CpuRegister.Rdx], int.MaxValue);
if (count < 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var payload = GC.AllocateUninitializedArray<byte>(count);
if (count > 0 && (!ctx.Memory.TryRead(source, payload) || !ctx.Memory.TryWrite(destination, payload)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = destination;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "+P6FRGH4LfA",
ExportName = "memmove",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Memmove(CpuContext ctx)
{
return Memcpy(ctx);
}
[SysAbiExport(
Nid = "DfivPArhucg",
ExportName = "memcmp",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Memcmp(CpuContext ctx)
{
var left = ctx[CpuRegister.Rdi];
var right = ctx[CpuRegister.Rsi];
var count = (int)Math.Min(ctx[CpuRegister.Rdx], int.MaxValue);
if (count < 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
Span<byte> leftByte = stackalloc byte[1];
Span<byte> rightByte = stackalloc byte[1];
for (var i = 0; i < count; i++)
{
if (!ctx.Memory.TryRead(left + (ulong)i, leftByte) ||
!ctx.Memory.TryRead(right + (ulong)i, rightByte))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var diff = leftByte[0] - rightByte[0];
if (diff != 0)
{
ctx[CpuRegister.Rax] = unchecked((ulong)diff);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "QrZZdJ8XsX0",
ExportName = "fputs",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Fputs(CpuContext ctx)
{
var textAddress = ctx[CpuRegister.Rdi];
var stream = ctx[CpuRegister.Rsi];
if (textAddress == 0)
{
ctx[CpuRegister.Rax] = unchecked((ulong)(-1L));
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryReadNullTerminatedUtf8(ctx, textAddress, MaxGuestStringLength, out var text))
{
ctx[CpuRegister.Rax] = unchecked((ulong)(-1L));
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (stream == 0)
{
Console.Error.Write(text);
Console.Error.Flush();
}
else
{
Console.Out.Write(text);
Console.Out.Flush();
}
ctx[CpuRegister.Rax] = unchecked((ulong)text.Length);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "6c3rCVE-fTU",
ExportName = "_open",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelOpenUnderscore(CpuContext ctx)
{
var pathAddress = ctx[CpuRegister.Rdi];
var flags = unchecked((int)ctx[CpuRegister.Rsi]);
if (!TryReadNullTerminatedUtf8(ctx, pathAddress, MaxGuestStringLength, out var guestPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var hostPath = ResolveGuestPath(guestPath);
var access = ResolveOpenAccess(flags);
var mode = ResolveOpenMode(flags, access);
try
{
var stream = new FileStream(hostPath, mode, access, FileShare.ReadWrite);
if ((flags & O_APPEND) != 0)
{
stream.Seek(0, SeekOrigin.End);
}
var fd = (int)Interlocked.Increment(ref _nextFileDescriptor);
lock (_fdGate)
{
_openFiles[fd] = stream;
}
ctx[CpuRegister.Rax] = unchecked((ulong)fd);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
catch (IOException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
catch (UnauthorizedAccessException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
}
[SysAbiExport(
Nid = "NNtFaKJbPt0",
ExportName = "_close",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelCloseUnderscore(CpuContext ctx)
{
var fd = unchecked((int)ctx[CpuRegister.Rdi]);
if (fd is 0 or 1 or 2)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
FileStream? stream;
lock (_fdGate)
{
if (!_openFiles.Remove(fd, out stream))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
}
stream.Dispose();
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "DRuBt2pvICk",
ExportName = "_read",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelReadUnderscore(CpuContext ctx)
{
var fd = unchecked((int)ctx[CpuRegister.Rdi]);
var bufferAddress = ctx[CpuRegister.Rsi];
var requested = (int)Math.Min(ctx[CpuRegister.Rdx], int.MaxValue);
if (requested < 0 || (requested > 0 && bufferAddress == 0))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (requested == 0 || fd == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
FileStream? stream;
lock (_fdGate)
{
_openFiles.TryGetValue(fd, out stream);
}
if (stream is null)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
var buffer = GC.AllocateUninitializedArray<byte>(requested);
var read = stream.Read(buffer, 0, requested);
if (read > 0 && !ctx.Memory.TryWrite(bufferAddress, buffer.AsSpan(0, read)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = unchecked((ulong)read);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "FxVZqBAA7ks",
ExportName = "_write",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelWriteUnderscore(CpuContext ctx)
{
var fd = unchecked((int)ctx[CpuRegister.Rdi]);
var bufferAddress = ctx[CpuRegister.Rsi];
var requested = (int)Math.Min(ctx[CpuRegister.Rdx], int.MaxValue);
if (requested < 0 || (requested > 0 && bufferAddress == 0))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var payload = requested == 0
? Array.Empty<byte>()
: GC.AllocateUninitializedArray<byte>(requested);
if (requested > 0 && !ctx.Memory.TryRead(bufferAddress, payload))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (fd == 1 || fd == 2)
{
var text = Encoding.UTF8.GetString(payload);
if (fd == 1)
{
Console.Out.Write(text);
Console.Out.Flush();
}
else
{
Console.Error.Write(text);
Console.Error.Flush();
}
ctx[CpuRegister.Rax] = unchecked((ulong)requested);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
FileStream? stream;
lock (_fdGate)
{
_openFiles.TryGetValue(fd, out stream);
}
if (stream is null)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
stream.Write(payload, 0, requested);
stream.Flush();
ctx[CpuRegister.Rax] = unchecked((ulong)requested);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "lLMT9vJAck0",
ExportName = "clock_gettime",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int ClockGettime(CpuContext ctx)
{
var timespecAddress = ctx[CpuRegister.Rsi];
if (timespecAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var now = DateTimeOffset.UtcNow;
var seconds = now.ToUnixTimeSeconds();
var nanoseconds = (now.Ticks % TimeSpan.TicksPerSecond) * 100;
if (!ctx.TryWriteUInt64(timespecAddress, unchecked((ulong)seconds)) ||
!ctx.TryWriteUInt64(timespecAddress + sizeof(long), unchecked((ulong)nanoseconds)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "vNe1w4diLCs",
ExportName = "__tls_get_addr",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int TlsGetAddr(CpuContext ctx)
{
var tlsInfoAddress = ctx[CpuRegister.Rdi];
if (tlsInfoAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!ctx.TryReadUInt64(tlsInfoAddress, out var moduleId) ||
!ctx.TryReadUInt64(tlsInfoAddress + sizeof(ulong), out var offset))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = ResolveTlsAddress(ctx, moduleId, offset);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static ulong ResolveTlsAddress(CpuContext ctx, ulong moduleId, ulong offset)
{
if (ctx.FsBase == 0)
{
return 0;
}
if (moduleId <= 1)
{
return unchecked(ctx.FsBase + offset);
}
var key = (ctx.FsBase << 16) ^ (moduleId & 0xFFFFUL);
ulong moduleBase;
lock (_tlsGate)
{
if (!_tlsModuleBlocks.TryGetValue(key, out moduleBase))
{
var block = Marshal.AllocHGlobal(TlsModuleBlockSize);
Marshal.Copy(new byte[TlsModuleBlockSize], 0, block, TlsModuleBlockSize);
moduleBase = unchecked((ulong)block);
_tlsModuleBlocks[key] = moduleBase;
}
}
return unchecked(moduleBase + offset);
}
[SysAbiExport(
Nid = "pB-yGZ2nQ9o",
ExportName = "_sceKernelSetThreadAtexitCount",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelSetThreadAtexitCount(CpuContext ctx)
{
_threadAtexitCountCallback = ctx[CpuRegister.Rdi];
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "WhCc1w3EhSI",
ExportName = "_sceKernelSetThreadAtexitReport",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelSetThreadAtexitReport(CpuContext ctx)
{
_threadAtexitReportCallback = ctx[CpuRegister.Rdi];
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "rNhWz+lvOMU",
ExportName = "_sceKernelSetThreadDtors",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelSetThreadDtors(CpuContext ctx)
{
_threadDtorsCallback = ctx[CpuRegister.Rdi];
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Tz4RNUCBbGI",
ExportName = "_sceKernelRtldThreadAtexitIncrement",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelRtldThreadAtexitIncrement(CpuContext ctx)
{
return KernelRtldThreadAtexitAdjust(ctx, delta: +1);
}
[SysAbiExport(
Nid = "8OnWXlgQlvo",
ExportName = "_sceKernelRtldThreadAtexitDecrement",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelRtldThreadAtexitDecrement(CpuContext ctx)
{
return KernelRtldThreadAtexitAdjust(ctx, delta: -1);
}
[SysAbiExport(
Nid = "pO96TwzOm5E",
ExportName = "sceKernelGetDirectMemorySize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetDirectMemorySize(CpuContext ctx)
{
ctx[CpuRegister.Rax] = DirectMemorySizeBytes;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "C0f7TJcbfac",
ExportName = "sceKernelAvailableDirectMemorySize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAvailableDirectMemorySize(CpuContext ctx)
{
var arg0 = ctx[CpuRegister.Rdi];
var arg1 = ctx[CpuRegister.Rsi];
var arg2 = ctx[CpuRegister.Rdx];
var arg3 = ctx[CpuRegister.Rcx];
var arg4 = ctx[CpuRegister.R8];
ulong used = 0;
lock (_memoryGate)
{
foreach (var allocation in _directAllocations.Values)
{
used = Math.Min(DirectMemorySizeBytes, used + allocation.Length);
}
}
var totalAvailable = used >= DirectMemorySizeBytes
? 0UL
: DirectMemorySizeBytes - used;
if (arg1 != 0 || arg2 != 0 || arg3 != 0 || arg4 != 0)
{
var searchStartRaw = unchecked((long)arg0);
var searchEndRaw = unchecked((long)arg1);
var alignment = arg2 == 0 ? 0x1000UL : arg2;
var outAddress = arg3;
var outSize = arg4;
if (outAddress == 0 || outSize == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var searchStart = searchStartRaw < 0 ? 0UL : (ulong)searchStartRaw;
var searchEnd = searchEndRaw <= 0
? DirectMemorySizeBytes
: Math.Min((ulong)searchEndRaw, DirectMemorySizeBytes);
if (searchStart >= searchEnd)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var candidate = AlignUp(Math.Max(searchStart, _nextPhysicalAddress), alignment);
if (candidate >= searchEnd)
{
candidate = AlignUp(searchStart, alignment);
if (candidate >= searchEnd)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
}
var rangeAvailable = searchEnd - candidate;
if (!ctx.TryWriteUInt64(outAddress, candidate) || !ctx.TryWriteUInt64(outSize, rangeAvailable))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
var outSizeAddress = arg0;
if (outSizeAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!ctx.TryWriteUInt64(outSizeAddress, totalAvailable))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "aNz11fnnzi4",
ExportName = "sceKernelAvailableFlexibleMemorySize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAvailableFlexibleMemorySize(CpuContext ctx)
{
var outSizeAddress = ctx[CpuRegister.Rdi];
if (outSizeAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
ulong available;
lock (_memoryGate)
{
available = _allocatedFlexibleBytes >= FlexibleMemorySizeBytes
? 0
: FlexibleMemorySizeBytes - _allocatedFlexibleBytes;
}
if (!ctx.TryWriteUInt64(outSizeAddress, available))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "rTXw65xmLIA",
ExportName = "sceKernelAllocateDirectMemory",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAllocateDirectMemory(CpuContext ctx)
{
var searchStartRaw = unchecked((long)ctx[CpuRegister.Rdi]);
var searchEndRaw = unchecked((long)ctx[CpuRegister.Rsi]);
var length = ctx[CpuRegister.Rdx];
var alignment = ctx[CpuRegister.Rcx];
var memoryType = unchecked((int)ctx[CpuRegister.R8]);
var outAddress = ctx[CpuRegister.R9];
if (length == 0 || outAddress == 0)
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
var limit = DirectMemorySizeBytes;
ulong searchStart;
ulong searchEnd;
if (searchEndRaw <= 0)
{
searchEnd = limit;
}
else
{
searchEnd = (ulong)searchEndRaw;
if (searchEnd > limit)
{
searchEnd = limit;
}
}
if (searchStartRaw < 0)
{
searchStart = 0;
}
else
{
searchStart = (ulong)searchStartRaw;
}
if (searchStart >= searchEnd)
{
searchStart = 0;
}
var align = alignment == 0 ? 0x1000UL : alignment;
var alignedStart = AlignUp(searchStart, align);
ulong selectedAddress;
lock (_memoryGate)
{
selectedAddress = AlignUp(Math.Max(alignedStart, _nextPhysicalAddress), align);
if (!TryAdd(selectedAddress, length, out var endAddr) ||
endAddr > searchEnd ||
endAddr > limit)
{
selectedAddress = alignedStart;
if (!TryAdd(selectedAddress, length, out endAddr) ||
endAddr > searchEnd ||
endAddr > limit)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
}
_directAllocations[selectedAddress] = new DirectAllocation(selectedAddress, length, memoryType);
_nextPhysicalAddress = selectedAddress + length;
}
if (!ctx.TryWriteUInt64(outAddress, selectedAddress))
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
static bool TryAdd(ulong a, ulong b, out ulong sum)
{
sum = a + b;
return sum >= a;
}
}
[SysAbiExport(
Nid = "B+vc2AO2Zrc",
ExportName = "sceKernelAllocateMainDirectMemory",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAllocateMainDirectMemory(CpuContext ctx)
{
var length = ctx[CpuRegister.Rdi];
var alignment = ctx[CpuRegister.Rsi];
var memoryType = unchecked((int)ctx[CpuRegister.Rdx]);
var outAddress = ctx[CpuRegister.Rcx];
if (outAddress == 0 || length == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var aligned = AlignUp(_nextPhysicalAddress, alignment == 0 ? 0x1000UL : alignment);
lock (_memoryGate)
{
if (aligned + length > DirectMemorySizeBytes)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
_directAllocations[aligned] = new DirectAllocation(aligned, length, memoryType);
_nextPhysicalAddress = aligned + length;
}
if (!ctx.TryWriteUInt64(outAddress, aligned))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "MBuItvba6z8",
ExportName = "sceKernelReleaseDirectMemory",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelReleaseDirectMemory(CpuContext ctx)
{
var start = ctx[CpuRegister.Rdi];
var length = ctx[CpuRegister.Rsi];
if (length == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_memoryGate)
{
if (!_directAllocations.TryGetValue(start, out var allocation) || allocation.Length != length)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
_directAllocations.Remove(start);
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "L-Q3LEjIbgA",
ExportName = "sceKernelMapDirectMemory",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelMapDirectMemory(CpuContext ctx)
{
var inOutAddressPointer = ctx[CpuRegister.Rdi];
var length = ctx[CpuRegister.Rsi];
var protection = unchecked((int)ctx[CpuRegister.Rdx]);
var flags = ctx[CpuRegister.Rcx];
var directMemoryStart = ctx[CpuRegister.R8];
var alignment = ctx[CpuRegister.R9];
Console.Error.WriteLine(
$"[LOADER][TRACE] map_direct: inout=0x{inOutAddressPointer:X16} len=0x{length:X16} prot=0x{protection:X8} flags=0x{flags:X16} direct=0x{directMemoryStart:X16} align=0x{alignment:X16}");
if (inOutAddressPointer == 0 || length == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!ctx.TryReadUInt64(inOutAddressPointer, out var requestedAddress))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ulong mappedAddress;
lock (_memoryGate)
{
var effectiveAlignment = alignment == 0 ? 0x1000UL : alignment;
var fixedMapping = (flags & 0x10UL) != 0;
var desiredAddress = requestedAddress != 0
? requestedAddress
: directMemoryStart != 0
? AlignUp(directMemoryStart, effectiveAlignment)
: AlignUp(_nextVirtualAddress == 0 ? 0x1_0000_0000UL : _nextVirtualAddress, effectiveAlignment);
var reserved = false;
if (fixedMapping && requestedAddress != 0)
{
mappedAddress = requestedAddress;
}
else
{
reserved = TryReserveGuestVirtualRange(ctx, desiredAddress, length, protection, out mappedAddress);
}
Console.Error.WriteLine(
$"[LOADER][TRACE] map_direct reserve: requested=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} reserved={reserved} mapped=0x{mappedAddress:X16}");
if (!reserved)
{
if (mappedAddress == 0)
{
mappedAddress = requestedAddress != 0
? requestedAddress
: AllocateMappedGuestAddress(ctx, length, effectiveAlignment);
Console.Error.WriteLine($"[LOADER][TRACE] map_direct fallback mapped=0x{mappedAddress:X16}");
}
}
if (mappedAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
_nextVirtualAddress = Math.Max(_nextVirtualAddress, mappedAddress + length);
_mappedRegions[mappedAddress] = new MappedRegion(mappedAddress, length, protection, IsFlexible: false, DirectStart: directMemoryStart);
}
if (!ctx.TryWriteUInt64(inOutAddressPointer, mappedAddress))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "NcaWUxfMNIQ",
ExportName = "sceKernelMapNamedDirectMemory",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelMapNamedDirectMemory(CpuContext ctx)
{
return KernelMapDirectMemory(ctx);
}
[SysAbiExport(
Nid = "mL8NDH86iQI",
ExportName = "sceKernelMapNamedFlexibleMemory",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelMapNamedFlexibleMemory(CpuContext ctx)
{
var inOutAddressPointer = ctx[CpuRegister.Rdi];
var length = ctx[CpuRegister.Rsi];
var protection = unchecked((int)ctx[CpuRegister.Rdx]);
var flags = ctx[CpuRegister.Rcx];
Console.Error.WriteLine(
$"[LOADER][TRACE] map_flexible: inout=0x{inOutAddressPointer:X16} len=0x{length:X16} prot=0x{protection:X8} flags=0x{flags:X16}");
if (inOutAddressPointer == 0 || length == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!ctx.TryReadUInt64(inOutAddressPointer, out var requestedAddress))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ulong mappedAddress;
lock (_memoryGate)
{
var fixedMapping = (flags & 0x10UL) != 0;
var desiredAddress = requestedAddress != 0
? requestedAddress
: AlignUp(_nextVirtualAddress == 0 ? 0x1_0000_0000UL : _nextVirtualAddress, 0x1000UL);
if (fixedMapping && requestedAddress != 0)
{
mappedAddress = requestedAddress;
}
else if (!TryReserveGuestVirtualRange(ctx, desiredAddress, length, protection, out mappedAddress))
{
mappedAddress = requestedAddress != 0 && fixedMapping
? requestedAddress
: AllocateMappedGuestAddress(ctx, length, 0x1000UL);
}
Console.Error.WriteLine(
$"[LOADER][TRACE] map_flexible reserve: requested=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} mapped=0x{mappedAddress:X16}");
if (mappedAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
_nextVirtualAddress = Math.Max(_nextVirtualAddress, mappedAddress + length);
_allocatedFlexibleBytes = Math.Min(FlexibleMemorySizeBytes, _allocatedFlexibleBytes + length);
_mappedRegions[mappedAddress] = new MappedRegion(mappedAddress, length, protection, IsFlexible: true, DirectStart: 0);
}
if (!ctx.TryWriteUInt64(inOutAddressPointer, mappedAddress))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "IWIBBdTHit4",
ExportName = "sceKernelMapFlexibleMemory",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelMapFlexibleMemory(CpuContext ctx)
{
return KernelMapNamedFlexibleMemory(ctx);
}
[SysAbiExport(
Nid = "cQke9UuBQOk",
ExportName = "sceKernelMunmap",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelMunmap(CpuContext ctx)
{
var address = ctx[CpuRegister.Rdi];
var length = ctx[CpuRegister.Rsi];
if (address == 0 || length == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_memoryGate)
{
if (!_mappedRegions.TryGetValue(address, out var mappedRegion) || mappedRegion.Length != length)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
_mappedRegions.Remove(address);
if (mappedRegion.IsFlexible)
{
_allocatedFlexibleBytes = mappedRegion.Length >= _allocatedFlexibleBytes
? 0
: _allocatedFlexibleBytes - mappedRegion.Length;
}
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "WFcfL2lzido",
ExportName = "sceKernelQueryMemoryProtection",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelQueryMemoryProtection(CpuContext ctx)
{
var queryAddress = ctx[CpuRegister.Rdi];
var startOut = ctx[CpuRegister.Rsi];
var endOut = ctx[CpuRegister.Rdx];
var protectionOut = ctx[CpuRegister.Rcx];
lock (_memoryGate)
{
foreach (var region in _mappedRegions.Values)
{
if (queryAddress < region.Address || queryAddress >= region.Address + region.Length)
{
continue;
}
if (startOut != 0 && !ctx.TryWriteUInt64(startOut, region.Address))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (endOut != 0 && !ctx.TryWriteUInt64(endOut, region.Address + region.Length - 1))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (protectionOut != 0 && !TryWriteInt32(ctx, protectionOut, region.Protection))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
}
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
[SysAbiExport(
Nid = "BHouLQzh0X0",
ExportName = "sceKernelDirectMemoryQuery",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelDirectMemoryQuery(CpuContext ctx)
{
var offset = ctx[CpuRegister.Rdi];
_ = ctx[CpuRegister.Rsi]; // flags
var infoAddress = ctx[CpuRegister.Rdx];
var infoSize = ctx[CpuRegister.Rcx];
if (infoAddress == 0 || infoSize < 24)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_memoryGate)
{
foreach (var block in _directAllocations.Values)
{
if (offset < block.Start || offset >= block.Start + block.Length)
{
continue;
}
if (!ctx.TryWriteUInt64(infoAddress, block.Start) ||
!ctx.TryWriteUInt64(infoAddress + sizeof(ulong), block.Start + block.Length) ||
!TryWriteInt32(ctx, infoAddress + (sizeof(ulong) * 2), block.MemoryType))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
}
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
[SysAbiExport(
Nid = "vSMAm3cxYTY",
ExportName = "sceKernelMprotect",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelMprotect(CpuContext ctx)
{
var address = ctx[CpuRegister.Rdi];
var length = ctx[CpuRegister.Rsi];
var protection = unchecked((int)ctx[CpuRegister.Rdx]);
if (address == 0 || length == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_memoryGate)
{
if (!_mappedRegions.TryGetValue(address, out var region) || region.Length != length)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
_mappedRegions[address] = region with { Protection = protection };
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int KernelRtldThreadAtexitAdjust(CpuContext ctx, int delta)
{
var counterAddress = ctx[CpuRegister.Rdi];
if (counterAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!ctx.TryReadUInt64(counterAddress, out var value))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var adjusted = delta >= 0
? unchecked(value + (ulong)delta)
: value >= (ulong)(-delta)
? unchecked(value - (ulong)(-delta))
: 0UL;
if (!ctx.TryWriteUInt64(counterAddress, adjusted))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = adjusted;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int SnprintfCore(CpuContext ctx)
{
var destination = ctx[CpuRegister.Rdi];
var bufferSize = ctx[CpuRegister.Rsi];
var formatAddress = ctx[CpuRegister.Rdx];
if (!TryReadCString(ctx, formatAddress, 1_048_576, out var formatBytes))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var format = Encoding.UTF8.GetString(formatBytes);
var result = FormatString(ctx, format);
var outputBytes = Encoding.UTF8.GetBytes(result);
return WriteSnprintfOutput(ctx, destination, bufferSize, outputBytes);
}
private static int VsnprintfCore(CpuContext ctx)
{
var destination = ctx[CpuRegister.Rdi];
var bufferSize = ctx[CpuRegister.Rsi];
var formatAddress = ctx[CpuRegister.Rdx];
var vaListAddress = ctx[CpuRegister.Rcx];
if (!TryReadCString(ctx, formatAddress, 1_048_576, out var formatBytes))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var format = Encoding.UTF8.GetString(formatBytes);
if (!TryCreateVaListCursor(ctx, vaListAddress, out var vaCursor))
{
return WriteSnprintfOutput(ctx, destination, bufferSize, formatBytes);
}
ulong NextGpArg() => vaCursor.NextGpArg();
double NextFloatArg() => vaCursor.NextFloatArg();
var rendered = FormatString(ctx, format, NextGpArg, NextFloatArg);
vaCursor.Commit();
var outputBytes = Encoding.UTF8.GetBytes(rendered);
return WriteSnprintfOutput(ctx, destination, bufferSize, outputBytes);
}
private static bool TryCreateVaListCursor(CpuContext ctx, ulong vaListAddress, out SysVAmd64VaListCursor cursor)
{
cursor = default;
if (vaListAddress == 0)
{
return false;
}
if (!TryReadUInt32Compat(ctx, vaListAddress + 0, out var gpOffset) ||
!TryReadUInt32Compat(ctx, vaListAddress + 4, out var fpOffset) ||
!TryReadUInt64Compat(ctx, vaListAddress + 8, out var overflowArgArea) ||
!TryReadUInt64Compat(ctx, vaListAddress + 16, out var regSaveArea))
{
return false;
}
cursor = new SysVAmd64VaListCursor(
ctx,
vaListAddress,
gpOffset,
fpOffset,
overflowArgArea,
regSaveArea);
return true;
}
private static int WriteSnprintfOutput(
CpuContext ctx,
ulong destination,
ulong bufferSize,
ReadOnlySpan<byte> outputBytes)
{
if (bufferSize != 0 && destination != 0)
{
var maxWritable = (int)Math.Min((ulong)int.MaxValue, bufferSize - 1);
var copyLength = Math.Min(maxWritable, outputBytes.Length);
if (copyLength > 0 && !ctx.Memory.TryWrite(destination, outputBytes[..copyLength]))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
Span<byte> nullTerminator = stackalloc byte[1];
if (!ctx.Memory.TryWrite(destination + (ulong)copyLength, nullTerminator))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
ctx[CpuRegister.Rax] = unchecked((ulong)outputBytes.Length);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static string FormatString(CpuContext ctx, string format)
{
var gpIndex = 3; // RDI, RSI, RDX used by snprintf itself, args start at RCX (index 3)
ulong GetGpArg(int index)
{
return index switch
{
0 => ctx[CpuRegister.Rdi],
1 => ctx[CpuRegister.Rsi],
2 => ctx[CpuRegister.Rdx],
3 => ctx[CpuRegister.Rcx],
4 => ctx[CpuRegister.R8],
5 => ctx[CpuRegister.R9],
_ => ReadStackArg(ctx, (ulong)(index - 6) * 8)
};
}
ulong NextGpArg() => GetGpArg(gpIndex++);
double NextFloatArg()
{
var rawBits = NextGpArg();
return BitConverter.Int64BitsToDouble(unchecked((long)rawBits));
}
return FormatString(ctx, format, NextGpArg, NextFloatArg);
}
private static string FormatString(
CpuContext ctx,
string format,
Func<ulong> nextGpArg,
Func<double> nextFloatArg)
{
var sb = new StringBuilder();
for (var i = 0; i < format.Length; i++)
{
if (format[i] != '%')
{
sb.Append(format[i]);
continue;
}
i++;
if (i >= format.Length)
{
sb.Append('%');
break;
}
var leftAlign = false;
var showSign = false;
var spaceForSign = false;
var padWithZero = false;
var alternateForm = false;
while (i < format.Length)
{
switch (format[i])
{
case '-': leftAlign = true; i++; continue;
case '+': showSign = true; i++; continue;
case ' ': spaceForSign = true; i++; continue;
case '0': padWithZero = true; i++; continue;
case '#': alternateForm = true; i++; continue;
}
break;
}
var width = 0;
if (i < format.Length && format[i] == '*')
{
width = unchecked((int)nextGpArg());
i++;
if (width < 0)
{
leftAlign = true;
width = -width;
}
}
else if (i < format.Length && char.IsDigit(format[i]))
{
while (i < format.Length && char.IsDigit(format[i]))
{
width = width * 10 + (format[i] - '0');
i++;
}
}
var precision = -1;
if (i < format.Length && format[i] == '.')
{
i++;
if (i < format.Length && format[i] == '*')
{
precision = unchecked((int)nextGpArg());
i++;
}
else if (i < format.Length && char.IsDigit(format[i]))
{
precision = 0;
while (i < format.Length && char.IsDigit(format[i]))
{
precision = precision * 10 + (format[i] - '0');
i++;
}
}
else
{
precision = 0;
}
}
var lengthMod = "";
if (i < format.Length)
{
if (i + 1 < format.Length &&
((format[i] == 'h' && format[i + 1] == 'h') ||
(format[i] == 'l' && format[i + 1] == 'l')))
{
lengthMod = format.Substring(i, 2);
i += 2;
}
else if (format[i] is 'h' or 'l' or 'j' or 'z' or 't' or 'L')
{
lengthMod = format[i].ToString();
i++;
}
}
if (i >= format.Length)
{
sb.Append('%');
break;
}
var specifier = format[i];
switch (specifier)
{
case '%':
sb.Append('%');
break;
case 'd':
case 'i':
{
long value = lengthMod switch
{
"hh" => unchecked((sbyte)nextGpArg()),
"h" => unchecked((short)nextGpArg()),
"l" => unchecked((long)nextGpArg()),
"ll" => unchecked((long)nextGpArg()),
"j" => unchecked((long)nextGpArg()),
"z" => unchecked((long)nextGpArg()),
"t" => unchecked((long)nextGpArg()),
_ => unchecked((int)nextGpArg())
};
var formatted = value.ToString();
if (showSign && value >= 0)
formatted = "+" + formatted;
else if (spaceForSign && value >= 0)
formatted = " " + formatted;
sb.Append(PadString(formatted, width, leftAlign, padWithZero && !leftAlign));
}
break;
case 'u':
{
ulong value = lengthMod switch
{
"hh" => (byte)nextGpArg(),
"h" => (ushort)nextGpArg(),
"l" => nextGpArg(),
"ll" => nextGpArg(),
"j" => nextGpArg(),
"z" => nextGpArg(),
"t" => nextGpArg(),
_ => (uint)nextGpArg()
};
var formatted = value.ToString();
sb.Append(PadString(formatted, width, leftAlign, padWithZero && !leftAlign));
}
break;
case 'x':
case 'X':
{
ulong value = lengthMod switch
{
"hh" => (byte)nextGpArg(),
"h" => (ushort)nextGpArg(),
"l" => nextGpArg(),
"ll" => nextGpArg(),
"j" => nextGpArg(),
"z" => nextGpArg(),
"t" => nextGpArg(),
_ => (uint)nextGpArg()
};
var formatted = specifier == 'x'
? value.ToString("x")
: value.ToString("X");
if (alternateForm && value != 0)
formatted = specifier == 'x' ? "0x" + formatted : "0X" + formatted;
sb.Append(PadString(formatted, width, leftAlign, padWithZero && !leftAlign));
}
break;
case 'o':
{
ulong value = lengthMod switch
{
"hh" => (byte)nextGpArg(),
"h" => (ushort)nextGpArg(),
"l" => nextGpArg(),
"ll" => nextGpArg(),
"j" => nextGpArg(),
"z" => nextGpArg(),
"t" => nextGpArg(),
_ => (uint)nextGpArg()
};
var formatted = Convert.ToString((long)value, 8);
if (alternateForm && value != 0)
formatted = "0" + formatted;
sb.Append(PadString(formatted, width, leftAlign, padWithZero && !leftAlign));
}
break;
case 'p':
{
var value = nextGpArg();
var formatted = value == 0
? "(nil)"
: $"0x{value:X}";
sb.Append(formatted);
}
break;
case 's':
{
var strAddr = nextGpArg();
if (strAddr == 0)
{
sb.Append("(null)");
}
else if (TryReadCString(ctx, strAddr, 1_048_576, out var strBytes))
{
var str = Encoding.UTF8.GetString(strBytes);
if (precision >= 0 && str.Length > precision)
str = str.Substring(0, precision);
sb.Append(PadString(str, width, leftAlign, false));
}
else
{
sb.Append("(null)");
}
}
break;
case 'c':
{
var ch = (char)(byte)nextGpArg();
sb.Append(PadString(ch.ToString(), width, leftAlign, false));
}
break;
case 'f':
case 'F':
case 'e':
case 'E':
case 'g':
case 'G':
{
var value = nextFloatArg();
var formatStr = precision >= 0
? $"{{0:{specifier}{precision}}}"
: $"{{0:{specifier}}}";
var formatted = string.Format(formatStr, value);
if (showSign && value >= 0)
formatted = "+" + formatted;
else if (spaceForSign && value >= 0)
formatted = " " + formatted;
sb.Append(PadString(formatted, width, leftAlign, padWithZero && !leftAlign));
}
break;
case 'n':
{
var addr = nextGpArg();
if (addr != 0)
{
_ = TryWriteInt32(ctx, addr, sb.Length);
}
}
break;
default:
sb.Append('%');
sb.Append(specifier);
break;
}
}
return sb.ToString();
}
private static ulong ReadStackArg(CpuContext ctx, ulong offset)
{
var rsp = ctx[CpuRegister.Rsp];
if (!ctx.TryReadUInt64(rsp + offset + 8, out var value)) // +8 to skip return address
{
return 0;
}
return value;
}
private static string PadString(string str, int width, bool leftAlign, bool padWithZero)
{
if (width <= str.Length)
return str;
var padChar = padWithZero ? '0' : ' ';
var padLength = width - str.Length;
var padding = new string(padChar, padLength);
return leftAlign ? str + padding : padding + str;
}
private struct SysVAmd64VaListCursor
{
private const uint GpSaveAreaLimit = 48;
private const uint FpSaveAreaLimit = 176;
private readonly CpuContext _ctx;
private readonly ulong _vaListAddress;
private uint _gpOffset;
private uint _fpOffset;
private ulong _overflowArgArea;
private readonly ulong _regSaveArea;
public SysVAmd64VaListCursor(
CpuContext ctx,
ulong vaListAddress,
uint gpOffset,
uint fpOffset,
ulong overflowArgArea,
ulong regSaveArea)
{
_ctx = ctx;
_vaListAddress = vaListAddress;
_gpOffset = gpOffset;
_fpOffset = fpOffset;
_overflowArgArea = overflowArgArea;
_regSaveArea = regSaveArea;
}
public ulong NextGpArg()
{
ulong readAddress;
if (_regSaveArea != 0 && _gpOffset <= GpSaveAreaLimit - 8)
{
readAddress = _regSaveArea + _gpOffset;
_gpOffset += 8;
}
else
{
readAddress = _overflowArgArea;
_overflowArgArea += 8;
}
return TryReadUInt64Compat(_ctx, readAddress, out var value) ? value : 0;
}
public double NextFloatArg()
{
ulong readAddress;
if (_regSaveArea != 0 && _fpOffset <= FpSaveAreaLimit - 16)
{
readAddress = _regSaveArea + _fpOffset;
_fpOffset += 16;
}
else
{
readAddress = _overflowArgArea;
_overflowArgArea += 8;
}
return TryReadUInt64Compat(_ctx, readAddress, out var rawBits)
? BitConverter.Int64BitsToDouble(unchecked((long)rawBits))
: 0.0;
}
public void Commit()
{
_ = TryWriteUInt32Compat(_ctx, _vaListAddress + 0, _gpOffset);
_ = TryWriteUInt32Compat(_ctx, _vaListAddress + 4, _fpOffset);
_ = TryWriteUInt64Compat(_ctx, _vaListAddress + 8, _overflowArgArea);
}
}
private static ulong AllocateMappedGuestAddress(CpuContext ctx, ulong length, ulong alignment)
{
if (length == 0)
{
return 0;
}
var effectiveAlignment = alignment == 0 ? 0x1000UL : alignment;
if (_nextVirtualAddress == 0)
{
_nextVirtualAddress = 0x0100_0000UL;
}
var probeCandidates = new[]
{
8UL * 1024 * 1024,
2UL * 1024 * 1024,
512UL * 1024,
128UL * 1024,
0x1000UL,
};
foreach (var probeCandidate in probeCandidates)
{
var cursor = AlignUp(_nextVirtualAddress, effectiveAlignment);
for (var i = 0; i < 0x4000; i++)
{
if (IsMappedGuestRangeAvailable(ctx, cursor, length, probeCandidate))
{
_nextVirtualAddress = cursor + length;
return cursor;
}
cursor = AlignUp(cursor + 0x1000UL, effectiveAlignment);
}
}
return 0;
}
private static bool TryReserveGuestVirtualRange(
CpuContext ctx,
ulong desiredAddress,
ulong length,
int protection,
out ulong mappedAddress)
{
mappedAddress = 0;
if (length == 0)
{
return false;
}
try
{
object memoryObject = ctx.Memory;
MethodInfo? allocateAt = null;
var allocateAtHasAllowAlternativeArg = false;
for (var depth = 0; depth < 4; depth++)
{
foreach (var candidate in memoryObject.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
if (!string.Equals(candidate.Name, "AllocateAt", StringComparison.Ordinal))
{
continue;
}
var parameters = candidate.GetParameters();
if (parameters.Length == 3 &&
parameters[0].ParameterType == typeof(ulong) &&
parameters[1].ParameterType == typeof(ulong) &&
parameters[2].ParameterType == typeof(bool))
{
allocateAt = candidate;
allocateAtHasAllowAlternativeArg = false;
break;
}
if (parameters.Length == 4 &&
parameters[0].ParameterType == typeof(ulong) &&
parameters[1].ParameterType == typeof(ulong) &&
parameters[2].ParameterType == typeof(bool) &&
parameters[3].ParameterType == typeof(bool))
{
allocateAt = candidate;
allocateAtHasAllowAlternativeArg = true;
break;
}
}
if (allocateAt is not null)
{
break;
}
var innerProperty = memoryObject.GetType().GetProperty("Inner", BindingFlags.Public | BindingFlags.Instance);
if (innerProperty is null)
{
break;
}
var innerValue = innerProperty.GetValue(memoryObject);
if (innerValue is null || ReferenceEquals(innerValue, memoryObject))
{
break;
}
memoryObject = innerValue;
}
if (allocateAt is null)
{
Console.Error.WriteLine($"[LOADER][TRACE] reserve range: AllocateAt missing on {ctx.Memory.GetType().FullName}");
return false;
}
var executable = (protection & 0x04) != 0;
var invokeArgs = allocateAtHasAllowAlternativeArg
? new object[] { desiredAddress, length, executable, true }
: new object[] { desiredAddress, length, executable };
var result = allocateAt.Invoke(memoryObject, invokeArgs);
if (result is not ulong allocated || allocated == 0)
{
var resultType = result?.GetType().FullName ?? "null";
Console.Error.WriteLine($"[LOADER][TRACE] reserve range: AllocateAt returned {resultType} value={result ?? "null"}");
return false;
}
mappedAddress = allocated;
return true;
}
catch
{
Console.Error.WriteLine("[LOADER][TRACE] reserve range threw while invoking AllocateAt");
return false;
}
}
private static bool IsMappedGuestRangeAvailable(
CpuContext ctx,
ulong address,
ulong length,
ulong minimumReadableSpan)
{
if (length == 0)
{
return false;
}
if (ulong.MaxValue - address < length - 1)
{
return false;
}
var end = address + length - 1;
foreach (var region in _mappedRegions.Values)
{
var regionEnd = region.Address + region.Length - 1;
if (address <= regionEnd && end >= region.Address)
{
return false;
}
}
var probeLength = Math.Min(length, Math.Max(0x1000UL, minimumReadableSpan));
var probeEnd = address + probeLength - 1;
Span<byte> probe = stackalloc byte[1];
return ctx.Memory.TryRead(address, probe) &&
ctx.Memory.TryRead(probeEnd, probe);
}
private static FileAccess ResolveOpenAccess(int flags)
{
if ((flags & O_RDWR) == O_RDWR)
{
return FileAccess.ReadWrite;
}
if ((flags & O_WRONLY) == O_WRONLY)
{
return FileAccess.Write;
}
return FileAccess.Read;
}
private static FileMode ResolveOpenMode(int flags, FileAccess access)
{
var create = (flags & O_CREAT) != 0;
var truncate = (flags & O_TRUNC) != 0;
if (create && truncate)
{
return FileMode.Create;
}
if (create)
{
return FileMode.OpenOrCreate;
}
if (truncate)
{
return access == FileAccess.Read ? FileMode.Open : FileMode.Truncate;
}
return FileMode.Open;
}
private static string ResolveGuestPath(string guestPath)
{
if (string.IsNullOrWhiteSpace(guestPath))
{
return guestPath;
}
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
if (!string.IsNullOrWhiteSpace(app0Root))
{
if (guestPath.StartsWith("/app0/", StringComparison.OrdinalIgnoreCase))
{
var relative = guestPath["/app0/".Length..].Replace('/', Path.DirectorySeparatorChar);
return Path.Combine(app0Root, relative);
}
if (guestPath.StartsWith("app0/", StringComparison.OrdinalIgnoreCase))
{
var relative = guestPath["app0/".Length..].Replace('/', Path.DirectorySeparatorChar);
return Path.Combine(app0Root, relative);
}
}
return guestPath;
}
private static bool TryReadCString(CpuContext ctx, ulong address, ulong maxLength, out byte[] bytes)
{
bytes = Array.Empty<byte>();
if (address == 0)
{
return false;
}
var limit = (int)Math.Min(maxLength, 1_048_576UL);
var buffer = new List<byte>(Math.Min(limit, 256));
Span<byte> one = stackalloc byte[1];
for (var i = 0; i < limit; i++)
{
if (!TryReadCompat(ctx, address + (ulong)i, one))
{
return false;
}
if (one[0] == 0)
{
bytes = buffer.ToArray();
return true;
}
buffer.Add(one[0]);
}
bytes = buffer.ToArray();
return true;
}
private static bool TryCompareStrings(CpuContext ctx, ulong left, ulong right, ulong limit, out int compare)
{
compare = 0;
if (left == 0 || right == 0)
{
return false;
}
var max = limit == ulong.MaxValue ? 1_048_576UL : Math.Min(limit, 1_048_576UL);
Span<byte> leftByte = stackalloc byte[1];
Span<byte> rightByte = stackalloc byte[1];
for (ulong i = 0; i < max; i++)
{
if (!TryReadCompat(ctx, left + i, leftByte) ||
!TryReadCompat(ctx, right + i, rightByte))
{
return false;
}
compare = leftByte[0] - rightByte[0];
if (compare != 0 || leftByte[0] == 0 || rightByte[0] == 0)
{
return true;
}
}
compare = 0;
return true;
}
private static bool TryReadNullTerminatedUtf8(CpuContext ctx, ulong address, int maxLength, out string value)
{
value = string.Empty;
if (address == 0 || maxLength <= 0)
{
return false;
}
var buffer = new List<byte>(Math.Min(maxLength, 256));
Span<byte> one = stackalloc byte[1];
for (var i = 0; i < maxLength; i++)
{
if (!TryReadCompat(ctx, address + (ulong)i, one))
{
return false;
}
if (one[0] == 0)
{
value = Encoding.UTF8.GetString(buffer.ToArray());
return true;
}
buffer.Add(one[0]);
}
value = Encoding.UTF8.GetString(buffer.ToArray());
return true;
}
private static bool TryReadCompat(CpuContext ctx, ulong address, Span<byte> destination)
{
if (destination.IsEmpty)
{
return true;
}
if (ctx.Memory.TryRead(address, destination))
{
return true;
}
if (!TryReadHostMemory(address, destination))
{
return false;
}
var recoveryIndex = Interlocked.Increment(ref _hostMemoryReadFallbackCount);
if (recoveryIndex <= 8)
{
Console.Error.WriteLine(
$"[LOADER][WARNING] host-read fallback#{recoveryIndex}: addr=0x{address:X16} len=0x{destination.Length:X}");
}
return true;
}
private static bool TryReadUInt32Compat(CpuContext ctx, ulong address, out uint value)
{
Span<byte> bytes = stackalloc byte[sizeof(uint)];
if (!TryReadCompat(ctx, address, bytes))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt32LittleEndian(bytes);
return true;
}
private static bool TryReadUInt64Compat(CpuContext ctx, ulong address, out ulong value)
{
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
if (!TryReadCompat(ctx, address, bytes))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt64LittleEndian(bytes);
return true;
}
private static bool TryWriteCompat(CpuContext ctx, ulong address, ReadOnlySpan<byte> source)
{
if (source.IsEmpty)
{
return true;
}
if (ctx.Memory.TryWrite(address, source))
{
return true;
}
if (!TryWriteHostMemory(address, source))
{
return false;
}
var recoveryIndex = Interlocked.Increment(ref _hostMemoryWriteFallbackCount);
if (recoveryIndex <= 8)
{
Console.Error.WriteLine(
$"[LOADER][WARNING] host-write fallback#{recoveryIndex}: addr=0x{address:X16} len=0x{source.Length:X}");
}
return true;
}
private static bool TryWriteUInt32Compat(CpuContext ctx, ulong address, uint value)
{
Span<byte> bytes = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(bytes, value);
return TryWriteCompat(ctx, address, bytes);
}
private static bool TryWriteUInt64Compat(CpuContext ctx, ulong address, ulong value)
{
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(bytes, value);
return TryWriteCompat(ctx, address, bytes);
}
private static bool TryReadHostMemory(ulong address, Span<byte> destination)
{
if (destination.IsEmpty || !IsHostRangeAccessible(address, (ulong)destination.Length, writeAccess: false))
{
return false;
}
try
{
var temporary = new byte[destination.Length];
Marshal.Copy((nint)address, temporary, 0, temporary.Length);
temporary.AsSpan().CopyTo(destination);
return true;
}
catch
{
return false;
}
}
private static bool TryWriteHostMemory(ulong address, ReadOnlySpan<byte> source)
{
if (source.IsEmpty || !IsHostRangeAccessible(address, (ulong)source.Length, writeAccess: true))
{
return false;
}
try
{
var temporary = source.ToArray();
Marshal.Copy(temporary, 0, (nint)address, temporary.Length);
return true;
}
catch
{
return false;
}
}
private static bool IsHostRangeAccessible(ulong address, ulong length, bool writeAccess)
{
if (address == 0 || length == 0)
{
return false;
}
const ulong canonicalUpper = 0x0000800000000000UL;
if (address >= canonicalUpper)
{
return false;
}
if (ulong.MaxValue - address < length - 1)
{
return false;
}
if (!TryQueryHostPage(address, out var startInfo) || !HasRequiredProtection(startInfo.Protect, writeAccess))
{
return false;
}
var endAddress = address + length - 1;
if (endAddress == address)
{
return true;
}
if (!TryQueryHostPage(endAddress, out var endInfo) || !HasRequiredProtection(endInfo.Protect, writeAccess))
{
return false;
}
return true;
}
private static bool TryQueryHostPage(ulong address, out MemoryBasicInformation info)
{
info = default;
var size = (nuint)Marshal.SizeOf<MemoryBasicInformation>();
if (VirtualQuery((nint)address, out info, size) == 0)
{
return false;
}
return info.State == MemCommit;
}
private static bool HasRequiredProtection(uint protect, bool writeAccess)
{
if ((protect & (PageNoAccess | PageGuard)) != 0)
{
return false;
}
const uint readableMask = PageReadOnly | PageReadWrite | PageWriteCopy | PageExecuteRead | PageExecuteReadWrite | PageExecuteWriteCopy;
const uint writableMask = PageReadWrite | PageWriteCopy | PageExecuteReadWrite | PageExecuteWriteCopy;
var expected = writeAccess ? writableMask : readableMask;
return (protect & expected) != 0;
}
private static bool TryWriteInt32(CpuContext ctx, ulong address, int value)
{
Span<byte> bytes = stackalloc byte[sizeof(int)];
BitConverter.TryWriteBytes(bytes, value);
return ctx.Memory.TryWrite(address, bytes);
}
private static ulong AlignUp(ulong value, ulong alignment)
{
if (alignment <= 1)
{
return value;
}
var mask = alignment - 1;
return (value + mask) & ~mask;
}
}