[kernel] Add guest memory support

This commit is contained in:
ParantezTech
2026-06-21 23:18:18 +03:00
parent 5d204afd10
commit 08b315b5fc
5 changed files with 612 additions and 53 deletions
+12 -1
View File
@@ -5,7 +5,7 @@ using SharpEmu.HLE;
namespace SharpEmu.Core.Cpu; namespace SharpEmu.Core.Cpu;
public sealed class TrackedCpuMemory : ICpuMemory, ITrackedCpuMemory public sealed class TrackedCpuMemory : ICpuMemory, ITrackedCpuMemory, IGuestMemoryAllocator
{ {
private readonly ICpuMemory _inner; private readonly ICpuMemory _inner;
@@ -39,4 +39,15 @@ public sealed class TrackedCpuMemory : ICpuMemory, ITrackedCpuMemory
return result; return result;
} }
public bool TryAllocateGuestMemory(ulong size, ulong alignment, out ulong address)
{
if (_inner is IGuestMemoryAllocator allocator)
{
return allocator.TryAllocateGuestMemory(size, alignment, out address);
}
address = 0;
return false;
}
} }
@@ -7,13 +7,17 @@ using SharpEmu.HLE;
namespace SharpEmu.Core.Memory; namespace SharpEmu.Core.Memory;
public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryAllocator, IDisposable
{ {
private readonly object _gate = new(); private readonly object _gate = new();
private readonly object _guestAllocationGate = new();
private readonly List<MemoryRegion> _regions = new(); private readonly List<MemoryRegion> _regions = new();
private readonly Dictionary<ulong, ProgramHeaderFlags> _pageProtections = new(); private readonly Dictionary<ulong, ProgramHeaderFlags> _pageProtections = new();
private bool _disposed; private bool _disposed;
private const ulong PageSize = 0x1000; private const ulong PageSize = 0x1000;
private const ulong GuestAllocationArenaAddress = 0x00006000_0000_0000;
private const ulong GuestAllocationArenaSize = 0x0100_0000;
private const ulong GuestAllocationArenaStartOffset = PageSize;
private const ulong LargeDataReserveThreshold = 0x4000_0000UL; // 1 GiB private const ulong LargeDataReserveThreshold = 0x4000_0000UL; // 1 GiB
private const ulong LazyReservePrimeBytes = 0x5000_0000UL; // 1.25 GiB private const ulong LazyReservePrimeBytes = 0x5000_0000UL; // 1.25 GiB
private const ulong LazyReservePrimeChunkBytes = 0x0200_0000UL; // 32 MiB private const ulong LazyReservePrimeChunkBytes = 0x0200_0000UL; // 32 MiB
@@ -29,6 +33,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
private const uint PAGE_READWRITE = 0x04; private const uint PAGE_READWRITE = 0x04;
private const uint PAGE_READONLY = 0x02; private const uint PAGE_READONLY = 0x02;
private ulong _guestAllocationArenaBase;
private ulong _guestAllocationOffset;
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
private static extern void* VirtualAlloc(void* lpAddress, nuint dwSize, uint flAllocationType, uint flProtect); private static extern void* VirtualAlloc(void* lpAddress, nuint dwSize, uint flAllocationType, uint flProtect);
@@ -215,16 +222,61 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
return actualAddress; return actualAddress;
} }
public bool TryAllocateGuestMemory(ulong size, ulong alignment, out ulong address)
{
address = 0;
if (size == 0 || alignment == 0 || (alignment & (alignment - 1)) != 0)
{
return false;
}
lock (_guestAllocationGate)
{
if (_guestAllocationArenaBase == 0)
{
try
{
_guestAllocationArenaBase = AllocateAt(
GuestAllocationArenaAddress,
GuestAllocationArenaSize,
executable: false,
allowAlternative: true);
_guestAllocationOffset = GuestAllocationArenaStartOffset;
}
catch (Exception)
{
return false;
}
}
var alignedOffset = AlignUp(_guestAllocationOffset, alignment);
if (alignedOffset > GuestAllocationArenaSize || size > GuestAllocationArenaSize - alignedOffset)
{
return false;
}
address = _guestAllocationArenaBase + alignedOffset;
_guestAllocationOffset = alignedOffset + size;
return true;
}
}
public void Clear() public void Clear()
{ {
lock (_gate) lock (_guestAllocationGate)
{ {
foreach (var region in _regions) lock (_gate)
{ {
VirtualFree((void*)region.VirtualAddress, 0, MEM_RELEASE); foreach (var region in _regions)
{
VirtualFree((void*)region.VirtualAddress, 0, MEM_RELEASE);
}
_regions.Clear();
_pageProtections.Clear();
} }
_regions.Clear();
_pageProtections.Clear(); _guestAllocationArenaBase = 0;
_guestAllocationOffset = 0;
} }
} }
@@ -0,0 +1,9 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.HLE;
public interface IGuestMemoryAllocator
{
bool TryAllocateGuestMemory(ulong size, ulong alignment, out ulong address);
}
@@ -38,6 +38,13 @@ public static class KernelMemoryCompatExports
private const int OrbisKernelBatchMapEntryProtectionOffset = 24; private const int OrbisKernelBatchMapEntryProtectionOffset = 24;
private const int OrbisKernelBatchMapEntryTypeOffset = 25; private const int OrbisKernelBatchMapEntryTypeOffset = 25;
private const int OrbisKernelBatchMapEntryOperationOffset = 28; private const int OrbisKernelBatchMapEntryOperationOffset = 28;
private const ulong OrbisPageSize = 0x4000;
private const int OrbisProtCpuRead = 0x01;
private const int OrbisProtCpuWrite = 0x02;
private const int OrbisProtCpuExec = 0x04;
private const int OrbisProtGpuRead = 0x10;
private const int OrbisProtGpuWrite = 0x20;
private const int OrbisProtCpuReadWrite = OrbisProtCpuRead | OrbisProtCpuWrite;
private const int SeekSet = 0; private const int SeekSet = 0;
private const int SeekCur = 1; private const int SeekCur = 1;
private const int SeekEnd = 2; private const int SeekEnd = 2;
@@ -47,14 +54,15 @@ public static class KernelMemoryCompatExports
private const int OrbisVirtualQueryInfoSize = 72; private const int OrbisVirtualQueryInfoSize = 72;
private const int OrbisKernelMaximumNameLength = 32; private const int OrbisKernelMaximumNameLength = 32;
private const uint MemCommit = 0x1000; private const uint MemCommit = 0x1000;
private const uint PageNoAccess = 0x01; private const uint HostPageNoAccess = 0x01;
private const uint PageReadOnly = 0x02; private const uint HostPageReadOnly = 0x02;
private const uint PageReadWrite = 0x04; private const uint HostPageReadWrite = 0x04;
private const uint PageWriteCopy = 0x08; private const uint HostPageWriteCopy = 0x08;
private const uint PageExecuteRead = 0x20; private const uint HostPageExecute = 0x10;
private const uint PageExecuteReadWrite = 0x40; private const uint HostPageExecuteRead = 0x20;
private const uint PageExecuteWriteCopy = 0x80; private const uint HostPageExecuteReadWrite = 0x40;
private const uint PageGuard = 0x100; private const uint HostPageExecuteWriteCopy = 0x80;
private const uint HostPageGuard = 0x100;
private const int Enomem = 12; private const int Enomem = 12;
private const int Einval = 22; private const int Einval = 22;
private const int Erange = 34; private const int Erange = 34;
@@ -121,6 +129,10 @@ public static class KernelMemoryCompatExports
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
private static extern nuint VirtualQuery(nint lpAddress, out MemoryBasicInformation lpBuffer, nuint dwLength); private static extern nuint VirtualQuery(nint lpAddress, out MemoryBasicInformation lpBuffer, nuint dwLength);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool VirtualProtect(nint lpAddress, nuint dwSize, uint flNewProtect, out uint lpflOldProtect);
private sealed class OpenDirectory private sealed class OpenDirectory
{ {
public required string Path { get; init; } public required string Path { get; init; }
@@ -130,9 +142,59 @@ public static class KernelMemoryCompatExports
private readonly record struct DirectAllocation(ulong Start, ulong Length, int MemoryType); 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 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 MappedRegion(ulong Address, ulong Length, int Protection, bool IsFlexible, bool IsDirect, ulong DirectStart);
private readonly record struct BatchMapEntry(ulong Start, ulong Offset, ulong Length, byte Protection, byte Type, int Operation); private readonly record struct BatchMapEntry(ulong Start, ulong Offset, ulong Length, byte Protection, byte Type, int Operation);
internal static bool TryAllocateHleData(
CpuContext ctx,
ulong length,
ulong alignment,
out ulong address)
{
address = 0;
if (length == 0 || length > int.MaxValue)
{
return false;
}
var mappedLength = AlignUp(length, 0x1000UL);
var effectiveAlignment = Math.Max(alignment, 0x1000UL);
lock (_memoryGate)
{
var desiredAddress = AlignUp(
_nextVirtualAddress == 0 ? 0x1_0000_0000UL : _nextVirtualAddress,
effectiveAlignment);
if (!TryReserveGuestVirtualRange(ctx, desiredAddress, mappedLength, OrbisProtCpuReadWrite, out address) ||
address == 0)
{
return false;
}
_nextVirtualAddress = Math.Max(_nextVirtualAddress, address + mappedLength);
_mappedRegions[address] = new MappedRegion(
address,
mappedLength,
OrbisProtCpuReadWrite,
IsFlexible: false,
IsDirect: false,
DirectStart: 0);
}
var zeroes = new byte[(int)Math.Min(mappedLength, (ulong)MemsetChunkSize)];
for (ulong offset = 0; offset < mappedLength;)
{
var chunkLength = (int)Math.Min((ulong)zeroes.Length, mappedLength - offset);
if (!ctx.Memory.TryWrite(address + offset, zeroes.AsSpan(0, chunkLength)))
{
return false;
}
offset += (ulong)chunkLength;
}
return true;
}
[SysAbiExport( [SysAbiExport(
Nid = "8zTFvBIAIN8", Nid = "8zTFvBIAIN8",
ExportName = "memset", ExportName = "memset",
@@ -1238,6 +1300,13 @@ public static class KernelMemoryCompatExports
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int KernelCloseUnderscore(CpuContext ctx) => KernelCloseCore(ctx, unchecked((int)ctx[CpuRegister.Rdi])); public static int KernelCloseUnderscore(CpuContext ctx) => KernelCloseCore(ctx, unchecked((int)ctx[CpuRegister.Rdi]));
[SysAbiExport(
Nid = "bY-PO6JhzhQ",
ExportName = "close",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixClose(CpuContext ctx) => KernelCloseCore(ctx, unchecked((int)ctx[CpuRegister.Rdi]));
[SysAbiExport( [SysAbiExport(
Nid = "UK2Tl2DWUns", Nid = "UK2Tl2DWUns",
ExportName = "sceKernelClose", ExportName = "sceKernelClose",
@@ -1444,6 +1513,20 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "AqBioC2vF3I",
ExportName = "read",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixRead(CpuContext ctx) => KernelReadUnderscore(ctx);
[SysAbiExport(
Nid = "Cg4srZ6TKbU",
ExportName = "sceKernelRead",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelRead(CpuContext ctx) => KernelReadUnderscore(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "Oy6IpwgtYOk", Nid = "Oy6IpwgtYOk",
ExportName = "lseek", ExportName = "lseek",
@@ -1629,6 +1712,20 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "FN4gaPmuFV8",
ExportName = "write",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixWrite(CpuContext ctx) => KernelWriteUnderscore(ctx);
[SysAbiExport(
Nid = "4wSze92BhLI",
ExportName = "sceKernelWrite",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelWrite(CpuContext ctx) => KernelWriteUnderscore(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "lLMT9vJAck0", Nid = "lLMT9vJAck0",
ExportName = "clock_gettime", ExportName = "clock_gettime",
@@ -2172,7 +2269,13 @@ public static class KernelMemoryCompatExports
} }
_nextVirtualAddress = Math.Max(_nextVirtualAddress, mappedAddress + length); _nextVirtualAddress = Math.Max(_nextVirtualAddress, mappedAddress + length);
_mappedRegions[mappedAddress] = new MappedRegion(mappedAddress, length, protection, IsFlexible: false, DirectStart: directMemoryStart); _mappedRegions[mappedAddress] = new MappedRegion(
mappedAddress,
length,
protection,
IsFlexible: false,
IsDirect: true,
DirectStart: directMemoryStart);
} }
if (!ctx.TryWriteUInt64(inOutAddressPointer, mappedAddress)) if (!ctx.TryWriteUInt64(inOutAddressPointer, mappedAddress))
@@ -2245,7 +2348,13 @@ public static class KernelMemoryCompatExports
_nextVirtualAddress = Math.Max(_nextVirtualAddress, mappedAddress + length); _nextVirtualAddress = Math.Max(_nextVirtualAddress, mappedAddress + length);
_allocatedFlexibleBytes = Math.Min(FlexibleMemorySizeBytes, _allocatedFlexibleBytes + length); _allocatedFlexibleBytes = Math.Min(FlexibleMemorySizeBytes, _allocatedFlexibleBytes + length);
_mappedRegions[mappedAddress] = new MappedRegion(mappedAddress, length, protection, IsFlexible: true, DirectStart: 0); _mappedRegions[mappedAddress] = new MappedRegion(
mappedAddress,
length,
protection,
IsFlexible: true,
IsDirect: false,
DirectStart: 0);
} }
if (!ctx.TryWriteUInt64(inOutAddressPointer, mappedAddress)) if (!ctx.TryWriteUInt64(inOutAddressPointer, mappedAddress))
@@ -2344,8 +2453,7 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
if (region.DirectStart != 0 && if (region.IsDirect && TryFindDirectAllocationLocked(region.DirectStart, out var allocation))
_directAllocations.TryGetValue(region.DirectStart, out var allocation))
{ {
memoryType = allocation.MemoryType; memoryType = allocation.MemoryType;
} }
@@ -2363,7 +2471,7 @@ public static class KernelMemoryCompatExports
stateFlags |= 0x01u; stateFlags |= 0x01u;
} }
if (region.DirectStart != 0) if (region.IsDirect)
{ {
stateFlags |= 0x02u; stateFlags |= 0x02u;
} }
@@ -2377,7 +2485,7 @@ public static class KernelMemoryCompatExports
BinaryPrimitives.WriteInt32LittleEndian(payload[28..32], memoryType); BinaryPrimitives.WriteInt32LittleEndian(payload[28..32], memoryType);
payload[32] = unchecked((byte)stateFlags); payload[32] = unchecked((byte)stateFlags);
var name = region.DirectStart != 0 var name = region.IsDirect
? "direct" ? "direct"
: region.IsFlexible : region.IsFlexible
? "flexible" ? "flexible"
@@ -2495,12 +2603,19 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
if (!TryNormalizeProtectRange(address, length, out var alignedAddress, out var alignedLength))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryProtectHostRange(alignedAddress, alignedLength, protection))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
lock (_memoryGate) lock (_memoryGate)
{ {
if (!TryApplyMappedRegionProtectionLocked(address, length, protection)) _ = TryApplyMappedRegionProtectionLocked(alignedAddress, alignedLength, protection);
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
} }
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
@@ -2522,12 +2637,19 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
if (!TryNormalizeProtectRange(address, length, out var alignedAddress, out var alignedLength))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryProtectHostRange(alignedAddress, alignedLength, protection))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
lock (_memoryGate) lock (_memoryGate)
{ {
if (!TryApplyMappedRegionProtectionLocked(address, length, protection, memoryType)) _ = TryApplyMappedRegionProtectionLocked(alignedAddress, alignedLength, protection, memoryType);
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
} }
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
@@ -3562,7 +3684,7 @@ public static class KernelMemoryCompatExports
return false; return false;
} }
var executable = (protection & 0x04) != 0; var executable = (protection & OrbisProtCpuExec) != 0;
var invokeArgs = allocateAtHasAllowAlternativeArg var invokeArgs = allocateAtHasAllowAlternativeArg
? new object[] { desiredAddress, length, executable, true } ? new object[] { desiredAddress, length, executable, true }
: new object[] { desiredAddress, length, executable }; : new object[] { desiredAddress, length, executable };
@@ -3692,6 +3814,25 @@ public static class KernelMemoryCompatExports
return temp0Root; return temp0Root;
} }
var hostappRoot = ResolveHostappRoot();
if (guestPath.StartsWith("/hostapp/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["/hostapp/".Length..]);
return Path.Combine(hostappRoot, relative);
}
if (guestPath.StartsWith("hostapp/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["hostapp/".Length..]);
return Path.Combine(hostappRoot, relative);
}
if (string.Equals(guestPath, "/hostapp", StringComparison.OrdinalIgnoreCase) ||
string.Equals(guestPath, "hostapp", StringComparison.OrdinalIgnoreCase))
{
return hostappRoot;
}
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR"); var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
if (!string.IsNullOrWhiteSpace(app0Root)) if (!string.IsNullOrWhiteSpace(app0Root))
{ {
@@ -3775,6 +3916,25 @@ public static class KernelMemoryCompatExports
return root; return root;
} }
private static string ResolveHostappRoot()
{
const string hostappVariableName = "SHARPEMU_HOSTAPP_DIR";
var configuredRoot = Environment.GetEnvironmentVariable(hostappVariableName);
string root;
if (!string.IsNullOrWhiteSpace(configuredRoot))
{
root = Path.GetFullPath(configuredRoot);
}
else
{
root = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "logs", "hostapp"));
Environment.SetEnvironmentVariable(hostappVariableName, root);
}
Directory.CreateDirectory(root);
return root;
}
private static void EnsureOpenParentDirectoryExists(string guestPath, string hostPath, int flags) private static void EnsureOpenParentDirectoryExists(string guestPath, string hostPath, int flags)
{ {
if (string.IsNullOrWhiteSpace(hostPath)) if (string.IsNullOrWhiteSpace(hostPath))
@@ -4336,23 +4496,171 @@ public static class KernelMemoryCompatExports
int protection, int protection,
int? memoryType = null) int? memoryType = null)
{ {
if (!_mappedRegions.TryGetValue(address, out var region) || region.Length != length) if (length == 0 || !TryAddU64(address, length, out var endAddress))
{ {
return false; return false;
} }
_mappedRegions[address] = region with { Protection = protection }; var affected = new List<MappedRegion>();
var cursor = address;
if (memoryType.HasValue && foreach (var region in _mappedRegions.Values.OrderBy(static region => region.Address))
region.DirectStart != 0 &&
_directAllocations.TryGetValue(region.DirectStart, out var allocation))
{ {
_directAllocations[region.DirectStart] = allocation with { MemoryType = memoryType.Value }; if (!TryAddU64(region.Address, region.Length, out var regionEnd) || regionEnd <= cursor)
{
continue;
}
if (region.Address > cursor)
{
return false;
}
affected.Add(region);
cursor = regionEnd >= endAddress ? endAddress : regionEnd;
if (cursor == endAddress)
{
break;
}
}
if (cursor != endAddress)
{
return false;
}
foreach (var region in affected)
{
_mappedRegions.Remove(region.Address);
}
foreach (var region in affected)
{
var regionEnd = region.Address + region.Length;
var protectStart = Math.Max(region.Address, address);
var protectEnd = Math.Min(regionEnd, endAddress);
if (region.Address < protectStart)
{
AddMappedRegionSliceLocked(region, region.Address, protectStart, region.Protection);
}
AddMappedRegionSliceLocked(region, protectStart, protectEnd, protection);
if (protectEnd < regionEnd)
{
AddMappedRegionSliceLocked(region, protectEnd, regionEnd, region.Protection);
}
if (memoryType.HasValue && region.IsDirect && TryFindDirectAllocationLocked(region.DirectStart, out var allocation))
{
_directAllocations[allocation.Start] = allocation with { MemoryType = memoryType.Value };
}
} }
return true; return true;
} }
private static void AddMappedRegionSliceLocked(
MappedRegion source,
ulong start,
ulong end,
int protection)
{
if (end <= start)
{
return;
}
var directStart = source.IsDirect
? unchecked(source.DirectStart + (start - source.Address))
: 0UL;
_mappedRegions[start] = source with
{
Address = start,
Length = end - start,
Protection = protection,
DirectStart = directStart,
};
}
private static bool TryFindDirectAllocationLocked(ulong directStart, out DirectAllocation allocation)
{
foreach (var candidate in _directAllocations.Values)
{
if (!TryAddU64(candidate.Start, candidate.Length, out var candidateEnd))
{
continue;
}
if (directStart >= candidate.Start && directStart < candidateEnd)
{
allocation = candidate;
return true;
}
}
allocation = default;
return false;
}
private static bool TryNormalizeProtectRange(
ulong address,
ulong length,
out ulong alignedAddress,
out ulong alignedLength)
{
alignedAddress = 0;
alignedLength = 0;
if (length == 0 || !TryAddU64(address, length, out var endAddress))
{
return false;
}
alignedAddress = AlignDown(address, OrbisPageSize);
var alignedEnd = AlignUp(endAddress, OrbisPageSize);
alignedLength = alignedEnd - alignedAddress;
return alignedLength != 0;
}
private static bool TryProtectHostRange(ulong address, ulong length, int orbisProtection)
{
if (length == 0 || length > nuint.MaxValue)
{
return false;
}
var hostProtection = ResolveHostProtection(orbisProtection);
if (!VirtualProtect((nint)address, (nuint)length, hostProtection, out _))
{
return false;
}
return true;
}
private static uint ResolveHostProtection(int orbisProtection)
{
var read = (orbisProtection & (OrbisProtCpuRead | OrbisProtGpuRead)) != 0;
var write = (orbisProtection & (OrbisProtCpuWrite | OrbisProtGpuWrite)) != 0;
var execute = (orbisProtection & OrbisProtCpuExec) != 0;
if (execute)
{
return write
? HostPageExecuteReadWrite
: read
? HostPageExecuteRead
: HostPageExecute;
}
return write
? HostPageReadWrite
: read
? HostPageReadOnly
: HostPageNoAccess;
}
private static bool TryFindVirtualQueryRegionLocked(ulong queryAddress, bool findNext, out MappedRegion region) private static bool TryFindVirtualQueryRegionLocked(ulong queryAddress, bool findNext, out MappedRegion region)
{ {
region = default; region = default;
@@ -4907,13 +5215,13 @@ public static class KernelMemoryCompatExports
private static bool HasRequiredProtection(uint protect, bool writeAccess) private static bool HasRequiredProtection(uint protect, bool writeAccess)
{ {
if ((protect & (PageNoAccess | PageGuard)) != 0) if ((protect & (HostPageNoAccess | HostPageGuard)) != 0)
{ {
return false; return false;
} }
const uint readableMask = PageReadOnly | PageReadWrite | PageWriteCopy | PageExecuteRead | PageExecuteReadWrite | PageExecuteWriteCopy; const uint readableMask = HostPageReadOnly | HostPageReadWrite | HostPageWriteCopy | HostPageExecuteRead | HostPageExecuteReadWrite | HostPageExecuteWriteCopy;
const uint writableMask = PageReadWrite | PageWriteCopy | PageExecuteReadWrite | PageExecuteWriteCopy; const uint writableMask = HostPageReadWrite | HostPageWriteCopy | HostPageExecuteReadWrite | HostPageExecuteWriteCopy;
var expected = writeAccess ? writableMask : readableMask; var expected = writeAccess ? writableMask : readableMask;
return (protect & expected) != 0; return (protect & expected) != 0;
} }
@@ -5233,6 +5541,17 @@ public static class KernelMemoryCompatExports
return (value + mask) & ~mask; return (value + mask) & ~mask;
} }
private static ulong AlignDown(ulong value, ulong alignment)
{
if (alignment <= 1)
{
return value;
}
var mask = alignment - 1;
return value & ~mask;
}
private static bool TryAddU64(ulong left, ulong right, out ulong sum) private static bool TryAddU64(ulong left, ulong right, out ulong sum)
{ {
sum = left + right; sum = left + right;
@@ -25,6 +25,7 @@ public static class KernelRuntimeCompatExports
private const ulong TlsNewReplaceOffset = 0x300; private const ulong TlsNewReplaceOffset = 0x300;
private const int MallocReplaceSize = 0x70; private const int MallocReplaceSize = 0x70;
private const int NewReplaceSize = 0x68; private const int NewReplaceSize = 0x68;
private const int OrbisTimesecSize = sizeof(long) + sizeof(uint) + sizeof(uint);
private const ulong ModuleInfoHandleOffset = 0x108; private const ulong ModuleInfoHandleOffset = 0x108;
private const ulong ModuleInfoNameOffset = 0x10; private const ulong ModuleInfoNameOffset = 0x10;
private const int ModuleInfoNameMaxBytes = 64; private const int ModuleInfoNameMaxBytes = 64;
@@ -52,6 +53,7 @@ public static class KernelRuntimeCompatExports
private static readonly object _prtApertureGate = new(); private static readonly object _prtApertureGate = new();
private static readonly (ulong Base, ulong Size)[] _prtApertures = new (ulong Base, ulong Size)[3]; private static readonly (ulong Base, ulong Size)[] _prtApertures = new (ulong Base, ulong Size)[3];
private static int _stackChkFailCount; private static int _stackChkFailCount;
private static long _usleepTraceCount;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate ulong RdtscDelegate(); private delegate ulong RdtscDelegate();
@@ -64,33 +66,52 @@ public static class KernelRuntimeCompatExports
public static int KernelUsleep(CpuContext ctx) public static int KernelUsleep(CpuContext ctx)
{ {
var micros = ctx[CpuRegister.Rdi]; var micros = ctx[CpuRegister.Rdi];
TraceUsleepSpin(ctx, micros);
if (micros == 0) if (micros == 0)
{ {
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
var sleepMilliseconds = (int)Math.Min(micros / 1000UL, int.MaxValue); if (micros < 1000)
if (sleepMilliseconds > 0)
{ {
Thread.Sleep(sleepMilliseconds); Thread.Yield();
} }
else
var remainingMicros = micros % 1000UL;
if (remainingMicros > 0)
{ {
var targetTicks = (long)((double)remainingMicros * Stopwatch.Frequency / 1_000_000.0); var sleepMilliseconds = (int)Math.Min((micros + 999UL) / 1000UL, int.MaxValue);
var spin = Stopwatch.StartNew(); Thread.Sleep(sleepMilliseconds);
while (spin.ElapsedTicks < targetTicks)
{
Thread.SpinWait(16);
}
} }
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static void TraceUsleepSpin(CpuContext ctx, ulong micros)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_USLEEP"), "1", StringComparison.Ordinal))
{
return;
}
var count = Interlocked.Increment(ref _usleepTraceCount);
if (count > 32 && count % 10000 != 0)
{
return;
}
var rbx = ctx[CpuRegister.Rbx];
var lockAddress = rbx == 0 ? 0 : rbx + 0xF78;
var lockText = "unreadable";
if (lockAddress != 0 && ctx.TryReadUInt64(lockAddress, out var lockValue))
{
lockText = $"0x{lockValue:X16}";
}
Console.Error.WriteLine(
$"[LOADER][TRACE] usleep#{count}: usec={micros} rbx=0x{rbx:X16} lock@+F78=0x{lockAddress:X16}:{lockText} r13=0x{ctx[CpuRegister.R13]:X16} r14=0x{ctx[CpuRegister.R14]:X16} r15=0x{ctx[CpuRegister.R15]:X16}");
}
[SysAbiExport( [SysAbiExport(
Nid = "QBi7HCK03hw", Nid = "QBi7HCK03hw",
ExportName = "sceKernelClockGettime", ExportName = "sceKernelClockGettime",
@@ -156,6 +177,37 @@ public static class KernelRuntimeCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "n88vx3C5nW8",
ExportName = "gettimeofday",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixGettimeofday(CpuContext ctx)
{
var timeAddress = ctx[CpuRegister.Rdi];
var timezoneAddress = ctx[CpuRegister.Rsi];
var now = DateTimeOffset.UtcNow;
var seconds = now.ToUnixTimeSeconds();
var microseconds = (now.Ticks % TimeSpan.TicksPerSecond) / 10;
if (timeAddress != 0 &&
(!ctx.TryWriteUInt64(timeAddress, unchecked((ulong)seconds)) ||
!ctx.TryWriteUInt64(timeAddress + sizeof(long), unchecked((ulong)microseconds))))
{
return -1;
}
if (timezoneAddress != 0 &&
(!TryWriteInt32(ctx, timezoneAddress, 0) ||
!TryWriteInt32(ctx, timezoneAddress + sizeof(int), 0)))
{
return -1;
}
ctx[CpuRegister.Rax] = 0;
return 0;
}
[SysAbiExport( [SysAbiExport(
Nid = "-2IRUCO--PM", Nid = "-2IRUCO--PM",
ExportName = "sceKernelReadTsc", ExportName = "sceKernelReadTsc",
@@ -552,6 +604,17 @@ public static class KernelRuntimeCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "jh+8XiK4LeE",
ExportName = "sceKernelIsAddressSanitizerEnabled",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelIsAddressSanitizerEnabled(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport( [SysAbiExport(
Nid = "ca7v6Cxulzs", Nid = "ca7v6Cxulzs",
ExportName = "sceKernelSetGPO", ExportName = "sceKernelSetGPO",
@@ -1024,6 +1087,111 @@ public static class KernelRuntimeCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "-o5uEDpN+oY",
ExportName = "sceKernelConvertUtcToLocaltime",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelConvertUtcToLocaltime(CpuContext ctx)
{
var utcSeconds = unchecked((long)ctx[CpuRegister.Rdi]);
var localTimeAddress = ctx[CpuRegister.Rsi];
var timesecAddress = ctx[CpuRegister.Rdx];
var dstSecondsAddress = ctx[CpuRegister.Rcx];
if (localTimeAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var utc = DateTimeOffset.FromUnixTimeSeconds(utcSeconds);
var local = TimeZoneInfo.ConvertTime(utc, TimeZoneInfo.Local);
var offset = local.Offset;
var localSeconds = utcSeconds + (long)offset.TotalSeconds;
var dstSeconds = TimeZoneInfo.Local.IsDaylightSavingTime(local.DateTime)
? (uint)Math.Max(0, TimeZoneInfo.Local.GetAdjustmentRules()
.Where(rule => rule.DateStart <= local.Date && rule.DateEnd >= local.Date)
.Select(rule => rule.DaylightDelta.TotalSeconds)
.DefaultIfEmpty(0)
.Max())
: 0u;
var westSeconds = unchecked((uint)(int)offset.TotalSeconds);
if (!ctx.TryWriteUInt64(localTimeAddress, unchecked((ulong)localSeconds)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (timesecAddress != 0)
{
Span<byte> timesec = stackalloc byte[OrbisTimesecSize];
BinaryPrimitives.WriteInt64LittleEndian(timesec, utcSeconds);
BinaryPrimitives.WriteUInt32LittleEndian(timesec.Slice(sizeof(long), sizeof(uint)), westSeconds);
BinaryPrimitives.WriteUInt32LittleEndian(timesec.Slice(sizeof(long) + sizeof(uint), sizeof(uint)), dstSeconds);
if (!ctx.Memory.TryWrite(timesecAddress, timesec))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
if (dstSecondsAddress != 0 && !ctx.TryWriteUInt64(dstSecondsAddress, dstSeconds))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "0NTHN1NKONI",
ExportName = "sceKernelConvertLocaltimeToUtc",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelConvertLocaltimeToUtc(CpuContext ctx)
{
var localSeconds = unchecked((long)ctx[CpuRegister.Rdi]);
var utcTimeAddress = ctx[CpuRegister.Rdx];
var timezoneAddress = ctx[CpuRegister.Rcx];
var dstSecondsAddress = ctx[CpuRegister.R8];
if (timezoneAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var localDate = DateTimeOffset.FromUnixTimeSeconds(localSeconds).DateTime;
var offset = TimeZoneInfo.Local.GetUtcOffset(localDate);
var utcSeconds = localSeconds - (long)offset.TotalSeconds;
var dstSeconds = TimeZoneInfo.Local.IsDaylightSavingTime(localDate)
? (int)Math.Max(0, TimeZoneInfo.Local.GetAdjustmentRules()
.Where(rule => rule.DateStart <= localDate.Date && rule.DateEnd >= localDate.Date)
.Select(rule => rule.DaylightDelta.TotalSeconds)
.DefaultIfEmpty(0)
.Max())
: 0;
var minutesWest = unchecked((int)-offset.TotalMinutes);
if (!TryWriteInt32(ctx, timezoneAddress, minutesWest) ||
!TryWriteInt32(ctx, timezoneAddress + sizeof(int), dstSeconds / 60))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (utcTimeAddress != 0 && !ctx.TryWriteUInt64(utcTimeAddress, unchecked((ulong)utcSeconds)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (dstSecondsAddress != 0 && !TryWriteInt32(ctx, dstSecondsAddress, dstSeconds))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport( [SysAbiExport(
Nid = "vYU8P9Td2Zo", Nid = "vYU8P9Td2Zo",
ExportName = "sceKernelAioInitializeImpl", ExportName = "sceKernelAioInitializeImpl",