Files
sharpemu/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs
T

5242 lines
171 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Ampr;
using System.Buffers.Binary;
using System.Text;
using System.Threading;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Linq;
using System.Globalization;
namespace SharpEmu.Libs.Kernel;
public static class KernelMemoryCompatExports
{
private const int MaxGuestStringLength = 4096;
private const int WideCharSize = sizeof(ushort);
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 OrbisKernelMapFixed = 0x0010;
private const int OrbisKernelMapOpMapDirect = 0;
private const int OrbisKernelMapOpUnmap = 1;
private const int OrbisKernelMapOpProtect = 2;
private const int OrbisKernelMapOpMapFlexible = 3;
private const int OrbisKernelMapOpTypeProtect = 4;
private const int OrbisKernelBatchMapEntrySize = 32;
private const int OrbisKernelBatchMapEntryStartOffset = 0;
private const int OrbisKernelBatchMapEntryOffsetOffset = 8;
private const int OrbisKernelBatchMapEntryLengthOffset = 16;
private const int OrbisKernelBatchMapEntryProtectionOffset = 24;
private const int OrbisKernelBatchMapEntryTypeOffset = 25;
private const int OrbisKernelBatchMapEntryOperationOffset = 28;
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 UnsetMainDirectMemoryPoolBase = ulong.MaxValue;
private const ulong FlexibleMemorySizeBytes = 448UL * 1024 * 1024;
private const int OrbisVirtualQueryInfoSize = 72;
private const int OrbisKernelMaximumNameLength = 32;
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 const int Enomem = 12;
private const int Einval = 22;
private const int Erange = 34;
private const int Struncate = 80;
private const nuint DefaultLibcHeapAlignment = 16;
private const ushort KernelStatModeDirectory = 0x41FF;
private const ushort KernelStatModeRegular = 0x81FF;
private const int KernelStatSize = 120;
private const int KernelStatStDevOffset = 0;
private const int KernelStatStInoOffset = 4;
private const int KernelStatStModeOffset = 8;
private const int KernelStatStNlinkOffset = 10;
private const int KernelStatStUidOffset = 12;
private const int KernelStatStGidOffset = 16;
private const int KernelStatStRdevOffset = 20;
private const int KernelStatStAtimOffset = 24;
private const int KernelStatStMtimOffset = 40;
private const int KernelStatStCtimOffset = 56;
private const int KernelStatStSizeOffset = 72;
private const int KernelStatStBlocksOffset = 80;
private const int KernelStatStBlksizeOffset = 88;
private const int KernelStatStFlagsOffset = 92;
private const int KernelStatStGenOffset = 96;
private const int KernelStatStLspareOffset = 100;
private const int KernelStatStBirthtimOffset = 104;
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 _libcAllocGate = 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, LibcHeapAllocation> _libcAllocations = 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 _mainDirectMemoryPoolBase = UnsetMainDirectMemoryPoolBase;
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;
private static int _nullWcscpyRecoveryCount;
[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 LibcHeapAllocation(nint BaseAddress, nuint Size, nuint Alignment);
private readonly record struct MappedRegion(ulong Address, ulong Length, int Protection, bool IsFlexible, ulong DirectStart);
private readonly record struct BatchMapEntry(ulong Start, ulong Offset, ulong Length, byte Protection, byte Type, int Operation);
[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 = "LHMrG7e8G78",
ExportName = "wcslen",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Wcslen(CpuContext ctx)
{
return WcslenCore(ctx, ctx[CpuRegister.Rdi]);
}
[SysAbiExport(
Nid = "WkkeywLJcgU",
ExportName = "wcslen",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int WcslenWkkey(CpuContext ctx)
{
return WcslenCore(ctx, ctx[CpuRegister.Rdi]);
}
private static int WcslenCore(CpuContext ctx, ulong address)
{
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_WIDE"), "1", StringComparison.Ordinal))
{
Span<byte> probe = stackalloc byte[32];
if (TryReadCompat(ctx, address, probe))
{
Console.Error.WriteLine(
$"[LOADER][TRACE] wcslen probe @0x{address:X16}: {Convert.ToHexString(probe).ToLowerInvariant()}");
}
else
{
Console.Error.WriteLine($"[LOADER][TRACE] wcslen probe @0x{address:X16}: <unreadable>");
}
}
if (!TryReadWideCString(ctx, address, 1_048_576, out var units))
{
Console.Error.WriteLine($"[LOADER][WARN] wcslen: unreadable string at 0x{address:X16}");
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = unchecked((ulong)units.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 = "fV2xHER+bKE",
ExportName = "wcscoll",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Wcscoll(CpuContext ctx)
{
return WcscollCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi]);
}
[SysAbiExport(
Nid = "pNtJdE3x49E",
ExportName = "wcscmp",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Wcscmp(CpuContext ctx)
{
return WcscmpCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi]);
}
private static int WcscollCore(CpuContext ctx, ulong left, ulong right)
{
return WcscmpCore(ctx, left, right);
}
private static int WcscmpCore(CpuContext ctx, ulong left, ulong right)
{
if (!TryCompareWideStrings(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 = "FM5NPnLqBc8",
ExportName = "wcscpy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int WcscpyFm5(CpuContext ctx)
{
return WcscpyCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi]);
}
private static int WcscpyCore(CpuContext ctx, ulong destination, ulong source)
{
if (source == 0)
{
var recoveryIndex = Interlocked.Increment(ref _nullWcscpyRecoveryCount);
if (recoveryIndex <= 8)
{
Console.Error.WriteLine(
$"[LOADER][WARNING] wcscpy null-src recovery#{recoveryIndex}: rip=0x{ctx.Rip:X16} dst=0x{destination:X16}");
}
if (!TryWriteWideTerminator(ctx, destination))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = destination;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!TryReadWideCString(ctx, source, 1_048_576, out var units))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (!TryWriteCompat(ctx, destination, EncodeWideUnitsWithTerminator(units)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = destination;
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 = "0nV21JjYCH8",
ExportName = "wcsncpy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Wcsncpy(CpuContext ctx)
{
return WcsncpyCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], ctx[CpuRegister.Rdx]);
}
[SysAbiExport(
Nid = "E8wCoUEbfzk",
ExportName = "wcsncmp",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Wcsncmp(CpuContext ctx)
{
return WcsncmpCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], ctx[CpuRegister.Rdx]);
}
private static int WcsncmpCore(CpuContext ctx, ulong left, ulong right, ulong limit)
{
if (!TryCompareWideStrings(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 = "nJz16JE1txM",
ExportName = "swprintf",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Swprintf(CpuContext ctx)
{
return SwprintfCore(ctx);
}
[SysAbiExport(
Nid = "u0XOsuOmOzc",
ExportName = "vswprintf",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Vswprintf(CpuContext ctx)
{
return VswprintfCore(ctx);
}
[SysAbiExport(
Nid = "Im55VJ-Bekc",
ExportName = "swprintf_s",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int SwprintfS(CpuContext ctx)
{
return SwprintfCore(ctx);
}
[SysAbiExport(
Nid = "oDoV9tyHTbA",
ExportName = "vswprintf_s",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int VswprintfS(CpuContext ctx)
{
return VswprintfCore(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 (!TryWriteCompat(ctx, destination, payload))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = destination;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "6f5f-qx4ucA",
ExportName = "wcscpy_s",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int WcscpyS(CpuContext ctx)
{
return WcscpySCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], ctx[CpuRegister.Rdx]);
}
[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 (!TryReadCompat(ctx, source + (ulong)copied, one))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
payload[copied] = one[0];
copied++;
if (one[0] == 0)
{
break;
}
}
if (!TryWriteCompat(ctx, destination, payload))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = destination;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Slmz4HMpNGs",
ExportName = "wcsncpy_s",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int WcsncpyS(CpuContext ctx)
{
return WcsncpySCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], ctx[CpuRegister.Rdx], ctx[CpuRegister.Rcx]);
}
private static int WcsncpyCore(CpuContext ctx, ulong destination, ulong source, ulong countValue)
{
var count = (int)Math.Min(countValue, int.MaxValue);
if (count < 0 || count > (int.MaxValue / WideCharSize))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var payload = new byte[count * WideCharSize];
for (var copied = 0; copied < count; copied++)
{
if (!TryReadUInt16Compat(ctx, source + ((ulong)copied * WideCharSize), out var unit))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
BinaryPrimitives.WriteUInt16LittleEndian(
payload.AsSpan(copied * WideCharSize, WideCharSize),
unit);
if (unit == 0)
{
break;
}
}
if (!TryWriteCompat(ctx, destination, payload))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = destination;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Ezzq78ZgHPs",
ExportName = "wcschr",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Wcschr(CpuContext ctx)
{
return WcschrCore(ctx, ctx[CpuRegister.Rdi], unchecked((ushort)ctx[CpuRegister.Rsi]));
}
private static int WcschrCore(CpuContext ctx, ulong address, ushort needle)
{
for (ulong index = 0; index < 1_048_576; index++)
{
if (!TryReadUInt16Compat(ctx, address + (index * WideCharSize), out var unit))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (unit == needle)
{
ctx[CpuRegister.Rax] = address + (index * WideCharSize);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (unit == 0)
{
break;
}
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int WcscpySCore(CpuContext ctx, ulong destination, ulong destinationCount, ulong source)
{
if (destination == 0 || destinationCount == 0)
{
ctx[CpuRegister.Rax] = Einval;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (source == 0)
{
if (!TryZeroWideDestination(ctx, destination, destinationCount))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = Einval;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
var probeLimit = Math.Min(destinationCount, 1_048_576UL);
if (!TryReadWideCStringBounded(ctx, source, probeLimit, out var units, out var terminated))
{
_ = TryZeroWideDestination(ctx, destination, destinationCount);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (!terminated || (ulong)units.Length + 1 > destinationCount)
{
if (!TryZeroWideDestination(ctx, destination, destinationCount))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = Erange;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!TryWriteCompat(ctx, destination, EncodeWideUnitsWithTerminator(units)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int WcsncpySCore(CpuContext ctx, ulong destination, ulong destinationCount, ulong source, ulong count)
{
if (destination == 0 || destinationCount == 0)
{
ctx[CpuRegister.Rax] = Einval;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (source == 0)
{
if (!TryZeroWideDestination(ctx, destination, destinationCount))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = Einval;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (count == 0)
{
if (!TryWriteWideTerminator(ctx, destination))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (count == ulong.MaxValue)
{
var copyLimit = Math.Min(destinationCount - 1, 1_048_576UL);
if (!TryReadWideCStringBounded(ctx, source, copyLimit, out var truncatedUnits, out var terminated))
{
_ = TryZeroWideDestination(ctx, destination, destinationCount);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (!TryWriteCompat(ctx, destination, EncodeWideUnitsWithTerminator(truncatedUnits)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = terminated ? 0UL : Struncate;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
var boundedCount = Math.Min(count, 1_048_576UL);
if (!TryReadWideCStringBounded(ctx, source, boundedCount, out var units, out var sourceTerminated))
{
_ = TryZeroWideDestination(ctx, destination, destinationCount);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var requiredUnits = sourceTerminated ? (ulong)units.Length + 1 : boundedCount + 1;
if (requiredUnits > destinationCount)
{
if (!TryZeroWideDestination(ctx, destination, destinationCount))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = Erange;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!TryWriteCompat(ctx, destination, EncodeWideUnitsWithTerminator(units)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
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 && (!TryReadCompat(ctx, source, payload) || !TryWriteCompat(ctx, 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 = "gQX+4GDQjpM",
ExportName = "malloc",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Malloc(CpuContext ctx)
{
ctx[CpuRegister.Rax] =
TryAllocateLibcHeap(ctx[CpuRegister.Rdi], DefaultLibcHeapAlignment, zeroFill: false, out var address)
? address
: 0;
TraceLibcAllocation(
ctx,
"malloc",
size: ctx[CpuRegister.Rdi],
alignment: DefaultLibcHeapAlignment,
resultAddress: ctx[CpuRegister.Rax]);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "tIhsqj0qsFE",
ExportName = "free",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Free(CpuContext ctx)
{
FreeLibcHeap(ctx[CpuRegister.Rdi]);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "2X5agFjKxMc",
ExportName = "calloc",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Calloc(CpuContext ctx)
{
ctx[CpuRegister.Rax] =
TryMultiplyAllocationSize(ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], out var totalSize) &&
TryAllocateLibcHeapCore(totalSize, DefaultLibcHeapAlignment, zeroFill: true, out var address)
? address
: 0;
TraceLibcAllocation(
ctx,
"calloc",
size: ctx[CpuRegister.Rdi],
count: ctx[CpuRegister.Rsi],
alignment: DefaultLibcHeapAlignment,
resultAddress: ctx[CpuRegister.Rax]);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Y7aJ1uydPMo",
ExportName = "realloc",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Realloc(CpuContext ctx)
{
var existingAddress = ctx[CpuRegister.Rdi];
var requestedSize = ctx[CpuRegister.Rsi];
if (existingAddress == 0)
{
ctx[CpuRegister.Rax] =
TryAllocateLibcHeap(requestedSize, DefaultLibcHeapAlignment, zeroFill: false, out var freshAddress)
? freshAddress
: 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (requestedSize == 0)
{
FreeLibcHeap(existingAddress);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
ctx[CpuRegister.Rax] =
TryReallocateLibcHeap(existingAddress, requestedSize, out var resizedAddress)
? resizedAddress
: 0;
TraceLibcAllocation(
ctx,
"realloc",
size: requestedSize,
existingAddress: existingAddress,
resultAddress: ctx[CpuRegister.Rax]);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Ujf3KzMvRmI",
ExportName = "memalign",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Memalign(CpuContext ctx)
{
ctx[CpuRegister.Rax] =
TryAllocateAlignedLibcHeap(
alignmentValue: ctx[CpuRegister.Rdi],
requestedSize: ctx[CpuRegister.Rsi],
requireSizeMultiple: false,
out var address)
? address
: 0;
TraceLibcAllocation(
ctx,
"memalign",
size: ctx[CpuRegister.Rsi],
alignment: ctx[CpuRegister.Rdi],
resultAddress: ctx[CpuRegister.Rax]);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "2Btkg8k24Zg",
ExportName = "aligned_alloc",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int AlignedAlloc(CpuContext ctx)
{
ctx[CpuRegister.Rax] =
TryAllocateAlignedLibcHeap(
alignmentValue: ctx[CpuRegister.Rdi],
requestedSize: ctx[CpuRegister.Rsi],
requireSizeMultiple: true,
out var address)
? address
: 0;
TraceLibcAllocation(
ctx,
"aligned_alloc",
size: ctx[CpuRegister.Rsi],
alignment: ctx[CpuRegister.Rdi],
resultAddress: ctx[CpuRegister.Rax]);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "cVSk9y8URbc",
ExportName = "posix_memalign",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int PosixMemalign(CpuContext ctx)
{
var outPointerAddress = ctx[CpuRegister.Rdi];
if (outPointerAddress == 0)
{
ctx[CpuRegister.Rax] = Einval;
TraceLibcAllocation(
ctx,
"posix_memalign",
size: ctx[CpuRegister.Rdx],
alignment: ctx[CpuRegister.Rsi],
existingAddress: outPointerAddress,
resultAddress: 0,
errorCode: Einval);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!TryValidateAlignedAllocation(
ctx[CpuRegister.Rsi],
ctx[CpuRegister.Rdx],
requireSizeMultiple: false,
requirePointerSizedAlignment: true,
out var alignment,
out var requestedSize))
{
_ = TryWriteUInt64Compat(ctx, outPointerAddress, 0);
ctx[CpuRegister.Rax] = Einval;
TraceLibcAllocation(
ctx,
"posix_memalign",
size: ctx[CpuRegister.Rdx],
alignment: ctx[CpuRegister.Rsi],
existingAddress: outPointerAddress,
resultAddress: 0,
errorCode: Einval);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!TryAllocateLibcHeapCore(requestedSize, alignment, zeroFill: false, out var address))
{
_ = TryWriteUInt64Compat(ctx, outPointerAddress, 0);
ctx[CpuRegister.Rax] = Enomem;
TraceLibcAllocation(
ctx,
"posix_memalign",
size: requestedSize,
alignment: alignment,
existingAddress: outPointerAddress,
resultAddress: 0,
errorCode: Enomem);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!TryWriteUInt64Compat(ctx, outPointerAddress, address))
{
FreeLibcHeap(address);
ctx[CpuRegister.Rax] = Einval;
TraceLibcAllocation(
ctx,
"posix_memalign",
size: requestedSize,
alignment: alignment,
existingAddress: outPointerAddress,
resultAddress: 0,
errorCode: Einval);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
ctx[CpuRegister.Rax] = 0;
TraceLibcAllocation(
ctx,
"posix_memalign",
size: requestedSize,
alignment: alignment,
existingAddress: outPointerAddress,
resultAddress: address);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[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 (!TryReadCompat(ctx, left + (ulong)i, leftByte) ||
!TryReadCompat(ctx, 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 wantsDirectory = (flags & O_DIRECTORY) != 0;
if (wantsDirectory || Directory.Exists(hostPath))
{
if (!Directory.Exists(hostPath))
{
LogOpenTrace($"_open miss path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} directory=1");
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (access != FileAccess.Read || (flags & (O_CREAT | O_TRUNC | O_APPEND)) != 0)
{
LogOpenTrace($"_open invalid-dir path='{guestPath}' host='{hostPath}' flags=0x{flags:X8}");
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var directoryFd = (int)Interlocked.Increment(ref _nextFileDescriptor);
lock (_fdGate)
{
_openDirectories[directoryFd] = new OpenDirectory
{
Path = hostPath,
Entries = EnumerateDirectoryEntries(hostPath),
NextIndex = 0
};
}
LogOpenTrace($"_open dir path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} fd={directoryFd}");
ctx[CpuRegister.Rax] = unchecked((ulong)directoryFd);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
EnsureOpenParentDirectoryExists(guestPath, hostPath, flags);
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;
}
LogOpenTrace($"_open file path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} fd={fd}");
ctx[CpuRegister.Rax] = unchecked((ulong)fd);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
LogOpenTrace($"_open fail path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} ex={ex.GetType().Name}: {ex.Message}");
return ex is UnauthorizedAccessException
? (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
}
[SysAbiExport(
Nid = "NNtFaKJbPt0",
ExportName = "_close",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelCloseUnderscore(CpuContext ctx) => KernelCloseCore(ctx, unchecked((int)ctx[CpuRegister.Rdi]));
[SysAbiExport(
Nid = "UK2Tl2DWUns",
ExportName = "sceKernelClose",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelClose(CpuContext ctx) => KernelCloseCore(ctx, unchecked((int)ctx[CpuRegister.Rdi]));
[SysAbiExport(
Nid = "eV9wAD2riIA",
ExportName = "sceKernelStat",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelStat(CpuContext ctx)
{
var pathAddress = ctx[CpuRegister.Rdi];
var statAddress = ctx[CpuRegister.Rsi];
if (pathAddress == 0 || statAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryReadNullTerminatedUtf8(ctx, pathAddress, MaxGuestStringLength, out var guestPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var hostPath = ResolveGuestPath(guestPath);
if (!TryWriteHostPathStat(ctx, statAddress, hostPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "gEpBkcwxUjw",
ExportName = "sceKernelAprResolveFilepathsToIdsAndFileSizes",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAprResolveFilepathsToIdsAndFileSizes(CpuContext ctx)
{
var pathListAddress = ctx[CpuRegister.Rdi];
var count = ctx[CpuRegister.Rsi];
var idsAddress = ctx[CpuRegister.Rdx];
var sizesAddress = ctx[CpuRegister.Rcx];
if (pathListAddress == 0 || count == 0 || sizesAddress == 0 || count > 1024)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
for (ulong i = 0; i < count; i++)
{
if (!TryResolveAprFilepath(ctx, pathListAddress, i, out var guestPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var hostPath = ResolveGuestPath(guestPath);
if (!TryGetAprFileSize(hostPath, out var fileSize))
{
LogIoTrace("apr_resolve", guestPath, $"host='{hostPath}' index={i} count={count} result=not_found");
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
var fileId = AmprFileRegistry.Register(guestPath, hostPath);
LogIoTrace("apr_resolve", guestPath, $"host='{hostPath}' index={i} count={count} id=0x{fileId:X8} size={fileSize}");
if (idsAddress != 0 &&
!TryWriteUInt32Compat(ctx, idsAddress + (i * sizeof(uint)), fileId))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (!TryWriteUInt64Compat(ctx, sizesAddress + (i * sizeof(ulong)), fileSize))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "kBwCPsYX-m4",
ExportName = "sceKernelFstat",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelFstat(CpuContext ctx)
{
var fd = unchecked((int)ctx[CpuRegister.Rdi]);
var statAddress = ctx[CpuRegister.Rsi];
if (statAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryWriteOpenDescriptorStat(ctx, fd, statAddress))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int KernelCloseCore(CpuContext ctx, int fd)
{
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))
{
}
else if (_openDirectories.Remove(fd))
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
else
{
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;
}
long positionBefore;
try
{
positionBefore = stream.Position;
}
catch (IOException)
{
positionBefore = -1;
}
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;
}
long positionAfter;
try
{
positionAfter = stream.Position;
}
catch (IOException)
{
positionAfter = -1;
}
LogIoTrace(
"read",
stream.Name,
$"fd={fd} req={requested} read={read} pos={positionBefore}->{positionAfter} preview='{PreviewIoBytes(buffer, read, 64)}' hex={PreviewIoHex(buffer, read, 32)} guest_tail={PreviewGuestHex(ctx, bufferAddress + (ulong)Math.Max(read, 0), 32)}");
ctx[CpuRegister.Rax] = unchecked((ulong)read);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Oy6IpwgtYOk",
ExportName = "lseek",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixLseek(CpuContext ctx)
{
var result = KernelLseekCore(
unchecked((int)ctx[CpuRegister.Rdi]),
unchecked((long)ctx[CpuRegister.Rsi]),
unchecked((int)ctx[CpuRegister.Rdx]),
out var position);
if (result != OrbisGen2Result.ORBIS_GEN2_OK)
{
ctx[CpuRegister.Rax] = ulong.MaxValue;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
ctx[CpuRegister.Rax] = unchecked((ulong)position);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "oib76F-12fk",
ExportName = "sceKernelLseek",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelLseek(CpuContext ctx)
{
var result = KernelLseekCore(
unchecked((int)ctx[CpuRegister.Rdi]),
unchecked((long)ctx[CpuRegister.Rsi]),
unchecked((int)ctx[CpuRegister.Rdx]),
out var position);
if (result != OrbisGen2Result.ORBIS_GEN2_OK)
{
return (int)result;
}
ctx[CpuRegister.Rax] = unchecked((ulong)position);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "taRWhTJFTgE",
ExportName = "sceKernelGetdirentries",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetdirentries(CpuContext ctx)
{
return KernelGetdirentriesCore(
ctx,
unchecked((int)ctx[CpuRegister.Rdi]),
ctx[CpuRegister.Rsi],
unchecked((int)Math.Min(ctx[CpuRegister.Rdx], int.MaxValue)),
ctx[CpuRegister.Rcx]);
}
[SysAbiExport(
Nid = "j2AIqSqJP0w",
ExportName = "sceKernelGetdents",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetdents(CpuContext ctx)
{
return KernelGetdirentriesCore(
ctx,
unchecked((int)ctx[CpuRegister.Rdi]),
ctx[CpuRegister.Rsi],
unchecked((int)Math.Min(ctx[CpuRegister.Rdx], int.MaxValue)),
0);
}
private static OrbisGen2Result KernelLseekCore(int fd, long offset, int whence, out long position)
{
position = -1;
FileStream? stream;
lock (_fdGate)
{
_openFiles.TryGetValue(fd, out stream);
}
if (stream is null)
{
LogIoTrace("lseek", $"fd:{fd}", $"offset={offset} whence={whence} result=badfd");
return OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
SeekOrigin origin;
switch (whence)
{
case SeekSet:
origin = SeekOrigin.Begin;
break;
case SeekCur:
origin = SeekOrigin.Current;
break;
case SeekEnd:
origin = SeekOrigin.End;
break;
default:
LogIoTrace("lseek", stream.Name, $"fd={fd} offset={offset} whence={whence} result=invalid_whence");
return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
try
{
position = stream.Seek(offset, origin);
}
catch (IOException ex)
{
LogIoTrace("lseek", stream.Name, $"fd={fd} offset={offset} whence={whence} result=io_error ex={ex.Message}");
return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
catch (ArgumentException ex)
{
LogIoTrace("lseek", stream.Name, $"fd={fd} offset={offset} whence={whence} result=invalid ex={ex.Message}");
return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
LogIoTrace("lseek", stream.Name, $"fd={fd} offset={offset} whence={whence} pos={position}");
return 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;
}
if (!TryFindAvailableDirectMemorySpanLocked(searchStart, searchEnd, alignment, out var candidate, out var rangeAvailable))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
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)
{
TraceDirectMemoryCall(
ctx,
"allocate_direct",
length,
alignment,
memoryType,
outAddress,
result: OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
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;
ulong selectedAddress;
lock (_memoryGate)
{
if (!TryAllocateDirectMemoryLocked(searchStart, searchEnd, length, align, memoryType, DirectMemorySizeBytes, out selectedAddress))
{
TraceDirectMemoryCall(
ctx,
"allocate_direct",
length,
align,
memoryType,
outAddress,
result: OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
}
}
if (!ctx.TryWriteUInt64(outAddress, selectedAddress))
{
TraceDirectMemoryCall(
ctx,
"allocate_direct",
length,
align,
memoryType,
outAddress,
selectedAddress,
OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceDirectMemoryCall(
ctx,
"allocate_direct",
length,
align,
memoryType,
outAddress,
selectedAddress,
OrbisGen2Result.ORBIS_GEN2_OK);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[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)
{
TraceDirectMemoryCall(
ctx,
"allocate_main_direct",
length,
alignment,
memoryType,
outAddress,
result: OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var effectiveAlignment = alignment == 0 ? 0x1000UL : alignment;
ulong aligned;
lock (_memoryGate)
{
var allocationLimit = DirectMemorySizeBytes;
if (_mainDirectMemoryPoolBase != UnsetMainDirectMemoryPoolBase &&
!TryAddU64(_mainDirectMemoryPoolBase, DirectMemorySizeBytes, out allocationLimit))
{
allocationLimit = ulong.MaxValue;
}
if (!TryAllocateDirectMemoryLocked(0, allocationLimit, length, effectiveAlignment, memoryType, allocationLimit, out aligned))
{
var poolBase = _mainDirectMemoryPoolBase == UnsetMainDirectMemoryPoolBase
? AlignUp(GetDirectMemoryHighWaterMarkLocked(), effectiveAlignment)
: _mainDirectMemoryPoolBase;
if (_mainDirectMemoryPoolBase == UnsetMainDirectMemoryPoolBase &&
TryAddU64(poolBase, DirectMemorySizeBytes, out var shiftedLimit) &&
TryAllocateDirectMemoryLocked(0, shiftedLimit, length, effectiveAlignment, memoryType, shiftedLimit, out aligned))
{
_mainDirectMemoryPoolBase = poolBase;
if (ShouldTraceDirectMemory())
{
Console.Error.WriteLine(
$"[LOADER][TRACE] main_direct_pool: base=0x{poolBase:X16} limit=0x{shiftedLimit:X16}");
}
}
else
{
TraceDirectMemoryCall(
ctx,
"allocate_main_direct",
length,
effectiveAlignment,
memoryType,
outAddress,
result: OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
}
}
}
if (!ctx.TryWriteUInt64(outAddress, aligned))
{
TraceDirectMemoryCall(
ctx,
"allocate_main_direct",
length,
effectiveAlignment,
memoryType,
outAddress,
aligned,
OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceDirectMemoryCall(
ctx,
"allocate_main_direct",
length,
effectiveAlignment,
memoryType,
outAddress,
aligned,
OrbisGen2Result.ORBIS_GEN2_OK);
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);
_nextPhysicalAddress = GetDirectMemoryHighWaterMarkLocked();
}
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];
if (ShouldTraceDirectMemory())
{
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);
}
if (ShouldTraceDirectMemory())
{
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);
if (ShouldTraceDirectMemory())
{
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 = "2SKEx6bSq-4",
ExportName = "sceKernelBatchMap",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelBatchMap(CpuContext ctx)
{
return KernelBatchMapCore(ctx, OrbisKernelMapFixed);
}
[SysAbiExport(
Nid = "kBJzF8x4SyE",
ExportName = "sceKernelBatchMap2",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelBatchMap2(CpuContext ctx)
{
return KernelBatchMapCore(ctx, unchecked((int)ctx[CpuRegister.Rcx]));
}
[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 = "rVjRvHJ0X6c",
ExportName = "sceKernelVirtualQuery",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelVirtualQuery(CpuContext ctx)
{
var queryAddress = ctx[CpuRegister.Rdi];
var flags = unchecked((int)ctx[CpuRegister.Rsi]);
var infoAddress = ctx[CpuRegister.Rdx];
var infoSize = ctx[CpuRegister.Rcx];
if (infoAddress == 0 || infoSize < OrbisVirtualQueryInfoSize)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
MappedRegion region;
var memoryType = 0;
lock (_memoryGate)
{
if (!TryFindVirtualQueryRegionLocked(queryAddress, findNext: (flags & 0x1) != 0, out region))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (region.DirectStart != 0 &&
_directAllocations.TryGetValue(region.DirectStart, out var allocation))
{
memoryType = allocation.MemoryType;
}
}
Span<byte> payload = stackalloc byte[OrbisVirtualQueryInfoSize];
payload.Clear();
var regionEnd = TryAddU64(region.Address, region.Length, out var exclusiveEnd)
? exclusiveEnd
: ulong.MaxValue;
var stateFlags = 0u;
if (region.IsFlexible)
{
stateFlags |= 0x01u;
}
if (region.DirectStart != 0)
{
stateFlags |= 0x02u;
}
stateFlags |= 0x10u;
BinaryPrimitives.WriteUInt64LittleEndian(payload[0..8], region.Address);
BinaryPrimitives.WriteUInt64LittleEndian(payload[8..16], regionEnd);
BinaryPrimitives.WriteUInt64LittleEndian(payload[16..24], region.DirectStart);
BinaryPrimitives.WriteInt32LittleEndian(payload[24..28], region.Protection);
BinaryPrimitives.WriteInt32LittleEndian(payload[28..32], memoryType);
payload[32] = unchecked((byte)stateFlags);
var name = region.DirectStart != 0
? "direct"
: region.IsFlexible
? "flexible"
: string.Empty;
if (!string.IsNullOrEmpty(name))
{
var nameBytes = Encoding.ASCII.GetBytes(name);
nameBytes.AsSpan(0, Math.Min(nameBytes.Length, OrbisKernelMaximumNameLength))
.CopyTo(payload.Slice(33, OrbisKernelMaximumNameLength));
}
if (!TryWriteCompat(ctx, infoAddress, payload))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
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 (!TryApplyMappedRegionProtectionLocked(address, length, protection))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "9bfdLIyuwCY",
ExportName = "sceKernelMtypeprotect",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelMtypeprotect(CpuContext ctx)
{
var address = ctx[CpuRegister.Rdi];
var length = ctx[CpuRegister.Rsi];
var memoryType = unchecked((int)ctx[CpuRegister.Rdx]);
var protection = unchecked((int)ctx[CpuRegister.Rcx]);
if (address == 0 || length == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_memoryGate)
{
if (!TryApplyMappedRegionProtectionLocked(address, length, protection, memoryType))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
}
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 int SwprintfCore(CpuContext ctx)
{
var destination = ctx[CpuRegister.Rdi];
var bufferSize = ctx[CpuRegister.Rsi];
var formatAddress = ctx[CpuRegister.Rdx];
if (!TryReadWideCString(ctx, formatAddress, 1_048_576, out var formatUnits))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var format = DecodeWideUnits(formatUnits);
var rendered = FormatString(ctx, format);
TraceWidePrintf(ctx, "swprintf", destination, bufferSize, format, rendered);
return WriteSwprintfOutput(ctx, destination, bufferSize, rendered);
}
private static int VswprintfCore(CpuContext ctx)
{
var destination = ctx[CpuRegister.Rdi];
var bufferSize = ctx[CpuRegister.Rsi];
var formatAddress = ctx[CpuRegister.Rdx];
var vaListAddress = ctx[CpuRegister.Rcx];
if (!TryReadWideCString(ctx, formatAddress, 1_048_576, out var formatUnits))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var format = DecodeWideUnits(formatUnits);
string rendered;
if (!TryCreateVaListCursor(ctx, vaListAddress, out var vaCursor))
{
rendered = format;
}
else
{
TraceWidePrintfVaList(ctx, "vswprintf", format, vaListAddress, vaCursor);
ulong NextGpArg() => vaCursor.NextGpArg();
double NextFloatArg() => vaCursor.NextFloatArg();
rendered = FormatString(ctx, format, NextGpArg, NextFloatArg);
vaCursor.Commit();
}
TraceWidePrintf(ctx, "vswprintf", destination, bufferSize, format, rendered);
return WriteSwprintfOutput(ctx, destination, bufferSize, rendered);
}
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 int WriteSwprintfOutput(
CpuContext ctx,
ulong destination,
ulong bufferSize,
string rendered)
{
if (bufferSize != 0 && destination != 0)
{
var maxWritableChars = (int)Math.Min((ulong)int.MaxValue, bufferSize - 1);
var copyLength = Math.Min(maxWritableChars, rendered.Length);
if (copyLength > 0)
{
var outputBytes = Encoding.Unicode.GetBytes(rendered[..copyLength]);
if (!TryWriteCompat(ctx, destination, outputBytes))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
Span<byte> nullTerminator = stackalloc byte[WideCharSize];
if (!TryWriteCompat(ctx, destination + ((ulong)copyLength * WideCharSize), nullTerminator))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
ctx[CpuRegister.Rax] = unchecked((ulong)rendered.Length);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static void TraceWidePrintf(CpuContext ctx, string exportName, ulong destination, ulong bufferSize, string format, string rendered)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_WIDE_PRINTF"), "1", StringComparison.Ordinal))
{
return;
}
var formatPreview = format.Length > 160 ? format[..160] + "..." : format;
var renderedPreview = rendered.Length > 160 ? rendered[..160] + "..." : rendered;
Console.Error.WriteLine(
$"[LOADER][TRACE] {exportName}: dst=0x{destination:X16} count=0x{bufferSize:X} len={rendered.Length} fmt='{formatPreview}' rendered='{renderedPreview}'");
}
private static void TraceWidePrintfVaList(
CpuContext ctx,
string exportName,
string format,
ulong vaListAddress,
SysVAmd64VaListCursor cursor)
{
if (!ShouldTraceWidePrintfArgs(format))
{
return;
}
Console.Error.WriteLine(
$"[LOADER][TRACE] {exportName}: va_list=0x{vaListAddress:X16} gp_offset={cursor.GpOffset} fp_offset={cursor.FpOffset} overflow=0x{cursor.OverflowArgArea:X16} reg_save=0x{cursor.RegSaveArea:X16}");
if (cursor.RegSaveArea != 0)
{
for (var i = 0; i < 6; i++)
{
var slotAddress = cursor.RegSaveArea + ((ulong)i * 8);
var valueText = TryReadUInt64Compat(ctx, slotAddress, out var value)
? $"0x{value:X16}"
: "<unreadable>";
Console.Error.WriteLine(
$"[LOADER][TRACE] {exportName}: reg_save.gp[{i}] @0x{slotAddress:X16} = {valueText}");
}
}
if (cursor.OverflowArgArea != 0)
{
for (var i = 0; i < 4; i++)
{
var slotAddress = cursor.OverflowArgArea + ((ulong)i * 8);
var valueText = TryReadUInt64Compat(ctx, slotAddress, out var value)
? $"0x{value:X16}"
: "<unreadable>";
Console.Error.WriteLine(
$"[LOADER][TRACE] {exportName}: overflow[{i}] @0x{slotAddress:X16} = {valueText}");
}
}
}
private static bool ShouldTraceWidePrintfArgs(string format)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_WIDE_PRINTF_ARGS"), "1", StringComparison.Ordinal))
{
return false;
}
var filter = Environment.GetEnvironmentVariable("SHARPEMU_LOG_WIDE_PRINTF_FILTER");
return string.IsNullOrEmpty(filter) || format.Contains(filter, StringComparison.Ordinal);
}
private static void TracePrintfStringArgument(CpuContext ctx, string lengthMod, ulong address)
{
if (!ShouldTraceWidePrintfArgs(lengthMod == "l" ? "%ls" : "%s"))
{
return;
}
var widePreview = "<not-wide>";
if (address != 0 && TryReadWideCString(ctx, address, 64, out var wideUnits))
{
widePreview = SanitizeTracePreview(DecodeWideUnits(wideUnits), 64);
}
else if (address == 0)
{
widePreview = "<null>";
}
else
{
widePreview = "<unreadable>";
}
var utf8Preview = "<null>";
if (address != 0 && TryReadCString(ctx, address, 128, out var utf8Bytes))
{
utf8Preview = SanitizeTracePreview(Encoding.UTF8.GetString(utf8Bytes), 64);
}
else if (address != 0)
{
utf8Preview = "<unreadable>";
}
var rawPreview = "<null>";
if (address != 0)
{
Span<byte> raw = stackalloc byte[32];
rawPreview = TryReadCompat(ctx, address, raw)
? Convert.ToHexString(raw).ToLowerInvariant()
: "<unreadable>";
}
Console.Error.WriteLine(
$"[LOADER][TRACE] printf-arg %{lengthMod}s addr=0x{address:X16} wide='{widePreview}' utf8='{utf8Preview}' raw={rawPreview}");
}
private static string SanitizeTracePreview(string value, int maxLength)
{
if (string.IsNullOrEmpty(value))
{
return value;
}
var sanitized = value
.Replace("\r", "\\r", StringComparison.Ordinal)
.Replace("\n", "\\n", StringComparison.Ordinal)
.Replace("\t", "\\t", StringComparison.Ordinal);
return sanitized.Length > maxLength
? sanitized[..maxLength] + "..."
: sanitized;
}
private readonly record struct PosixTm(
int Second,
int Minute,
int Hour,
int Day,
int Month,
int Year,
int WeekDay,
int YearDay,
int IsDst)
{
public DateTime ToDateTime()
{
var year = Math.Clamp(1900 + Year, 1, 9999);
var month = Math.Clamp(Month + 1, 1, 12);
var day = Math.Clamp(Day, 1, DateTime.DaysInMonth(year, month));
var hour = Math.Clamp(Hour, 0, 23);
var minute = Math.Clamp(Minute, 0, 59);
var second = Math.Clamp(Second, 0, 60);
if (second == 60)
{
second = 59;
}
return new DateTime(year, month, day, hour, minute, second, DateTimeKind.Unspecified);
}
}
private static bool TryReadPosixTm(CpuContext ctx, ulong address, out PosixTm tm)
{
tm = default;
if (address == 0)
{
return false;
}
Span<byte> bytes = stackalloc byte[9 * sizeof(int)];
if (!TryReadCompat(ctx, address, bytes))
{
return false;
}
tm = new PosixTm(
BinaryPrimitives.ReadInt32LittleEndian(bytes[0..4]),
BinaryPrimitives.ReadInt32LittleEndian(bytes[4..8]),
BinaryPrimitives.ReadInt32LittleEndian(bytes[8..12]),
BinaryPrimitives.ReadInt32LittleEndian(bytes[12..16]),
BinaryPrimitives.ReadInt32LittleEndian(bytes[16..20]),
BinaryPrimitives.ReadInt32LittleEndian(bytes[20..24]),
BinaryPrimitives.ReadInt32LittleEndian(bytes[24..28]),
BinaryPrimitives.ReadInt32LittleEndian(bytes[28..32]),
BinaryPrimitives.ReadInt32LittleEndian(bytes[32..36]));
return true;
}
private static string FormatWcsftime(string format, PosixTm tm)
{
var timestamp = tm.ToDateTime();
var culture = CultureInfo.InvariantCulture;
var builder = new StringBuilder(format.Length + 16);
for (var i = 0; i < format.Length; i++)
{
var ch = format[i];
if (ch != '%' || i + 1 >= format.Length)
{
builder.Append(ch);
continue;
}
i++;
builder.Append(format[i] switch
{
'%' => "%",
'a' => timestamp.ToString("ddd", culture),
'A' => timestamp.ToString("dddd", culture),
'b' or 'h' => timestamp.ToString("MMM", culture),
'B' => timestamp.ToString("MMMM", culture),
'c' => timestamp.ToString("ddd MMM dd HH:mm:ss yyyy", culture),
'd' => timestamp.ToString("dd", culture),
'e' => timestamp.Day.ToString().PadLeft(2, ' '),
'F' => timestamp.ToString("yyyy-MM-dd", culture),
'H' => timestamp.ToString("HH", culture),
'I' => timestamp.ToString("hh", culture),
'j' => timestamp.DayOfYear.ToString("D3", culture),
'm' => timestamp.ToString("MM", culture),
'M' => timestamp.ToString("mm", culture),
'n' => "\n",
'p' => timestamp.ToString("tt", culture),
'R' => timestamp.ToString("HH:mm", culture),
'S' => timestamp.ToString("ss", culture),
't' => "\t",
'T' => timestamp.ToString("HH:mm:ss", culture),
'u' => (((int)timestamp.DayOfWeek + 6) % 7 + 1).ToString(culture),
'w' => ((int)timestamp.DayOfWeek).ToString(culture),
'x' => timestamp.ToString("MM/dd/yy", culture),
'X' => timestamp.ToString("HH:mm:ss", culture),
'y' => timestamp.ToString("yy", culture),
'Y' => timestamp.ToString("yyyy", culture),
'Z' => tm.IsDst == 0 ? "UTC" : "DST",
'z' => "+0000",
_ => "%" + format[i]
});
}
return builder.ToString();
}
private static void TraceLibcAllocation(
CpuContext ctx,
string operation,
ulong size,
ulong alignment = 0,
ulong count = 0,
ulong existingAddress = 0,
ulong resultAddress = 0,
int? errorCode = null)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_LIBC_ALLOC"), "1", StringComparison.Ordinal))
{
return;
}
var returnRip = 0UL;
var stackPointer = ctx[CpuRegister.Rsp];
if (stackPointer != 0)
{
_ = ctx.TryReadUInt64(stackPointer, out returnRip);
}
Console.Error.WriteLine(
$"[LOADER][TRACE] {operation}: ret=0x{returnRip:X16} size=0x{size:X16} count=0x{count:X16} align=0x{alignment:X16} in=0x{existingAddress:X16} result=0x{resultAddress:X16} errno={(errorCode.HasValue ? errorCode.Value : 0)}");
}
internal static string FormatStringFromVarArgs(CpuContext ctx, string format, int firstGpArgIndex)
{
var gpIndex = Math.Max(0, firstGpArgIndex);
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)
{
return FormatStringFromVarArgs(ctx, format, firstGpArgIndex: 3);
}
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();
TracePrintfStringArgument(ctx, lengthMod, strAddr);
if (strAddr == 0)
{
sb.Append("(null)");
}
else if (lengthMod == "l")
{
if (TryReadWideCString(ctx, strAddr, 1_048_576, out var wideUnits))
{
var str = DecodeWideUnits(wideUnits);
if (precision >= 0 && str.Length > precision)
str = str.Substring(0, precision);
sb.Append(PadString(str, width, leftAlign, false));
}
else
{
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':
{
string renderedChar;
if (lengthMod == "l")
{
var scalar = unchecked((ushort)nextGpArg());
renderedChar = TryConvertWideScalarToString(scalar, out var wideCharText)
? wideCharText
: "?";
}
else
{
renderedChar = ((char)(byte)nextGpArg()).ToString();
}
sb.Append(PadString(renderedChar, 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);
}
public uint GpOffset => _gpOffset;
public uint FpOffset => _fpOffset;
public ulong OverflowArgArea => _overflowArgArea;
public ulong RegSaveArea => _regSaveArea;
}
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 devlogAppRoot = ResolveDevlogAppRoot();
if (guestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["/devlog/app/".Length..]);
return Path.Combine(devlogAppRoot, relative);
}
if (guestPath.StartsWith("devlog/app/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["devlog/app/".Length..]);
return Path.Combine(devlogAppRoot, relative);
}
if (string.Equals(guestPath, "/devlog/app", StringComparison.OrdinalIgnoreCase) ||
string.Equals(guestPath, "devlog/app", StringComparison.OrdinalIgnoreCase))
{
return devlogAppRoot;
}
var temp0Root = ResolveTemp0Root();
if (guestPath.StartsWith("/temp0/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["/temp0/".Length..]);
return Path.Combine(temp0Root, relative);
}
if (string.Equals(guestPath, "/temp0", StringComparison.OrdinalIgnoreCase))
{
return temp0Root;
}
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
if (!string.IsNullOrWhiteSpace(app0Root))
{
if (string.Equals(guestPath, "/app0", StringComparison.OrdinalIgnoreCase) ||
string.Equals(guestPath, "app0", StringComparison.OrdinalIgnoreCase))
{
return app0Root;
}
if (guestPath.StartsWith("/app0/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["/app0/".Length..]);
return Path.Combine(app0Root, relative);
}
if (guestPath.StartsWith("app0/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["app0/".Length..]);
return Path.Combine(app0Root, relative);
}
if (!Path.IsPathFullyQualified(guestPath) &&
!guestPath.StartsWith("/", StringComparison.Ordinal) &&
!guestPath.StartsWith("\\", StringComparison.Ordinal))
{
var relative = guestPath.Replace('/', Path.DirectorySeparatorChar);
return Path.Combine(app0Root, relative);
}
}
return guestPath;
}
private static string NormalizeMountRelativePath(string relativePath)
{
return relativePath
.TrimStart('/', '\\')
.Replace('/', Path.DirectorySeparatorChar)
.Replace('\\', Path.DirectorySeparatorChar);
}
private static string ResolveDevlogAppRoot()
{
var configuredRoot = Environment.GetEnvironmentVariable("SHARPEMU_DEVLOG_APP_DIR");
string root;
if (!string.IsNullOrWhiteSpace(configuredRoot))
{
root = Path.GetFullPath(configuredRoot);
}
else
{
root = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "logs", "devlog", "app"));
}
Directory.CreateDirectory(root);
return root;
}
private static string ResolveTemp0Root()
{
const string temp0VariableName = "SHARPEMU_TEMP0_DIR";
var configuredRoot = Environment.GetEnvironmentVariable(temp0VariableName);
if (!string.IsNullOrWhiteSpace(configuredRoot))
{
return Path.GetFullPath(configuredRoot);
}
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
var appName = string.IsNullOrWhiteSpace(app0Root)
? "default"
: Path.GetFileName(Path.TrimEndingDirectorySeparator(app0Root));
if (string.IsNullOrWhiteSpace(appName))
{
appName = "default";
}
var invalidChars = Path.GetInvalidFileNameChars();
appName = new string(appName.Select(ch => invalidChars.Contains(ch) ? '_' : ch).ToArray());
var root = Path.Combine(Path.GetTempPath(), "SharpEmu", appName, "temp0");
Environment.SetEnvironmentVariable(temp0VariableName, root);
return root;
}
private static void EnsureOpenParentDirectoryExists(string guestPath, string hostPath, int flags)
{
if (string.IsNullOrWhiteSpace(hostPath))
{
return;
}
var shouldCreateParent =
(flags & O_CREAT) != 0 ||
guestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase) ||
guestPath.StartsWith("devlog/app/", StringComparison.OrdinalIgnoreCase);
if (!shouldCreateParent)
{
return;
}
var parentDirectory = Path.GetDirectoryName(hostPath);
if (!string.IsNullOrWhiteSpace(parentDirectory))
{
Directory.CreateDirectory(parentDirectory);
}
}
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 TryReadWideCString(CpuContext ctx, ulong address, ulong maxLength, out ushort[] units)
{
units = Array.Empty<ushort>();
if (address == 0)
{
return false;
}
var limit = (int)Math.Min(maxLength, 1_048_576UL);
var buffer = new List<ushort>(Math.Min(limit, 256));
for (var i = 0; i < limit; i++)
{
if (!TryReadUInt16Compat(ctx, address + ((ulong)i * WideCharSize), out var unit))
{
return false;
}
if (unit == 0)
{
units = buffer.ToArray();
return true;
}
buffer.Add(unit);
}
units = buffer.ToArray();
return true;
}
private static bool TryReadWideCStringBounded(CpuContext ctx, ulong address, ulong maxLength, out ushort[] units, out bool terminated)
{
units = Array.Empty<ushort>();
terminated = false;
if (address == 0)
{
return false;
}
var limit = (int)Math.Min(maxLength, 1_048_576UL);
var buffer = new List<ushort>(Math.Min(limit, 256));
for (var i = 0; i < limit; i++)
{
if (!TryReadUInt16Compat(ctx, address + ((ulong)i * WideCharSize), out var unit))
{
return false;
}
if (unit == 0)
{
terminated = true;
units = buffer.ToArray();
return true;
}
buffer.Add(unit);
}
units = buffer.ToArray();
return true;
}
private static bool TryCompareWideStrings(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);
for (ulong i = 0; i < max; i++)
{
if (!TryReadUInt16Compat(ctx, left + (i * WideCharSize), out var leftUnit) ||
!TryReadUInt16Compat(ctx, right + (i * WideCharSize), out var rightUnit))
{
return false;
}
compare = leftUnit == rightUnit ? 0 : leftUnit < rightUnit ? -1 : 1;
if (compare != 0 || leftUnit == 0 || rightUnit == 0)
{
return true;
}
}
compare = 0;
return true;
}
private static byte[] EncodeWideUnits(ReadOnlySpan<ushort> units)
{
var bytes = new byte[units.Length * WideCharSize];
for (var i = 0; i < units.Length; i++)
{
BinaryPrimitives.WriteUInt16LittleEndian(
bytes.AsSpan(i * WideCharSize, WideCharSize),
units[i]);
}
return bytes;
}
private static string DecodeWideUnits(ReadOnlySpan<ushort> units)
{
if (units.IsEmpty)
{
return string.Empty;
}
return new string(MemoryMarshal.Cast<ushort, char>(units));
}
private static bool TryConvertWideScalarToString(ushort scalar, out string text)
{
text = ((char)scalar).ToString();
return true;
}
private static byte[] EncodeWideUnitsWithTerminator(ReadOnlySpan<ushort> units)
{
var bytes = new byte[(units.Length + 1) * WideCharSize];
EncodeWideUnits(units).CopyTo(bytes, 0);
return bytes;
}
private static bool TryWriteWideTerminator(CpuContext ctx, ulong address)
{
Span<byte> terminator = stackalloc byte[WideCharSize];
terminator.Clear();
return TryWriteCompat(ctx, address, terminator);
}
private static bool TryZeroWideDestination(CpuContext ctx, ulong destination, ulong destinationCount)
{
return destination == 0 || destinationCount == 0 || TryWriteWideTerminator(ctx, destination);
}
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 TryResolveAprFilepath(CpuContext ctx, ulong pathListAddress, ulong index, out string guestPath)
{
guestPath = string.Empty;
if (TryReadAprPathPointer(ctx, pathListAddress + (index * sizeof(ulong)), out guestPath))
{
return true;
}
if (index != 0)
{
return false;
}
if (TryReadAprPathText(ctx, pathListAddress, out guestPath))
{
return true;
}
const ulong scanLimit = 0x40;
for (ulong offset = 0; offset < scanLimit; offset += sizeof(ulong))
{
if (TryReadAprPathPointer(ctx, pathListAddress + offset, out guestPath))
{
return true;
}
}
return false;
}
private static bool TryReadAprPathPointer(CpuContext ctx, ulong pointerAddress, out string guestPath)
{
guestPath = string.Empty;
if (!ctx.TryReadUInt64(pointerAddress, out var candidatePath) || candidatePath == 0)
{
return false;
}
return TryReadAprPathText(ctx, candidatePath, out guestPath);
}
private static bool TryReadAprPathText(CpuContext ctx, ulong address, out string guestPath)
{
guestPath = string.Empty;
if (TryReadNullTerminatedUtf8(ctx, address, MaxGuestStringLength, out guestPath) &&
!string.IsNullOrWhiteSpace(guestPath))
{
return true;
}
if (!TryReadWideCString(ctx, address, MaxGuestStringLength, out var wideUnits))
{
return false;
}
guestPath = DecodeWideUnits(wideUnits);
return !string.IsNullOrWhiteSpace(guestPath);
}
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 TryReadUInt16Compat(CpuContext ctx, ulong address, out ushort value)
{
Span<byte> bytes = stackalloc byte[sizeof(ushort)];
if (!TryReadCompat(ctx, address, bytes))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt16LittleEndian(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 int KernelBatchMapCore(CpuContext ctx, int flags)
{
var entriesAddress = ctx[CpuRegister.Rdi];
var entryCount = unchecked((int)ctx[CpuRegister.Rsi]);
var processedOutAddress = ctx[CpuRegister.Rdx];
var processedCount = 0;
var result = (int)OrbisGen2Result.ORBIS_GEN2_OK;
for (var index = 0; index < entryCount; index++)
{
var entryAddress = entriesAddress + (ulong)(index * OrbisKernelBatchMapEntrySize);
if (!TryReadBatchMapEntry(ctx, entryAddress, out var entry) ||
entry.Length == 0 ||
entry.Operation < OrbisKernelMapOpMapDirect ||
entry.Operation > OrbisKernelMapOpTypeProtect)
{
result = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
break;
}
result = entry.Operation switch
{
OrbisKernelMapOpMapDirect => InvokeKernelMemoryOperation(
ctx,
KernelMapDirectMemory,
entryAddress + OrbisKernelBatchMapEntryStartOffset,
entry.Length,
entry.Protection,
unchecked((ulong)(uint)flags),
entry.Offset,
0),
OrbisKernelMapOpUnmap => InvokeKernelMemoryOperation(
ctx,
KernelMunmap,
entry.Start,
entry.Length),
OrbisKernelMapOpProtect => InvokeKernelMemoryOperation(
ctx,
KernelMprotect,
entry.Start,
entry.Length,
entry.Protection),
OrbisKernelMapOpMapFlexible => InvokeKernelMemoryOperation(
ctx,
KernelMapNamedFlexibleMemory,
entryAddress + OrbisKernelBatchMapEntryStartOffset,
entry.Length,
entry.Protection,
unchecked((ulong)(uint)flags)),
OrbisKernelMapOpTypeProtect => InvokeKernelMemoryOperation(
ctx,
KernelMtypeprotect,
entry.Start,
entry.Length,
entry.Type,
entry.Protection),
_ => (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT,
};
if (result != (int)OrbisGen2Result.ORBIS_GEN2_OK)
{
break;
}
processedCount++;
}
if (processedOutAddress != 0 && !TryWriteInt32(ctx, processedOutAddress, processedCount))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
return result;
}
private static int InvokeKernelMemoryOperation(
CpuContext ctx,
Func<CpuContext, int> operation,
ulong rdi = 0,
ulong rsi = 0,
ulong rdx = 0,
ulong rcx = 0,
ulong r8 = 0,
ulong r9 = 0)
{
var savedRdi = ctx[CpuRegister.Rdi];
var savedRsi = ctx[CpuRegister.Rsi];
var savedRdx = ctx[CpuRegister.Rdx];
var savedRcx = ctx[CpuRegister.Rcx];
var savedR8 = ctx[CpuRegister.R8];
var savedR9 = ctx[CpuRegister.R9];
ctx[CpuRegister.Rdi] = rdi;
ctx[CpuRegister.Rsi] = rsi;
ctx[CpuRegister.Rdx] = rdx;
ctx[CpuRegister.Rcx] = rcx;
ctx[CpuRegister.R8] = r8;
ctx[CpuRegister.R9] = r9;
try
{
return operation(ctx);
}
finally
{
ctx[CpuRegister.Rdi] = savedRdi;
ctx[CpuRegister.Rsi] = savedRsi;
ctx[CpuRegister.Rdx] = savedRdx;
ctx[CpuRegister.Rcx] = savedRcx;
ctx[CpuRegister.R8] = savedR8;
ctx[CpuRegister.R9] = savedR9;
}
}
private static bool TryReadBatchMapEntry(CpuContext ctx, ulong entryAddress, out BatchMapEntry entry)
{
entry = default;
if (!ctx.TryReadUInt64(entryAddress + OrbisKernelBatchMapEntryStartOffset, out var start) ||
!ctx.TryReadUInt64(entryAddress + OrbisKernelBatchMapEntryOffsetOffset, out var offset) ||
!ctx.TryReadUInt64(entryAddress + OrbisKernelBatchMapEntryLengthOffset, out var length))
{
return false;
}
Span<byte> protection = stackalloc byte[1];
Span<byte> memoryType = stackalloc byte[1];
if (!TryReadCompat(ctx, entryAddress + OrbisKernelBatchMapEntryProtectionOffset, protection) ||
!TryReadCompat(ctx, entryAddress + OrbisKernelBatchMapEntryTypeOffset, memoryType) ||
!TryReadUInt32Compat(ctx, entryAddress + OrbisKernelBatchMapEntryOperationOffset, out var operation))
{
return false;
}
entry = new BatchMapEntry(start, offset, length, protection[0], memoryType[0], unchecked((int)operation));
return true;
}
private static bool TryApplyMappedRegionProtectionLocked(
ulong address,
ulong length,
int protection,
int? memoryType = null)
{
if (!_mappedRegions.TryGetValue(address, out var region) || region.Length != length)
{
return false;
}
_mappedRegions[address] = region with { Protection = protection };
if (memoryType.HasValue &&
region.DirectStart != 0 &&
_directAllocations.TryGetValue(region.DirectStart, out var allocation))
{
_directAllocations[region.DirectStart] = allocation with { MemoryType = memoryType.Value };
}
return true;
}
private static bool TryFindVirtualQueryRegionLocked(ulong queryAddress, bool findNext, out MappedRegion region)
{
region = default;
var foundNext = false;
foreach (var candidate in _mappedRegions.Values)
{
if (TryAddU64(candidate.Address, candidate.Length, out var candidateEnd) &&
queryAddress >= candidate.Address &&
queryAddress < candidateEnd)
{
region = candidate;
return true;
}
if (!findNext || candidate.Address < queryAddress)
{
continue;
}
if (!foundNext || candidate.Address < region.Address)
{
region = candidate;
foundNext = true;
}
}
return foundNext;
}
private static void TraceDirectMemoryCall(
CpuContext ctx,
string operation,
ulong length,
ulong alignment,
int memoryType,
ulong outAddress,
ulong selectedAddress = 0,
OrbisGen2Result? result = null)
{
if (!ShouldTraceDirectMemory())
{
return;
}
var returnRip = 0UL;
var stackPointer = ctx[CpuRegister.Rsp];
if (stackPointer != 0)
{
_ = ctx.TryReadUInt64(stackPointer, out returnRip);
}
Console.Error.WriteLine(
$"[LOADER][TRACE] {operation}: ret=0x{returnRip:X16} len=0x{length:X16} align=0x{alignment:X16} type=0x{memoryType:X8} out=0x{outAddress:X16} selected=0x{selectedAddress:X16} result={result?.ToString() ?? "<pending>"}");
}
private static bool ShouldTraceDirectMemory()
{
return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_DIRECT_MEMORY"), "1", StringComparison.Ordinal);
}
private static bool TryAllocateDirectMemoryLocked(
ulong searchStart,
ulong searchEnd,
ulong length,
ulong alignment,
int memoryType,
ulong allocationLimit,
out ulong selectedAddress)
{
selectedAddress = 0;
if (length == 0 || searchStart >= searchEnd)
{
return false;
}
var effectiveAlignment = alignment == 0 ? 0x1000UL : alignment;
if (!TryFindAllocatableDirectMemoryRangeLocked(searchStart, searchEnd, length, effectiveAlignment, allocationLimit, out var freePosition) ||
!TryAddU64(freePosition, length, out var endAddress))
{
return false;
}
_directAllocations[freePosition] = new DirectAllocation(freePosition, length, memoryType);
_nextPhysicalAddress = endAddress;
selectedAddress = freePosition;
return true;
}
private static bool TryFindAllocatableDirectMemoryRangeLocked(
ulong searchStart,
ulong searchEnd,
ulong length,
ulong alignment,
ulong allocationLimit,
out ulong selectedAddress)
{
selectedAddress = 0;
if (length == 0 || searchStart >= searchEnd)
{
return false;
}
var effectiveEnd = Math.Min(searchEnd, allocationLimit);
var candidate = AlignUp(searchStart, alignment);
if (candidate >= effectiveEnd)
{
return false;
}
var allocations = new List<DirectAllocation>(_directAllocations.Values);
allocations.Sort(static (left, right) => left.Start.CompareTo(right.Start));
foreach (var allocation in allocations)
{
if (!TryAddU64(allocation.Start, allocation.Length, out var allocationEnd))
{
return false;
}
if (allocationEnd <= candidate)
{
continue;
}
var gapEnd = Math.Min(allocation.Start, effectiveEnd);
if (candidate < gapEnd &&
TryAddU64(candidate, length, out var candidateEnd) &&
candidateEnd <= gapEnd)
{
selectedAddress = candidate;
return true;
}
if (allocation.Start >= effectiveEnd)
{
break;
}
candidate = AlignUp(Math.Max(candidate, allocationEnd), alignment);
if (candidate >= effectiveEnd)
{
return false;
}
}
if (!TryAddU64(candidate, length, out var endAddress) || endAddress > effectiveEnd)
{
return false;
}
selectedAddress = candidate;
return true;
}
private static bool TryFindAvailableDirectMemorySpanLocked(
ulong searchStart,
ulong searchEnd,
ulong alignment,
out ulong spanStart,
out ulong spanLength)
{
spanStart = 0;
spanLength = 0;
if (searchStart >= searchEnd)
{
return false;
}
var effectiveEnd = Math.Min(searchEnd, DirectMemorySizeBytes);
var candidate = AlignUp(searchStart, alignment);
if (candidate >= effectiveEnd)
{
return false;
}
var allocations = new List<DirectAllocation>(_directAllocations.Values);
allocations.Sort(static (left, right) => left.Start.CompareTo(right.Start));
foreach (var allocation in allocations)
{
if (!TryAddU64(allocation.Start, allocation.Length, out var allocationEnd))
{
return false;
}
if (allocationEnd <= candidate)
{
continue;
}
var gapEnd = Math.Min(allocation.Start, effectiveEnd);
if (candidate < gapEnd)
{
spanStart = candidate;
spanLength = gapEnd - candidate;
return true;
}
if (allocation.Start >= effectiveEnd)
{
break;
}
candidate = AlignUp(Math.Max(candidate, allocationEnd), alignment);
if (candidate >= effectiveEnd)
{
return false;
}
}
spanStart = candidate;
spanLength = effectiveEnd - candidate;
return spanLength != 0;
}
private static ulong GetDirectMemoryHighWaterMarkLocked()
{
ulong highWaterMark = 0;
foreach (var allocation in _directAllocations.Values)
{
if (!TryAddU64(allocation.Start, allocation.Length, out var endAddress))
{
return ulong.MaxValue;
}
if (endAddress > highWaterMark)
{
highWaterMark = endAddress;
}
}
return highWaterMark;
}
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 TryAllocateLibcHeap(ulong requestedSize, nuint alignment, bool zeroFill, out ulong address)
{
address = 0;
return TryConvertAllocationSize(requestedSize, out var size) &&
TryAllocateLibcHeapCore(size, alignment, zeroFill, out address);
}
private static unsafe bool TryAllocateLibcHeapCore(nuint requestedSize, nuint alignment, bool zeroFill, out ulong address)
{
address = 0;
alignment = NormalizeLibcAlignment(alignment);
var actualSize = requestedSize == 0 ? 1u : requestedSize;
nuint totalSize;
try
{
checked
{
totalSize = actualSize + alignment - 1 + (nuint)IntPtr.Size;
}
}
catch (OverflowException)
{
return false;
}
nint baseAddress;
try
{
baseAddress = Marshal.AllocHGlobal(checked((nint)totalSize));
}
catch (OutOfMemoryException)
{
return false;
}
catch (OverflowException)
{
return false;
}
if (baseAddress == 0)
{
return false;
}
var alignedAddress = AlignUp(unchecked((ulong)baseAddress) + (ulong)IntPtr.Size, (ulong)alignment);
lock (_libcAllocGate)
{
_libcAllocations[alignedAddress] = new LibcHeapAllocation(baseAddress, actualSize, alignment);
}
try
{
if (zeroFill)
{
NativeMemory.Clear((void*)alignedAddress, actualSize);
}
}
catch
{
FreeLibcHeap(alignedAddress);
return false;
}
address = alignedAddress;
return true;
}
private static unsafe bool TryReallocateLibcHeap(ulong existingAddress, ulong requestedSize, out ulong resizedAddress)
{
resizedAddress = 0;
if (existingAddress == 0)
{
return TryAllocateLibcHeap(requestedSize, DefaultLibcHeapAlignment, zeroFill: false, out resizedAddress);
}
if (requestedSize == 0)
{
FreeLibcHeap(existingAddress);
return true;
}
LibcHeapAllocation allocation;
lock (_libcAllocGate)
{
if (!_libcAllocations.TryGetValue(existingAddress, out allocation))
{
return false;
}
}
if (!TryAllocateLibcHeap(requestedSize, allocation.Alignment, zeroFill: false, out resizedAddress))
{
return false;
}
var bytesToCopy = Math.Min(allocation.Size, (nuint)requestedSize);
Buffer.MemoryCopy(
source: (void*)existingAddress,
destination: (void*)resizedAddress,
destinationSizeInBytes: checked((long)Math.Max(bytesToCopy, 1u)),
sourceBytesToCopy: checked((long)bytesToCopy));
FreeLibcHeap(existingAddress);
return true;
}
private static bool TryAllocateAlignedLibcHeap(ulong alignmentValue, ulong requestedSize, bool requireSizeMultiple, out ulong address)
{
address = 0;
return TryValidateAlignedAllocation(
alignmentValue,
requestedSize,
requireSizeMultiple,
requirePointerSizedAlignment: false,
out var alignment,
out var size) &&
TryAllocateLibcHeapCore(size, alignment, zeroFill: false, out address);
}
private static bool TryValidateAlignedAllocation(
ulong alignmentValue,
ulong requestedSize,
bool requireSizeMultiple,
bool requirePointerSizedAlignment,
out nuint alignment,
out nuint size)
{
alignment = 0;
size = 0;
if (!TryConvertAllocationSize(requestedSize, out size) ||
alignmentValue == 0 ||
alignmentValue > (ulong)nint.MaxValue)
{
return false;
}
alignment = (nuint)alignmentValue;
if (!IsPowerOfTwo(alignment))
{
return false;
}
if (requirePointerSizedAlignment && alignment % (nuint)IntPtr.Size != 0)
{
return false;
}
if (alignment < (nuint)IntPtr.Size)
{
alignment = (nuint)IntPtr.Size;
}
if (requireSizeMultiple && size % alignment != 0)
{
return false;
}
return true;
}
private static void FreeLibcHeap(ulong address)
{
if (address == 0)
{
return;
}
LibcHeapAllocation allocation;
lock (_libcAllocGate)
{
if (!_libcAllocations.Remove(address, out allocation))
{
return;
}
}
Marshal.FreeHGlobal(allocation.BaseAddress);
}
private static bool TryMultiplyAllocationSize(ulong left, ulong right, out nuint size)
{
size = 0;
if (!TryConvertAllocationSize(left, out var leftSize) ||
!TryConvertAllocationSize(right, out var rightSize))
{
return false;
}
try
{
checked
{
size = leftSize * rightSize;
}
}
catch (OverflowException)
{
return false;
}
return true;
}
private static bool TryConvertAllocationSize(ulong requestedSize, out nuint size)
{
size = 0;
if (requestedSize > (ulong)nint.MaxValue)
{
return false;
}
size = (nuint)requestedSize;
return true;
}
private static nuint NormalizeLibcAlignment(nuint alignment)
{
if (alignment < DefaultLibcHeapAlignment)
{
return DefaultLibcHeapAlignment;
}
return alignment;
}
private static bool IsPowerOfTwo(nuint value)
{
return value != 0 && (value & (value - 1)) == 0;
}
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 bool TryWriteOpenDescriptorStat(CpuContext ctx, int fd, ulong statAddress)
{
if (fd is 0 or 1 or 2)
{
var now = DateTime.UtcNow;
LogIoTrace("fstat", $"stdio:{fd}", $"fd={fd} size=0 dir=0");
return TryWriteKernelStat(ctx, statAddress, isDirectory: false, size: 0, now, now, now, $"stdio:{fd}");
}
string? hostPath = null;
bool isDirectory = false;
lock (_fdGate)
{
if (_openDirectories.TryGetValue(fd, out var directory))
{
hostPath = directory.Path;
isDirectory = true;
}
else if (_openFiles.TryGetValue(fd, out var stream))
{
hostPath = stream.Name;
}
}
if (!string.IsNullOrWhiteSpace(hostPath))
{
long size = 0;
if (!isDirectory)
{
try
{
size = new FileInfo(hostPath!).Length;
}
catch (IOException)
{
size = -1;
}
}
LogIoTrace("fstat", hostPath!, $"fd={fd} size={size} dir={(isDirectory ? 1 : 0)}");
}
return !string.IsNullOrWhiteSpace(hostPath) && TryWriteHostPathStat(ctx, statAddress, hostPath!, isDirectory);
}
private static bool TryWriteHostPathStat(CpuContext ctx, ulong statAddress, string hostPath)
{
var isDirectory = Directory.Exists(hostPath);
if (!isDirectory && !File.Exists(hostPath))
{
return false;
}
return TryWriteHostPathStat(ctx, statAddress, hostPath, isDirectory);
}
private static bool TryGetAprFileSize(string hostPath, out ulong size)
{
size = 0;
try
{
if (Directory.Exists(hostPath))
{
size = 65536;
return true;
}
if (!File.Exists(hostPath))
{
return false;
}
var length = new FileInfo(hostPath).Length;
size = length < 0 ? 0UL : unchecked((ulong)length);
return true;
}
catch
{
return false;
}
}
private static bool TryWriteHostPathStat(CpuContext ctx, ulong statAddress, string hostPath, bool isDirectory)
{
if (isDirectory)
{
if (!Directory.Exists(hostPath))
{
return false;
}
}
else if (!File.Exists(hostPath))
{
return false;
}
try
{
var lastAccessUtc = File.GetLastAccessTimeUtc(hostPath);
var lastWriteUtc = File.GetLastWriteTimeUtc(hostPath);
var creationUtc = File.GetCreationTimeUtc(hostPath);
var size = isDirectory ? 65536L : new FileInfo(hostPath).Length;
return TryWriteKernelStat(ctx, statAddress, isDirectory, size, lastAccessUtc, lastWriteUtc, creationUtc, hostPath);
}
catch
{
return false;
}
}
private static bool TryWriteKernelStat(
CpuContext ctx,
ulong statAddress,
bool isDirectory,
long size,
DateTime lastAccessUtc,
DateTime lastWriteUtc,
DateTime creationUtc,
string inodeSeed)
{
Span<byte> payload = stackalloc byte[KernelStatSize];
payload.Clear();
var seedBytes = Encoding.UTF8.GetBytes(inodeSeed);
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStDevOffset..], 0);
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStInoOffset..], ComputeDirectoryEntryHash(seedBytes));
BinaryPrimitives.WriteUInt16LittleEndian(payload[KernelStatStModeOffset..], isDirectory ? KernelStatModeDirectory : KernelStatModeRegular);
BinaryPrimitives.WriteUInt16LittleEndian(payload[KernelStatStNlinkOffset..], 1);
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStUidOffset..], 0);
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStGidOffset..], 0);
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStRdevOffset..], 0);
WriteKernelTimespec(payload[KernelStatStAtimOffset..], lastAccessUtc);
WriteKernelTimespec(payload[KernelStatStMtimOffset..], lastWriteUtc);
WriteKernelTimespec(payload[KernelStatStCtimOffset..], lastWriteUtc);
BinaryPrimitives.WriteInt64LittleEndian(payload[KernelStatStSizeOffset..], size);
BinaryPrimitives.WriteInt64LittleEndian(payload[KernelStatStBlocksOffset..], isDirectory ? 128 : (size + 511) / 512);
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStBlksizeOffset..], isDirectory ? 65536U : 512U);
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStFlagsOffset..], 0);
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStGenOffset..], 0);
BinaryPrimitives.WriteInt32LittleEndian(payload[KernelStatStLspareOffset..], 0);
WriteKernelTimespec(payload[KernelStatStBirthtimOffset..], creationUtc);
return TryWriteCompat(ctx, statAddress, payload);
}
private static void WriteKernelTimespec(Span<byte> destination, DateTime utcTime)
{
var timestamp = utcTime.Kind == DateTimeKind.Utc ? utcTime : utcTime.ToUniversalTime();
var dto = new DateTimeOffset(timestamp);
BinaryPrimitives.WriteInt64LittleEndian(destination, dto.ToUnixTimeSeconds());
var ticksWithinSecond = timestamp.Ticks % TimeSpan.TicksPerSecond;
BinaryPrimitives.WriteInt64LittleEndian(destination[sizeof(long)..], ticksWithinSecond * 100);
}
private static int KernelGetdirentriesCore(CpuContext ctx, int fd, ulong bufferAddress, int requested, ulong basePointerAddress)
{
if (fd < 0 || bufferAddress == 0 || requested < 512)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
OpenDirectory? directory;
lock (_fdGate)
{
_openDirectories.TryGetValue(fd, out directory);
}
if (directory is null)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
var currentIndex = directory.NextIndex;
if (basePointerAddress != 0 && !TryWriteUInt64Compat(ctx, basePointerAddress, (ulong)currentIndex))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (currentIndex >= directory.Entries.Length)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
var entryName = directory.Entries[currentIndex];
directory.NextIndex = currentIndex + 1;
var entryBytes = Encoding.UTF8.GetBytes(entryName);
var nameLength = Math.Min(entryBytes.Length, 255);
var entryPath = Path.Combine(directory.Path, entryName);
var entryType = Directory.Exists(entryPath) ? (byte)4 : (byte)8;
var payload = new byte[512];
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(0, sizeof(uint)), ComputeDirectoryEntryHash(entryBytes.AsSpan(0, nameLength)));
BinaryPrimitives.WriteUInt16LittleEndian(payload.AsSpan(4, sizeof(ushort)), 512);
payload[6] = entryType;
payload[7] = unchecked((byte)nameLength);
entryBytes.AsSpan(0, nameLength).CopyTo(payload.AsSpan(8));
if (!TryWriteCompat(ctx, bufferAddress, payload))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 512;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static string[] EnumerateDirectoryEntries(string hostPath)
{
return Directory.EnumerateFileSystemEntries(hostPath)
.Select(Path.GetFileName)
.Where(static name => !string.IsNullOrEmpty(name))
.OrderBy(static name => name, StringComparer.OrdinalIgnoreCase)
.ToArray()!;
}
private static uint ComputeDirectoryEntryHash(ReadOnlySpan<byte> utf8Name)
{
const uint offsetBasis = 2166136261;
const uint prime = 16777619;
var hash = offsetBasis;
for (var i = 0; i < utf8Name.Length; i++)
{
hash ^= utf8Name[i];
hash *= prime;
}
return hash;
}
private static void LogOpenTrace(string message)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_OPEN"), "1", StringComparison.Ordinal))
{
return;
}
Console.Error.WriteLine($"[LOADER][TRACE] {message}");
}
private static void LogIoTrace(string operation, string path, string detail)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_IO"), "1", StringComparison.Ordinal))
{
return;
}
var filter = Environment.GetEnvironmentVariable("SHARPEMU_LOG_IO_FILTER");
if (!string.IsNullOrWhiteSpace(filter) &&
path.IndexOf(filter, StringComparison.OrdinalIgnoreCase) < 0)
{
return;
}
Console.Error.WriteLine($"[LOADER][TRACE] {operation} path='{path}' {detail}");
}
private static string PreviewIoBytes(byte[] buffer, int count, int maxBytes)
{
if (count <= 0)
{
return string.Empty;
}
var previewLength = Math.Min(count, maxBytes);
var text = Encoding.UTF8.GetString(buffer, 0, previewLength);
return SanitizeTracePreview(text, maxBytes);
}
private static string PreviewIoHex(byte[] buffer, int count, int maxBytes)
{
if (count <= 0)
{
return string.Empty;
}
var previewLength = Math.Min(count, maxBytes);
return Convert.ToHexString(buffer, 0, previewLength);
}
private static string PreviewGuestHex(CpuContext ctx, ulong address, int maxBytes)
{
if (address == 0 || maxBytes <= 0)
{
return string.Empty;
}
var bytes = GC.AllocateUninitializedArray<byte>(maxBytes);
if (!ctx.Memory.TryRead(address, bytes))
{
return "<unreadable>";
}
return Convert.ToHexString(bytes);
}
private static ulong AlignUp(ulong value, ulong alignment)
{
if (alignment <= 1)
{
return value;
}
var mask = alignment - 1;
return (value + mask) & ~mask;
}
private static bool TryAddU64(ulong left, ulong right, out ulong sum)
{
sum = left + right;
return sum >= left;
}
}