mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-15 15:36:11 +08:00
5242 lines
171 KiB
C#
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;
|
|
}
|
|
}
|