diff --git a/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs
index 604e34067..bac5c6c15 100644
--- a/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs
+++ b/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs
@@ -115,7 +115,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
- public bool IsHypervisorAvailable => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
+ public bool IsHypervisorAvailable => RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
public bool DirectoryChanged
{
diff --git a/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs b/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs
index 99e4c0479..7c8067189 100644
--- a/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs
+++ b/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs
@@ -32,6 +32,11 @@ namespace Ryujinx.Cpu.AppleHv
{
}
+ ///
+ public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize)
+ {
+ }
+
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
{
return new DummyDiskCacheLoadState();
diff --git a/src/Ryujinx.Cpu/AppleHv/HvEngine.cs b/src/Ryujinx.Cpu/AppleHv/HvEngine.cs
index c3c1a4484..1a50f8d19 100644
--- a/src/Ryujinx.Cpu/AppleHv/HvEngine.cs
+++ b/src/Ryujinx.Cpu/AppleHv/HvEngine.cs
@@ -14,7 +14,7 @@ namespace Ryujinx.Cpu.AppleHv
}
///
- public ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit)
+ public ICpuContext CreateCpuContext(ICpuMemoryManager memoryManager, bool for64Bit)
{
return new HvCpuContext(_tickSource, memoryManager, for64Bit);
}
diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs
index 2f9743ab4..41e5ec7de 100644
--- a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs
+++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs
@@ -16,7 +16,7 @@ namespace Ryujinx.Cpu.AppleHv
/// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table.
///
[SupportedOSPlatform("macos")]
- public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
+ public class HvMemoryManager : MemoryManagerBase, ICpuMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
@@ -182,6 +182,11 @@ namespace Ryujinx.Cpu.AppleHv
}
}
+ ///
+ public void Reprotect(ulong va, ulong size, MemoryPermission permission)
+ {
+ }
+
///
public T Read(ulong va) where T : unmanaged
{
diff --git a/src/Ryujinx.Cpu/DiskCacheLoadState.cs b/src/Ryujinx.Cpu/DiskCacheLoadState.cs
new file mode 100644
index 000000000..930bd95a1
--- /dev/null
+++ b/src/Ryujinx.Cpu/DiskCacheLoadState.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Ryujinx.Cpu.Jit
+{
+ class DiskCacheLoadState : IDiskCacheLoadState
+ {
+ ///
+ public event Action StateChanged;
+
+ ///
+ public void Cancel()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Cpu/ICpuContext.cs b/src/Ryujinx.Cpu/ICpuContext.cs
index edcebdfc4..011f6ed28 100644
--- a/src/Ryujinx.Cpu/ICpuContext.cs
+++ b/src/Ryujinx.Cpu/ICpuContext.cs
@@ -38,6 +38,8 @@ namespace Ryujinx.Cpu
/// Size of the region to be invalidated
void InvalidateCacheRegion(ulong address, ulong size);
+ void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize);
+
///
/// Loads cached code from disk for a given application.
///
diff --git a/src/Ryujinx.Cpu/ICpuEngine.cs b/src/Ryujinx.Cpu/ICpuEngine.cs
index b53b23a8c..e23c78750 100644
--- a/src/Ryujinx.Cpu/ICpuEngine.cs
+++ b/src/Ryujinx.Cpu/ICpuEngine.cs
@@ -1,5 +1,3 @@
-using ARMeilleure.Memory;
-
namespace Ryujinx.Cpu
{
///
@@ -13,6 +11,6 @@ namespace Ryujinx.Cpu
/// Memory manager for the address space of the context
/// Indicates if the context will be used to run 64-bit or 32-bit Arm code
/// CPU context
- ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit);
+ ICpuContext CreateCpuContext(ICpuMemoryManager memoryManager, bool for64Bit);
}
}
diff --git a/src/Ryujinx.Cpu/ICpuMemoryManager.cs b/src/Ryujinx.Cpu/ICpuMemoryManager.cs
new file mode 100644
index 000000000..e0927c5f4
--- /dev/null
+++ b/src/Ryujinx.Cpu/ICpuMemoryManager.cs
@@ -0,0 +1,19 @@
+using ARMeilleure.Memory;
+using Ryujinx.Memory;
+
+namespace Ryujinx.Cpu
+{
+ ///
+ /// CPU memory manager interface.
+ ///
+ public interface ICpuMemoryManager : IMemoryManager
+ {
+ ///
+ /// Reprotects a previously mapped range of virtual memory.
+ ///
+ /// Virtual address of the range to be reprotected
+ /// Size of the range to be reprotected
+ /// New protection of the memory range
+ void Reprotect(ulong va, ulong size, MemoryPermission permission);
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs
index dce0490a4..0adb6af74 100644
--- a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs
+++ b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs
@@ -46,6 +46,11 @@ namespace Ryujinx.Cpu.Jit
_translator.InvalidateJitCacheRegion(address, size);
}
+ ///
+ public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize)
+ {
+ }
+
///
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
{
diff --git a/src/Ryujinx.Cpu/Jit/JitEngine.cs b/src/Ryujinx.Cpu/Jit/JitEngine.cs
index deebb8b9e..ad991e5a5 100644
--- a/src/Ryujinx.Cpu/Jit/JitEngine.cs
+++ b/src/Ryujinx.Cpu/Jit/JitEngine.cs
@@ -12,7 +12,7 @@ namespace Ryujinx.Cpu.Jit
}
///
- public ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit)
+ public ICpuContext CreateCpuContext(ICpuMemoryManager memoryManager, bool for64Bit)
{
return new JitCpuContext(_tickSource, memoryManager, for64Bit);
}
diff --git a/src/Ryujinx.Cpu/Jit/MemoryManager.cs b/src/Ryujinx.Cpu/Jit/MemoryManager.cs
index b9a547025..fca12870c 100644
--- a/src/Ryujinx.Cpu/Jit/MemoryManager.cs
+++ b/src/Ryujinx.Cpu/Jit/MemoryManager.cs
@@ -14,7 +14,7 @@ namespace Ryujinx.Cpu.Jit
///
/// Represents a CPU memory manager.
///
- public sealed class MemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
+ public sealed class MemoryManager : MemoryManagerBase, ICpuMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
@@ -126,6 +126,11 @@ namespace Ryujinx.Cpu.Jit
}
}
+ ///
+ public void Reprotect(ulong va, ulong size, MemoryPermission permission)
+ {
+ }
+
///
public T Read(ulong va) where T : unmanaged
{
diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs
index 2b315e841..f6adb93a1 100644
--- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs
+++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs
@@ -13,7 +13,7 @@ namespace Ryujinx.Cpu.Jit
///
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
///
- public sealed class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
+ public sealed class MemoryManagerHostMapped : MemoryManagerBase, ICpuMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
@@ -194,6 +194,11 @@ namespace Ryujinx.Cpu.Jit
}
}
+ ///
+ public void Reprotect(ulong va, ulong size, MemoryPermission permission)
+ {
+ }
+
///
public T Read(ulong va) where T : unmanaged
{
diff --git a/src/Ryujinx.Cpu/MemoryEhMeilleure.cs b/src/Ryujinx.Cpu/MemoryEhMeilleure.cs
index f3a5b056b..74f62bc13 100644
--- a/src/Ryujinx.Cpu/MemoryEhMeilleure.cs
+++ b/src/Ryujinx.Cpu/MemoryEhMeilleure.cs
@@ -46,6 +46,20 @@ namespace Ryujinx.Cpu
}
}
+ public MemoryEhMeilleure(ulong asSize, MemoryTracking tracking)
+ {
+ _baseAddress = 0UL;
+ ulong endAddress = asSize;
+
+ _trackingEvent = new TrackingEventDelegate(tracking.VirtualMemoryEvent);
+ bool added = NativeSignalHandler.AddTrackedRegion((nuint)_baseAddress, (nuint)endAddress, Marshal.GetFunctionPointerForDelegate(_trackingEvent));
+
+ if (!added)
+ {
+ throw new InvalidOperationException("Number of allowed tracked regions exceeded.");
+ }
+ }
+
public void Dispose()
{
GC.SuppressFinalize(this);
diff --git a/src/Ryujinx.Cpu/Nce/MemoryManagerNative.cs b/src/Ryujinx.Cpu/Nce/MemoryManagerNative.cs
new file mode 100644
index 000000000..e7c757247
--- /dev/null
+++ b/src/Ryujinx.Cpu/Nce/MemoryManagerNative.cs
@@ -0,0 +1,848 @@
+using ARMeilleure.Memory;
+using Ryujinx.Memory;
+using Ryujinx.Memory.Range;
+using Ryujinx.Memory.Tracking;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading;
+
+namespace Ryujinx.Cpu.Nce
+{
+ ///
+ /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
+ ///
+ public sealed class MemoryManagerNative : MemoryManagerBase, ICpuMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
+ {
+ public const int PageBits = 12;
+ public const int PageSize = 1 << PageBits;
+ public const int PageMask = PageSize - 1;
+
+ public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry.
+ public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set.
+
+ private enum HostMappedPtBits : ulong
+ {
+ Unmapped = 0,
+ Mapped,
+ WriteTracked,
+ ReadWriteTracked,
+
+ MappedReplicated = 0x5555555555555555,
+ WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa,
+ ReadWriteTrackedReplicated = ulong.MaxValue
+ }
+
+ private readonly InvalidAccessHandler _invalidAccessHandler;
+
+ private readonly MemoryBlock _addressSpace;
+ private readonly MemoryBlock _addressSpaceMirror;
+ private readonly ulong _addressSpaceSize;
+
+ private readonly MemoryBlock _backingMemory;
+ private readonly PageTable _pageTable;
+
+ private readonly MemoryEhMeilleure _memoryEh;
+
+ private readonly ulong[] _pageBitmap;
+
+ ///
+ public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize;
+
+ public int AddressSpaceBits { get; }
+
+ public IntPtr PageTablePointer => IntPtr.Zero;
+
+ public ulong ReservedSize => (ulong)_addressSpace.Pointer.ToInt64();
+
+ public MemoryManagerType Type => MemoryManagerType.HostMappedUnsafe;
+
+ public MemoryTracking Tracking { get; }
+
+ public event Action UnmapEvent;
+
+ ///
+ /// Creates a new instance of the host mapped memory manager.
+ ///
+ /// Address space instance to use
+ /// Physical backing memory where virtual memory will be mapped to
+ /// Size of the address space
+ /// Optional function to handle invalid memory accesses
+ public MemoryManagerNative(
+ AddressSpace addressSpace,
+ MemoryBlock backingMemory,
+ ulong addressSpaceSize,
+ InvalidAccessHandler invalidAccessHandler = null)
+ {
+ _backingMemory = backingMemory;
+ _pageTable = new PageTable();
+ _invalidAccessHandler = invalidAccessHandler;
+ _addressSpaceSize = addressSpaceSize;
+
+ ulong asSize = PageSize;
+ int asBits = PageBits;
+
+ while (asSize < addressSpaceSize)
+ {
+ asSize <<= 1;
+ asBits++;
+ }
+
+ AddressSpaceBits = asBits;
+
+ _pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
+
+ _addressSpace = addressSpace.Base;
+ _addressSpaceMirror = addressSpace.Mirror;
+
+ Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler);
+ _memoryEh = new MemoryEhMeilleure(asSize, Tracking);
+ }
+
+ ///
+ /// Checks if the virtual address is part of the addressable space.
+ ///
+ /// Virtual address
+ /// True if the virtual address is part of the addressable space
+ private bool ValidateAddress(ulong va)
+ {
+ return va < _addressSpaceSize;
+ }
+
+ ///
+ /// Checks if the combination of virtual address and size is part of the addressable space.
+ ///
+ /// Virtual address of the range
+ /// Size of the range in bytes
+ /// True if the combination of virtual address and size is part of the addressable space
+ private bool ValidateAddressAndSize(ulong va, ulong size)
+ {
+ ulong endVa = va + size;
+ return endVa >= va && endVa >= size && endVa <= _addressSpaceSize;
+ }
+
+ ///
+ /// Ensures the combination of virtual address and size is part of the addressable space.
+ ///
+ /// Virtual address of the range
+ /// Size of the range in bytes
+ /// Throw when the memory region specified outside the addressable space
+ private void AssertValidAddressAndSize(ulong va, ulong size)
+ {
+ if (!ValidateAddressAndSize(va, size))
+ {
+ throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
+ }
+ }
+
+ ///
+ /// Ensures the combination of virtual address and size is part of the addressable space and fully mapped.
+ ///
+ /// Virtual address of the range
+ /// Size of the range in bytes
+ private void AssertMapped(ulong va, ulong size)
+ {
+ if (!ValidateAddressAndSize(va, size) || !IsRangeMappedImpl(va, size))
+ {
+ throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
+ }
+ }
+
+ ///
+ public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
+ {
+ AssertValidAddressAndSize(va, size);
+
+ _addressSpace.MapView(_backingMemory, pa, AddressToOffset(va), size);
+ _addressSpaceMirror.MapView(_backingMemory, pa, AddressToOffset(va), size);
+ AddMapping(va, size);
+ PtMap(va, pa, size);
+
+ Tracking.Map(va, size);
+ }
+
+ ///
+ public void MapForeign(ulong va, nuint hostPointer, ulong size)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public void Unmap(ulong va, ulong size)
+ {
+ AssertValidAddressAndSize(va, size);
+
+ UnmapEvent?.Invoke(va, size);
+ Tracking.Unmap(va, size);
+
+ RemoveMapping(va, size);
+ PtUnmap(va, size);
+ _addressSpace.UnmapView(_backingMemory, AddressToOffset(va), size);
+ _addressSpaceMirror.UnmapView(_backingMemory, AddressToOffset(va), size);
+ }
+
+ private void PtMap(ulong va, ulong pa, ulong size)
+ {
+ while (size != 0)
+ {
+ _pageTable.Map(va, pa);
+
+ va += PageSize;
+ pa += PageSize;
+ size -= PageSize;
+ }
+ }
+
+ private void PtUnmap(ulong va, ulong size)
+ {
+ while (size != 0)
+ {
+ _pageTable.Unmap(va);
+
+ va += PageSize;
+ size -= PageSize;
+ }
+ }
+
+ ///
+ public void Reprotect(ulong va, ulong size, MemoryPermission permission)
+ {
+ _addressSpace.Reprotect(AddressToOffset(va), size, permission);
+ }
+
+ ///
+ public T Read(ulong va) where T : unmanaged
+ {
+ try
+ {
+ AssertMapped(va, (ulong)Unsafe.SizeOf());
+
+ return _addressSpaceMirror.Read(AddressToOffset(va));
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+ {
+ throw;
+ }
+
+ return default;
+ }
+ }
+
+ ///
+ public T ReadTracked(ulong va) where T : unmanaged
+ {
+ try
+ {
+ SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false);
+
+ return Read(va);
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+ {
+ throw;
+ }
+
+ return default;
+ }
+ }
+
+ ///
+ public void Read(ulong va, Span data)
+ {
+ try
+ {
+ AssertMapped(va, (ulong)data.Length);
+
+ _addressSpaceMirror.Read(AddressToOffset(va), data);
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+ {
+ throw;
+ }
+ }
+ }
+
+
+ ///
+ public void Write(ulong va, T value) where T : unmanaged
+ {
+ try
+ {
+ SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), write: true);
+
+ _addressSpaceMirror.Write(AddressToOffset(va), value);
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+ {
+ throw;
+ }
+ }
+ }
+
+ ///
+ public void Write(ulong va, ReadOnlySpan data)
+ {
+ try
+ {
+ SignalMemoryTracking(va, (ulong)data.Length, write: true);
+
+ _addressSpaceMirror.Write(AddressToOffset(va), data);
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+ {
+ throw;
+ }
+ }
+ }
+
+ ///
+ public void WriteUntracked(ulong va, ReadOnlySpan data)
+ {
+ try
+ {
+ AssertMapped(va, (ulong)data.Length);
+
+ _addressSpaceMirror.Write(AddressToOffset(va), data);
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+ {
+ throw;
+ }
+ }
+ }
+
+ ///
+ public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data)
+ {
+ try
+ {
+ SignalMemoryTracking(va, (ulong)data.Length, false);
+
+ Span target = _addressSpaceMirror.GetSpan(AddressToOffset(va), data.Length);
+ bool changed = !data.SequenceEqual(target);
+
+ if (changed)
+ {
+ data.CopyTo(target);
+ }
+
+ return changed;
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+ {
+ throw;
+ }
+
+ return true;
+ }
+ }
+
+ ///
+ public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false)
+ {
+ if (tracked)
+ {
+ SignalMemoryTracking(va, (ulong)size, write: false);
+ }
+ else
+ {
+ AssertMapped(va, (ulong)size);
+ }
+
+ if (size == 0)
+ {
+ return ReadOnlySpan.Empty;
+ }
+
+ return _addressSpaceMirror.GetSpan(AddressToOffset(va), size);
+ }
+
+ ///
+ public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
+ {
+ if (tracked)
+ {
+ SignalMemoryTracking(va, (ulong)size, true);
+ }
+ else
+ {
+ AssertMapped(va, (ulong)size);
+ }
+
+ return _addressSpaceMirror.GetWritableRegion(AddressToOffset(va), size);
+ }
+
+ ///
+ public ref T GetRef(ulong va) where T : unmanaged
+ {
+ SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), true);
+
+ return ref _addressSpaceMirror.GetRef(AddressToOffset(va));
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsMapped(ulong va)
+ {
+ return ValidateAddress(va) && IsMappedImpl(va);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool IsMappedImpl(ulong va)
+ {
+ ulong page = va >> PageBits;
+
+ int bit = (int)((page & 31) << 1);
+
+ int pageIndex = (int)(page >> PageToPteShift);
+ ref ulong pageRef = ref _pageBitmap[pageIndex];
+
+ ulong pte = Volatile.Read(ref pageRef);
+
+ return ((pte >> bit) & 3) != 0;
+ }
+
+ ///
+ public bool IsRangeMapped(ulong va, ulong size)
+ {
+ AssertValidAddressAndSize(va, size);
+
+ return IsRangeMappedImpl(va, size);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex)
+ {
+ startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1);
+ endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1));
+
+ pageIndex = (int)(pageStart >> PageToPteShift);
+ pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift);
+ }
+
+ private bool IsRangeMappedImpl(ulong va, ulong size)
+ {
+ int pages = GetPagesCount(va, size, out _);
+
+ if (pages == 1)
+ {
+ return IsMappedImpl(va);
+ }
+
+ ulong pageStart = va >> PageBits;
+ ulong pageEnd = pageStart + (ulong)pages;
+
+ GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
+
+ // Check if either bit in each 2 bit page entry is set.
+ // OR the block with itself shifted down by 1, and check the first bit of each entry.
+
+ ulong mask = BlockMappedMask & startMask;
+
+ while (pageIndex <= pageEndIndex)
+ {
+ if (pageIndex == pageEndIndex)
+ {
+ mask &= endMask;
+ }
+
+ ref ulong pageRef = ref _pageBitmap[pageIndex++];
+ ulong pte = Volatile.Read(ref pageRef);
+
+ pte |= pte >> 1;
+ if ((pte & mask) != mask)
+ {
+ return false;
+ }
+
+ mask = BlockMappedMask;
+ }
+
+ return true;
+ }
+
+ ///
+ public IEnumerable GetHostRegions(ulong va, ulong size)
+ {
+ AssertValidAddressAndSize(va, size);
+
+ return Enumerable.Repeat(new HostMemoryRange((nuint)(ulong)_addressSpaceMirror.GetPointer(AddressToOffset(va), size), size), 1);
+ }
+
+ ///
+ public IEnumerable GetPhysicalRegions(ulong va, ulong size)
+ {
+ int pages = GetPagesCount(va, (uint)size, out va);
+
+ var regions = new List();
+
+ ulong regionStart = GetPhysicalAddressChecked(va);
+ ulong regionSize = PageSize;
+
+ for (int page = 0; page < pages - 1; page++)
+ {
+ if (!ValidateAddress(va + PageSize))
+ {
+ return null;
+ }
+
+ ulong newPa = GetPhysicalAddressChecked(va + PageSize);
+
+ if (GetPhysicalAddressChecked(va) + PageSize != newPa)
+ {
+ regions.Add(new MemoryRange(regionStart, regionSize));
+ regionStart = newPa;
+ regionSize = 0;
+ }
+
+ va += PageSize;
+ regionSize += PageSize;
+ }
+
+ regions.Add(new MemoryRange(regionStart, regionSize));
+
+ return regions;
+ }
+
+ private ulong GetPhysicalAddressChecked(ulong va)
+ {
+ if (!IsMapped(va))
+ {
+ ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}");
+ }
+
+ return GetPhysicalAddressInternal(va);
+ }
+
+ private ulong GetPhysicalAddressInternal(ulong va)
+ {
+ return _pageTable.Read(va) + (va & PageMask);
+ }
+
+ ///
+ ///
+ /// This function also validates that the given range is both valid and mapped, and will throw if it is not.
+ ///
+ public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
+ {
+ AssertValidAddressAndSize(va, size);
+
+ if (precise)
+ {
+ Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId);
+ return;
+ }
+
+ // Software table, used for managed memory tracking.
+
+ int pages = GetPagesCount(va, size, out _);
+ ulong pageStart = va >> PageBits;
+
+ if (pages == 1)
+ {
+ ulong tag = (ulong)(write ? HostMappedPtBits.WriteTracked : HostMappedPtBits.ReadWriteTracked);
+
+ int bit = (int)((pageStart & 31) << 1);
+
+ int pageIndex = (int)(pageStart >> PageToPteShift);
+ ref ulong pageRef = ref _pageBitmap[pageIndex];
+
+ ulong pte = Volatile.Read(ref pageRef);
+ ulong state = ((pte >> bit) & 3);
+
+ if (state >= tag)
+ {
+ Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
+ return;
+ }
+ else if (state == 0)
+ {
+ ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
+ }
+ }
+ else
+ {
+ ulong pageEnd = pageStart + (ulong)pages;
+
+ GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
+
+ ulong mask = startMask;
+
+ ulong anyTrackingTag = (ulong)HostMappedPtBits.WriteTrackedReplicated;
+
+ while (pageIndex <= pageEndIndex)
+ {
+ if (pageIndex == pageEndIndex)
+ {
+ mask &= endMask;
+ }
+
+ ref ulong pageRef = ref _pageBitmap[pageIndex++];
+
+ ulong pte = Volatile.Read(ref pageRef);
+ ulong mappedMask = mask & BlockMappedMask;
+
+ ulong mappedPte = pte | (pte >> 1);
+ if ((mappedPte & mappedMask) != mappedMask)
+ {
+ ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
+ }
+
+ pte &= mask;
+ if ((pte & anyTrackingTag) != 0) // Search for any tracking.
+ {
+ // Writes trigger any tracking.
+ // Only trigger tracking from reads if both bits are set on any page.
+ if (write || (pte & (pte >> 1) & BlockMappedMask) != 0)
+ {
+ Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
+ break;
+ }
+ }
+
+ mask = ulong.MaxValue;
+ }
+ }
+ }
+
+ ///
+ /// Computes the number of pages in a virtual address range.
+ ///
+ /// Virtual address of the range
+ /// Size of the range
+ /// The virtual address of the beginning of the first page
+ /// This function does not differentiate between allocated and unallocated pages.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private int GetPagesCount(ulong va, ulong size, out ulong startVa)
+ {
+ // WARNING: Always check if ulong does not overflow during the operations.
+ startVa = va & ~(ulong)PageMask;
+ ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
+
+ return (int)(vaSpan / PageSize);
+ }
+
+ ///
+ public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
+ {
+ // Protection is inverted on software pages, since the default value is 0.
+ protection = (~protection) & MemoryPermission.ReadAndWrite;
+
+ int pages = GetPagesCount(va, size, out va);
+ ulong pageStart = va >> PageBits;
+
+ if (pages == 1)
+ {
+ ulong protTag = protection switch
+ {
+ MemoryPermission.None => (ulong)HostMappedPtBits.Mapped,
+ MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTracked,
+ _ => (ulong)HostMappedPtBits.ReadWriteTracked,
+ };
+
+ int bit = (int)((pageStart & 31) << 1);
+
+ ulong tagMask = 3UL << bit;
+ ulong invTagMask = ~tagMask;
+
+ ulong tag = protTag << bit;
+
+ int pageIndex = (int)(pageStart >> PageToPteShift);
+ ref ulong pageRef = ref _pageBitmap[pageIndex];
+
+ ulong pte;
+
+ do
+ {
+ pte = Volatile.Read(ref pageRef);
+ }
+ while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
+ }
+ else
+ {
+ ulong pageEnd = pageStart + (ulong)pages;
+
+ GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
+
+ ulong mask = startMask;
+
+ ulong protTag = protection switch
+ {
+ MemoryPermission.None => (ulong)HostMappedPtBits.MappedReplicated,
+ MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTrackedReplicated,
+ _ => (ulong)HostMappedPtBits.ReadWriteTrackedReplicated,
+ };
+
+ while (pageIndex <= pageEndIndex)
+ {
+ if (pageIndex == pageEndIndex)
+ {
+ mask &= endMask;
+ }
+
+ ref ulong pageRef = ref _pageBitmap[pageIndex++];
+
+ ulong pte;
+ ulong mappedMask;
+
+ // Change the protection of all 2 bit entries that are mapped.
+ do
+ {
+ pte = Volatile.Read(ref pageRef);
+
+ mappedMask = pte | (pte >> 1);
+ mappedMask |= (mappedMask & BlockMappedMask) << 1;
+ mappedMask &= mask; // Only update mapped pages within the given range.
+ }
+ while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte);
+
+ mask = ulong.MaxValue;
+ }
+ }
+
+ protection = protection switch
+ {
+ MemoryPermission.None => MemoryPermission.ReadAndWrite,
+ MemoryPermission.Write => MemoryPermission.Read,
+ _ => MemoryPermission.None
+ };
+
+ _addressSpace.Reprotect(AddressToOffset(va), size, protection, false);
+ }
+
+ ///
+ public RegionHandle BeginTracking(ulong address, ulong size, int id)
+ {
+ return Tracking.BeginTracking(address, size, id);
+ }
+
+ ///
+ public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id)
+ {
+ return Tracking.BeginGranularTracking(address, size, handles, granularity, id);
+ }
+
+ ///
+ public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
+ {
+ return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
+ }
+
+ ///
+ /// Adds the given address mapping to the page table.
+ ///
+ /// Virtual memory address
+ /// Size to be mapped
+ private void AddMapping(ulong va, ulong size)
+ {
+ int pages = GetPagesCount(va, size, out _);
+ ulong pageStart = va >> PageBits;
+ ulong pageEnd = pageStart + (ulong)pages;
+
+ GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
+
+ ulong mask = startMask;
+
+ while (pageIndex <= pageEndIndex)
+ {
+ if (pageIndex == pageEndIndex)
+ {
+ mask &= endMask;
+ }
+
+ ref ulong pageRef = ref _pageBitmap[pageIndex++];
+
+ ulong pte;
+ ulong mappedMask;
+
+ // Map all 2-bit entries that are unmapped.
+ do
+ {
+ pte = Volatile.Read(ref pageRef);
+
+ mappedMask = pte | (pte >> 1);
+ mappedMask |= (mappedMask & BlockMappedMask) << 1;
+ mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged.
+ }
+ while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte);
+
+ mask = ulong.MaxValue;
+ }
+ }
+
+ ///
+ /// Removes the given address mapping from the page table.
+ ///
+ /// Virtual memory address
+ /// Size to be unmapped
+ private void RemoveMapping(ulong va, ulong size)
+ {
+ int pages = GetPagesCount(va, size, out _);
+ ulong pageStart = va >> PageBits;
+ ulong pageEnd = pageStart + (ulong)pages;
+
+ GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
+
+ startMask = ~startMask;
+ endMask = ~endMask;
+
+ ulong mask = startMask;
+
+ while (pageIndex <= pageEndIndex)
+ {
+ if (pageIndex == pageEndIndex)
+ {
+ mask |= endMask;
+ }
+
+ ref ulong pageRef = ref _pageBitmap[pageIndex++];
+ ulong pte;
+
+ do
+ {
+ pte = Volatile.Read(ref pageRef);
+ }
+ while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte);
+
+ mask = 0;
+ }
+ }
+
+ private ulong AddressToOffset(ulong address)
+ {
+ if (address < ReservedSize)
+ {
+ throw new ArgumentException($"Invalid address 0x{address:x16}");
+ }
+
+ return address - ReservedSize;
+ }
+
+ ///
+ /// Disposes of resources used by the memory manager.
+ ///
+ protected override void Destroy()
+ {
+ _addressSpace.Dispose();
+ _addressSpaceMirror.Dispose();
+ _memoryEh.Dispose();
+ }
+
+ private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
+ }
+}
diff --git a/src/Ryujinx.Cpu/Nce/NceAsmTable.cs b/src/Ryujinx.Cpu/Nce/NceAsmTable.cs
new file mode 100644
index 000000000..72e2d9780
--- /dev/null
+++ b/src/Ryujinx.Cpu/Nce/NceAsmTable.cs
@@ -0,0 +1,409 @@
+using System;
+
+namespace Ryujinx.Cpu.Nce
+{
+ static class NceAsmTable
+ {
+ public static uint[] GetTpidrEl0Code = new uint[]
+ {
+ GetMrsTpidrEl0(0), // mrs x0, tpidr_el0
+ 0xd65f03c0u, // ret
+ };
+
+ public static uint[] ThreadStartCode = new uint[]
+ {
+ 0xa9ae53f3u, // stp x19, x20, [sp, #-288]!
+ 0xa9015bf5u, // stp x21, x22, [sp, #16]
+ 0xa90263f7u, // stp x23, x24, [sp, #32]
+ 0xa9036bf9u, // stp x25, x26, [sp, #48]
+ 0xa90473fbu, // stp x27, x28, [sp, #64]
+ 0xa9057bfdu, // stp x29, x30, [sp, #80]
+ 0x6d0627e8u, // stp d8, d9, [sp, #96]
+ 0x6d072feau, // stp d10, d11, [sp, #112]
+ 0x6d0837ecu, // stp d12, d13, [sp, #128]
+ 0x6d093feeu, // stp d14, d15, [sp, #144]
+ 0x6d0a47f0u, // stp d16, d17, [sp, #160]
+ 0x6d0b4ff2u, // stp d18, d19, [sp, #176]
+ 0x6d0c57f4u, // stp d20, d21, [sp, #192]
+ 0x6d0d5ff6u, // stp d22, d23, [sp, #208]
+ 0x6d0e67f8u, // stp d24, d25, [sp, #224]
+ 0x6d0f6ffau, // stp d26, d27, [sp, #240]
+ 0x6d1077fcu, // stp d28, d29, [sp, #256]
+ 0x6d117ffeu, // stp d30, d31, [sp, #272]
+ 0xb9031c1fu, // str wzr, [x0, #796]
+ 0x910003e1u, // mov x1, sp
+ 0xf9019001u, // str x1, [x0, #800]
+ 0xa9410c02u, // ldp x2, x3, [x0, #16]
+ 0xa9421404u, // ldp x4, x5, [x0, #32]
+ 0xa9431c06u, // ldp x6, x7, [x0, #48]
+ 0xa9442408u, // ldp x8, x9, [x0, #64]
+ 0xa9452c0au, // ldp x10, x11, [x0, #80]
+ 0xa946340cu, // ldp x12, x13, [x0, #96]
+ 0xa9473c0eu, // ldp x14, x15, [x0, #112]
+ 0xa9484410u, // ldp x16, x17, [x0, #128]
+ 0xa9494c12u, // ldp x18, x19, [x0, #144]
+ 0xa94a5414u, // ldp x20, x21, [x0, #160]
+ 0xa94b5c16u, // ldp x22, x23, [x0, #176]
+ 0xa94c6418u, // ldp x24, x25, [x0, #192]
+ 0xa94d6c1au, // ldp x26, x27, [x0, #208]
+ 0xa94e741cu, // ldp x28, x29, [x0, #224]
+ 0xad480400u, // ldp q0, q1, [x0, #256]
+ 0xad490c02u, // ldp q2, q3, [x0, #288]
+ 0xad4a1404u, // ldp q4, q5, [x0, #320]
+ 0xad4b1c06u, // ldp q6, q7, [x0, #352]
+ 0xad4c2408u, // ldp q8, q9, [x0, #384]
+ 0xad4d2c0au, // ldp q10, q11, [x0, #416]
+ 0xad4e340cu, // ldp q12, q13, [x0, #448]
+ 0xad4f3c0eu, // ldp q14, q15, [x0, #480]
+ 0xad504410u, // ldp q16, q17, [x0, #512]
+ 0xad514c12u, // ldp q18, q19, [x0, #544]
+ 0xad525414u, // ldp q20, q21, [x0, #576]
+ 0xad535c16u, // ldp q22, q23, [x0, #608]
+ 0xad546418u, // ldp q24, q25, [x0, #640]
+ 0xad556c1au, // ldp q26, q27, [x0, #672]
+ 0xad56741cu, // ldp q28, q29, [x0, #704]
+ 0xad577c1eu, // ldp q30, q31, [x0, #736]
+ 0xa94f041eu, // ldp x30, x1, [x0, #240]
+ 0x9100003fu, // mov sp, x1
+ 0xa9400400u, // ldp x0, x1, [x0]
+ 0xd61f03c0u, // br x30
+ };
+
+ public static uint[] ExceptionHandlerEntryCode = new uint[]
+ {
+ 0xa9bc53f3u, // stp x19, x20, [sp, #-64]!
+ 0xa9015bf5u, // stp x21, x22, [sp, #16]
+ 0xa90263f7u, // stp x23, x24, [sp, #32]
+ 0xf9001bf9u, // str x25, [sp, #48]
+ 0xaa0003f3u, // mov x19, x0
+ 0xaa0103f4u, // mov x20, x1
+ 0xaa0203f5u, // mov x21, x2
+ 0x910003f6u, // mov x22, sp
+ 0xaa1e03f7u, // mov x23, x30
+ 0xd2800018u, // mov x24, #0x0
+ 0xf2a00018u, // movk x24, #0x0, lsl #16
+ 0xf2c00018u, // movk x24, #0x0, lsl #32
+ 0xf2e00018u, // movk x24, #0x0, lsl #48
+ 0xf85f8319u, // ldur x25, [x24, #-8]
+ 0x8b191319u, // add x25, x24, x25, lsl #4
+ GetMrsTpidrEl0(1), // mrs x1, tpidr_el0
+ 0xeb19031fu, // cmp x24, x25
+ 0x540000a0u, // b.eq 13c
+ 0xf8410702u, // ldr x2, [x24], #16
+ 0xeb02003fu, // cmp x1, x2
+ 0x54000080u, // b.eq 144
+ 0x17fffffbu, // b 124
+ 0xd2800018u, // mov x24, #0x0
+ 0x14000002u, // b 148
+ 0xf85f8318u, // ldur x24, [x24, #-8]
+ 0xb4000438u, // cbz x24, 1cc
+ 0xf9419300u, // ldr x0, [x24, #800]
+ 0x9100001fu, // mov sp, x0
+ 0x7100027fu, // cmp w19, #0x0
+ 0x54000180u, // b.eq 188
+ 0x52800020u, // mov w0, #0x1
+ 0xb9031f00u, // str w0, [x24, #796]
+ 0xaa1303e0u, // mov x0, x19
+ 0xaa1403e1u, // mov x1, x20
+ 0xaa1503e2u, // mov x2, x21
+ 0xd2800008u, // mov x8, #0x0
+ 0xf2a00008u, // movk x8, #0x0, lsl #16
+ 0xf2c00008u, // movk x8, #0x0, lsl #32
+ 0xf2e00008u, // movk x8, #0x0, lsl #48
+ 0xd63f0100u, // blr x8
+ 0x1400000au, // b 1ac
+ 0xb9431f00u, // ldr w0, [x24, #796]
+ 0x35000120u, // cbnz w0, 1b0
+ 0x52800020u, // mov w0, #0x1
+ 0xb9031f00u, // str w0, [x24, #796]
+ 0xd2800000u, // mov x0, #0x0
+ 0xf2a00000u, // movk x0, #0x0, lsl #16
+ 0xf2c00000u, // movk x0, #0x0, lsl #32
+ 0xf2e00000u, // movk x0, #0x0, lsl #48
+ 0xd63f0000u, // blr x0
+ 0xb9031f1fu, // str wzr, [x24, #796]
+ 0x910002dfu, // mov sp, x22
+ 0xaa1703feu, // mov x30, x23
+ 0xa9415bf5u, // ldp x21, x22, [sp, #16]
+ 0xa94263f7u, // ldp x23, x24, [sp, #32]
+ 0xa9436bf9u, // ldp x25, x26, [sp, #48]
+ 0xa8c453f3u, // ldp x19, x20, [sp], #64
+ 0xd65f03c0u, // ret
+ 0xaa1303e0u, // mov x0, x19
+ 0xaa1403e1u, // mov x1, x20
+ 0xaa1503e2u, // mov x2, x21
+ 0x910002dfu, // mov sp, x22
+ 0xa9415bf5u, // ldp x21, x22, [sp, #16]
+ 0xa94263f7u, // ldp x23, x24, [sp, #32]
+ 0xf9401bf9u, // ldr x25, [sp, #48]
+ 0xa8c453f3u, // ldp x19, x20, [sp], #64
+ 0xd2800003u, // mov x3, #0x0
+ 0xf2a00003u, // movk x3, #0x0, lsl #16
+ 0xf2c00003u, // movk x3, #0x0, lsl #32
+ 0xf2e00003u, // movk x3, #0x0, lsl #48
+ 0xd61f0060u, // br x3
+ };
+
+ public static uint[] SvcPatchCode = new uint[]
+ {
+ 0xa9be53f3u, // stp x19, x20, [sp, #-32]!
+ 0xf9000bf5u, // str x21, [sp, #16]
+ 0xd2800013u, // mov x19, #0x0
+ 0xf2a00013u, // movk x19, #0x0, lsl #16
+ 0xf2c00013u, // movk x19, #0x0, lsl #32
+ 0xf2e00013u, // movk x19, #0x0, lsl #48
+ GetMrsTpidrEl0(20), // mrs x20, tpidr_el0
+ 0xf8410675u, // ldr x21, [x19], #16
+ 0xeb15029fu, // cmp x20, x21
+ 0x54000040u, // b.eq 22c
+ 0x17fffffdu, // b 21c
+ 0xf85f8273u, // ldur x19, [x19, #-8]
+ 0xa9000660u, // stp x0, x1, [x19]
+ 0xa9010e62u, // stp x2, x3, [x19, #16]
+ 0xa9021664u, // stp x4, x5, [x19, #32]
+ 0xa9031e66u, // stp x6, x7, [x19, #48]
+ 0xa9042668u, // stp x8, x9, [x19, #64]
+ 0xa9052e6au, // stp x10, x11, [x19, #80]
+ 0xa906366cu, // stp x12, x13, [x19, #96]
+ 0xa9073e6eu, // stp x14, x15, [x19, #112]
+ 0xa9084670u, // stp x16, x17, [x19, #128]
+ 0xf9400bf5u, // ldr x21, [sp, #16]
+ 0xa8c253e0u, // ldp x0, x20, [sp], #32
+ 0xa9090272u, // stp x18, x0, [x19, #144]
+ 0xa90a5674u, // stp x20, x21, [x19, #160]
+ 0xa90b5e76u, // stp x22, x23, [x19, #176]
+ 0xa90c6678u, // stp x24, x25, [x19, #192]
+ 0xa90d6e7au, // stp x26, x27, [x19, #208]
+ 0xa90e767cu, // stp x28, x29, [x19, #224]
+ 0x910003e0u, // mov x0, sp
+ 0xa90f027eu, // stp x30, x0, [x19, #240]
+ 0xad080660u, // stp q0, q1, [x19, #256]
+ 0xad090e62u, // stp q2, q3, [x19, #288]
+ 0xad0a1664u, // stp q4, q5, [x19, #320]
+ 0xad0b1e66u, // stp q6, q7, [x19, #352]
+ 0xad0c2668u, // stp q8, q9, [x19, #384]
+ 0xad0d2e6au, // stp q10, q11, [x19, #416]
+ 0xad0e366cu, // stp q12, q13, [x19, #448]
+ 0xad0f3e6eu, // stp q14, q15, [x19, #480]
+ 0xad104670u, // stp q16, q17, [x19, #512]
+ 0xad114e72u, // stp q18, q19, [x19, #544]
+ 0xad125674u, // stp q20, q21, [x19, #576]
+ 0xad135e76u, // stp q22, q23, [x19, #608]
+ 0xad146678u, // stp q24, q25, [x19, #640]
+ 0xad156e7au, // stp q26, q27, [x19, #672]
+ 0xad16767cu, // stp q28, q29, [x19, #704]
+ 0xad177e7eu, // stp q30, q31, [x19, #736]
+ 0xf9419260u, // ldr x0, [x19, #800]
+ 0x9100001fu, // mov sp, x0
+ 0x52800020u, // mov w0, #0x1
+ 0xb9031e60u, // str w0, [x19, #796]
+ 0x52800000u, // mov w0, #0x0
+ 0xf941aa68u, // ldr x8, [x19, #848]
+ 0xd63f0100u, // blr x8
+ 0x35000280u, // cbnz w0, 328
+ 0x6d517ffeu, // ldp d30, d31, [sp, #272]
+ 0x6d5077fcu, // ldp d28, d29, [sp, #256]
+ 0x6d4f6ffau, // ldp d26, d27, [sp, #240]
+ 0x6d4e67f8u, // ldp d24, d25, [sp, #224]
+ 0x6d4d5ff6u, // ldp d22, d23, [sp, #208]
+ 0x6d4c57f4u, // ldp d20, d21, [sp, #192]
+ 0x6d4b4ff2u, // ldp d18, d19, [sp, #176]
+ 0x6d4a47f0u, // ldp d16, d17, [sp, #160]
+ 0x6d493feeu, // ldp d14, d15, [sp, #144]
+ 0x6d4837ecu, // ldp d12, d13, [sp, #128]
+ 0x6d472feau, // ldp d10, d11, [sp, #112]
+ 0x6d4627e8u, // ldp d8, d9, [sp, #96]
+ 0xa9457bfdu, // ldp x29, x30, [sp, #80]
+ 0xa94473fbu, // ldp x27, x28, [sp, #64]
+ 0xa9436bf9u, // ldp x25, x26, [sp, #48]
+ 0xa94263f7u, // ldp x23, x24, [sp, #32]
+ 0xa9415bf5u, // ldp x21, x22, [sp, #16]
+ 0xa8d253f3u, // ldp x19, x20, [sp], #288
+ 0xd65f03c0u, // ret
+ 0xb9031e7fu, // str wzr, [x19, #796]
+ 0xa94f027eu, // ldp x30, x0, [x19, #240]
+ 0x9100001fu, // mov sp, x0
+ 0xa9400660u, // ldp x0, x1, [x19]
+ 0xa9410e62u, // ldp x2, x3, [x19, #16]
+ 0xa9421664u, // ldp x4, x5, [x19, #32]
+ 0xa9431e66u, // ldp x6, x7, [x19, #48]
+ 0xa9442668u, // ldp x8, x9, [x19, #64]
+ 0xa9452e6au, // ldp x10, x11, [x19, #80]
+ 0xa946366cu, // ldp x12, x13, [x19, #96]
+ 0xa9473e6eu, // ldp x14, x15, [x19, #112]
+ 0xa9484670u, // ldp x16, x17, [x19, #128]
+ 0xf9404a72u, // ldr x18, [x19, #144]
+ 0xa94a5674u, // ldp x20, x21, [x19, #160]
+ 0xa94b5e76u, // ldp x22, x23, [x19, #176]
+ 0xa94c6678u, // ldp x24, x25, [x19, #192]
+ 0xa94d6e7au, // ldp x26, x27, [x19, #208]
+ 0xa94e767cu, // ldp x28, x29, [x19, #224]
+ 0xad480660u, // ldp q0, q1, [x19, #256]
+ 0xad490e62u, // ldp q2, q3, [x19, #288]
+ 0xad4a1664u, // ldp q4, q5, [x19, #320]
+ 0xad4b1e66u, // ldp q6, q7, [x19, #352]
+ 0xad4c2668u, // ldp q8, q9, [x19, #384]
+ 0xad4d2e6au, // ldp q10, q11, [x19, #416]
+ 0xad4e366cu, // ldp q12, q13, [x19, #448]
+ 0xad4f3e6eu, // ldp q14, q15, [x19, #480]
+ 0xad504670u, // ldp q16, q17, [x19, #512]
+ 0xad514e72u, // ldp q18, q19, [x19, #544]
+ 0xad525674u, // ldp q20, q21, [x19, #576]
+ 0xad535e76u, // ldp q22, q23, [x19, #608]
+ 0xad546678u, // ldp q24, q25, [x19, #640]
+ 0xad556e7au, // ldp q26, q27, [x19, #672]
+ 0xad56767cu, // ldp q28, q29, [x19, #704]
+ 0xad577e7eu, // ldp q30, q31, [x19, #736]
+ 0xf9404e73u, // ldr x19, [x19, #152]
+ 0x14000000u, // b 3b4
+ };
+
+ public static uint[] MrsTpidrroEl0PatchCode = new uint[]
+ {
+ 0xa9be4fffu, // stp xzr, x19, [sp, #-32]!
+ 0xa90157f4u, // stp x20, x21, [sp, #16]
+ 0xd2800013u, // mov x19, #0x0
+ 0xf2a00013u, // movk x19, #0x0, lsl #16
+ 0xf2c00013u, // movk x19, #0x0, lsl #32
+ 0xf2e00013u, // movk x19, #0x0, lsl #48
+ GetMrsTpidrEl0(20), // mrs x20, tpidr_el0
+ 0xf8410675u, // ldr x21, [x19], #16
+ 0xeb15029fu, // cmp x20, x21
+ 0x54000040u, // b.eq 3e4
+ 0x17fffffdu, // b 3d4
+ 0xf85f8273u, // ldur x19, [x19, #-8]
+ 0xf9418673u, // ldr x19, [x19, #776]
+ 0xf90003f3u, // str x19, [sp]
+ 0xa94157f4u, // ldp x20, x21, [sp, #16]
+ 0xf94007f3u, // ldr x19, [sp, #8]
+ 0xf84207e0u, // ldr x0, [sp], #32
+ 0x14000000u, // b 3fc
+ };
+
+ public static uint[] MrsTpidrEl0PatchCode = new uint[]
+ {
+ 0xa9be4fffu, // stp xzr, x19, [sp, #-32]!
+ 0xa90157f4u, // stp x20, x21, [sp, #16]
+ 0xd2800013u, // mov x19, #0x0
+ 0xf2a00013u, // movk x19, #0x0, lsl #16
+ 0xf2c00013u, // movk x19, #0x0, lsl #32
+ 0xf2e00013u, // movk x19, #0x0, lsl #48
+ GetMrsTpidrEl0(20), // mrs x20, tpidr_el0
+ 0xf8410675u, // ldr x21, [x19], #16
+ 0xeb15029fu, // cmp x20, x21
+ 0x54000040u, // b.eq 42c
+ 0x17fffffdu, // b 41c
+ 0xf85f8273u, // ldur x19, [x19, #-8]
+ 0xf9418273u, // ldr x19, [x19, #768]
+ 0xf90003f3u, // str x19, [sp]
+ 0xa94157f4u, // ldp x20, x21, [sp, #16]
+ 0xf94007f3u, // ldr x19, [sp, #8]
+ 0xf84207e0u, // ldr x0, [sp], #32
+ 0x14000000u, // b 444
+ };
+
+ public static uint[] MrsCtrEl0PatchCode = new uint[]
+ {
+ 0xa9be4fffu, // stp xzr, x19, [sp, #-32]!
+ 0xa90157f4u, // stp x20, x21, [sp, #16]
+ 0xd2800013u, // mov x19, #0x0
+ 0xf2a00013u, // movk x19, #0x0, lsl #16
+ 0xf2c00013u, // movk x19, #0x0, lsl #32
+ 0xf2e00013u, // movk x19, #0x0, lsl #48
+ GetMrsTpidrEl0(20), // mrs x20, tpidr_el0
+ 0xf8410675u, // ldr x21, [x19], #16
+ 0xeb15029fu, // cmp x20, x21
+ 0x54000040u, // b.eq 474
+ 0x17fffffdu, // b 464
+ 0xf85f8273u, // ldur x19, [x19, #-8]
+ 0xf9419e73u, // ldr x19, [x19, #824]
+ 0xf90003f3u, // str x19, [sp]
+ 0xa94157f4u, // ldp x20, x21, [sp, #16]
+ 0xf94007f3u, // ldr x19, [sp, #8]
+ 0xf84207e0u, // ldr x0, [sp], #32
+ 0x14000000u, // b 48c
+ };
+
+ public static uint[] MsrTpidrEl0PatchCode = new uint[]
+ {
+ 0xa9be03f3u, // stp x19, x0, [sp, #-32]!
+ 0xa90157f4u, // stp x20, x21, [sp, #16]
+ 0xd2800013u, // mov x19, #0x0
+ 0xf2a00013u, // movk x19, #0x0, lsl #16
+ 0xf2c00013u, // movk x19, #0x0, lsl #32
+ 0xf2e00013u, // movk x19, #0x0, lsl #48
+ GetMrsTpidrEl0(20), // mrs x20, tpidr_el0
+ 0xf8410675u, // ldr x21, [x19], #16
+ 0xeb15029fu, // cmp x20, x21
+ 0x54000040u, // b.eq 4bc
+ 0x17fffffdu, // b 4ac
+ 0xf85f8273u, // ldur x19, [x19, #-8]
+ 0xf94007f4u, // ldr x20, [sp, #8]
+ 0xf9018274u, // str x20, [x19, #768]
+ 0xa94157f4u, // ldp x20, x21, [sp, #16]
+ 0xf84207f3u, // ldr x19, [sp], #32
+ 0x14000000u, // b 4d0
+ };
+
+ public static uint[] MrsCntpctEl0PatchCode = new uint[]
+ {
+ 0xa9b407e0u, // stp x0, x1, [sp, #-192]!
+ 0xa9010fe2u, // stp x2, x3, [sp, #16]
+ 0xa90217e4u, // stp x4, x5, [sp, #32]
+ 0xa9031fe6u, // stp x6, x7, [sp, #48]
+ 0xa90427e8u, // stp x8, x9, [sp, #64]
+ 0xa9052feau, // stp x10, x11, [sp, #80]
+ 0xa90637ecu, // stp x12, x13, [sp, #96]
+ 0xa9073feeu, // stp x14, x15, [sp, #112]
+ 0xa90847f0u, // stp x16, x17, [sp, #128]
+ 0xa9094ff2u, // stp x18, x19, [sp, #144]
+ 0xa90a57f4u, // stp x20, x21, [sp, #160]
+ 0xf9005ffeu, // str x30, [sp, #184]
+ 0xd2800013u, // mov x19, #0x0
+ 0xf2a00013u, // movk x19, #0x0, lsl #16
+ 0xf2c00013u, // movk x19, #0x0, lsl #32
+ 0xf2e00013u, // movk x19, #0x0, lsl #48
+ GetMrsTpidrEl0(20), // mrs x20, tpidr_el0
+ 0xf8410675u, // ldr x21, [x19], #16
+ 0xeb15029fu, // cmp x20, x21
+ 0x54000040u, // b.eq 528
+ 0x17fffffdu, // b 518
+ 0xf85f8273u, // ldur x19, [x19, #-8]
+ 0x52800020u, // mov w0, #0x1
+ 0xb9031e60u, // str w0, [x19, #796]
+ 0xd2800000u, // mov x0, #0x0
+ 0xf2a00000u, // movk x0, #0x0, lsl #16
+ 0xf2c00000u, // movk x0, #0x0, lsl #32
+ 0xf2e00000u, // movk x0, #0x0, lsl #48
+ 0xd63f0000u, // blr x0
+ 0xb9031e7fu, // str wzr, [x19, #796]
+ 0xf9005be0u, // str x0, [sp, #176]
+ 0xf9405ffeu, // ldr x30, [sp, #184]
+ 0xa94a57f4u, // ldp x20, x21, [sp, #160]
+ 0xa9494ff2u, // ldp x18, x19, [sp, #144]
+ 0xa94847f0u, // ldp x16, x17, [sp, #128]
+ 0xa9473feeu, // ldp x14, x15, [sp, #112]
+ 0xa94637ecu, // ldp x12, x13, [sp, #96]
+ 0xa9452feau, // ldp x10, x11, [sp, #80]
+ 0xa94427e8u, // ldp x8, x9, [sp, #64]
+ 0xa9431fe6u, // ldp x6, x7, [sp, #48]
+ 0xa94217e4u, // ldp x4, x5, [sp, #32]
+ 0xa9410fe2u, // ldp x2, x3, [sp, #16]
+ 0xa8cb07e0u, // ldp x0, x1, [sp], #176
+ 0xf84107e0u, // ldr x0, [sp], #16
+ 0x14000000u, // b 584
+ };
+
+ private static uint GetMrsTpidrEl0(uint rd)
+ {
+ if (OperatingSystem.IsMacOS())
+ {
+ return 0xd53bd060u | rd; // TPIDRRO
+ }
+ else
+ {
+ return 0xd53bd040u | rd; // TPIDR
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Cpu/Nce/NceCpuContext.cs b/src/Ryujinx.Cpu/Nce/NceCpuContext.cs
new file mode 100644
index 000000000..04a97a696
--- /dev/null
+++ b/src/Ryujinx.Cpu/Nce/NceCpuContext.cs
@@ -0,0 +1,99 @@
+using ARMeilleure.Memory;
+using ARMeilleure.Signal;
+using Ryujinx.Cpu.Jit;
+using Ryujinx.Common;
+using Ryujinx.Memory;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Cpu.Nce
+{
+ class NceCpuContext : ICpuContext
+ {
+ private delegate void ThreadStart(IntPtr nativeContextPtr);
+ private delegate IntPtr GetTpidrEl0();
+ private static MemoryBlock _codeBlock;
+ private static ThreadStart _threadStart;
+ private static GetTpidrEl0 _getTpidrEl0;
+
+ private readonly ITickSource _tickSource;
+ private readonly ICpuMemoryManager _memoryManager;
+
+ static NceCpuContext()
+ {
+ ulong threadStartCodeSize = (ulong)NceAsmTable.ThreadStartCode.Length * 4;
+ ulong enEntryCodeOffset = threadStartCodeSize;
+ ulong ehEntryCodeSize = (ulong)NceAsmTable.ExceptionHandlerEntryCode.Length * 4;
+ ulong getTpidrEl0CodeOffset = threadStartCodeSize + ehEntryCodeSize;
+ ulong getTpidrEl0CodeSize = (ulong)NceAsmTable.GetTpidrEl0Code.Length * 4;
+
+ ulong size = BitUtils.AlignUp(threadStartCodeSize + ehEntryCodeSize + getTpidrEl0CodeSize, 0x1000UL);
+
+ MemoryBlock codeBlock = new MemoryBlock(size);
+
+ codeBlock.Write(0, MemoryMarshal.Cast(NceAsmTable.ThreadStartCode.AsSpan()));
+ codeBlock.Write(getTpidrEl0CodeOffset, MemoryMarshal.Cast(NceAsmTable.GetTpidrEl0Code.AsSpan()));
+
+ NativeSignalHandler.Initialize(new JitMemoryAllocator());
+
+ NativeSignalHandler.InitializeSignalHandler(MemoryBlock.GetPageSize(), (IntPtr oldSignalHandlerSegfaultPtr, IntPtr signalHandlerPtr) =>
+ {
+ uint[] ehEntryCode = NcePatcher.GenerateExceptionHandlerEntry(oldSignalHandlerSegfaultPtr, signalHandlerPtr);
+ codeBlock.Write(enEntryCodeOffset, MemoryMarshal.Cast(ehEntryCode.AsSpan()));
+ codeBlock.Reprotect(0, size, MemoryPermission.ReadAndExecute, true);
+ return codeBlock.GetPointer(enEntryCodeOffset, ehEntryCodeSize);
+ }, NceThreadPal.UnixSuspendSignal);
+
+ _threadStart = Marshal.GetDelegateForFunctionPointer(codeBlock.GetPointer(0, threadStartCodeSize));
+ _getTpidrEl0 = Marshal.GetDelegateForFunctionPointer(codeBlock.GetPointer(getTpidrEl0CodeOffset, getTpidrEl0CodeSize));
+ _codeBlock = codeBlock;
+ }
+
+ public NceCpuContext(ITickSource tickSource, ICpuMemoryManager memory, bool for64Bit)
+ {
+ _tickSource = tickSource;
+ _memoryManager = memory;
+ }
+
+ ///
+ public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks)
+ {
+ return new NceExecutionContext(exceptionCallbacks);
+ }
+
+ ///
+ public void Execute(IExecutionContext context, ulong address)
+ {
+ NceExecutionContext nec = (NceExecutionContext)context;
+ NceNativeInterface.RegisterThread(nec, _tickSource);
+ int tableIndex = NceThreadTable.Register(_getTpidrEl0(), nec.NativeContextPtr);
+
+ nec.SetStartAddress(address);
+ _threadStart(nec.NativeContextPtr);
+
+ NceThreadTable.Unregister(tableIndex);
+ }
+
+ ///
+ public void InvalidateCacheRegion(ulong address, ulong size)
+ {
+ }
+
+ ///
+ public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize)
+ {
+ NcePatcher.Patch(_memoryManager, textAddress, textSize, patchRegionAddress, patchRegionSize);
+ }
+
+ ///
+ public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
+ {
+ return new DiskCacheLoadState();
+ }
+
+ ///
+ public void PrepareCodeRange(ulong address, ulong size)
+ {
+ }
+ }
+}
diff --git a/src/Ryujinx.Cpu/Nce/NceEngine.cs b/src/Ryujinx.Cpu/Nce/NceEngine.cs
new file mode 100644
index 000000000..17c7ed0f7
--- /dev/null
+++ b/src/Ryujinx.Cpu/Nce/NceEngine.cs
@@ -0,0 +1,19 @@
+using ARMeilleure.Memory;
+
+namespace Ryujinx.Cpu.Nce
+{
+ public class NceEngine : ICpuEngine
+ {
+ public ITickSource TickSource{ get; }
+
+ public NceEngine(ITickSource tickSource)
+ {
+ TickSource = tickSource;
+ }
+
+ public ICpuContext CreateCpuContext(ICpuMemoryManager memoryManager, bool for64Bit)
+ {
+ return new NceCpuContext(TickSource, memoryManager, for64Bit);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Cpu/Nce/NceExecutionContext.cs b/src/Ryujinx.Cpu/Nce/NceExecutionContext.cs
new file mode 100644
index 000000000..8746cac33
--- /dev/null
+++ b/src/Ryujinx.Cpu/Nce/NceExecutionContext.cs
@@ -0,0 +1,131 @@
+using ARMeilleure.State;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Cpu.Nce
+{
+ class NceExecutionContext : IExecutionContext
+ {
+ private readonly NceNativeContext _context;
+ private readonly ExceptionCallbacks _exceptionCallbacks;
+
+ internal IntPtr NativeContextPtr => _context.BasePtr;
+
+ public ulong Pc => 0UL;
+
+ public long TpidrEl0
+ {
+ get => (long)_context.GetStorage().TpidrEl0;
+ set => _context.GetStorage().TpidrEl0 = (ulong)value;
+ }
+
+ public long TpidrroEl0
+ {
+ get => (long)_context.GetStorage().TpidrroEl0;
+ set => _context.GetStorage().TpidrroEl0 = (ulong)value;
+ }
+
+ public uint Pstate
+ {
+ get => _context.GetStorage().Pstate;
+ set => _context.GetStorage().Pstate = value;
+ }
+
+ public uint Fpcr
+ {
+ get => _context.GetStorage().Fpcr;
+ set => _context.GetStorage().Fpcr = value;
+ }
+
+ public uint Fpsr
+ {
+ get => _context.GetStorage().Fpsr;
+ set => _context.GetStorage().Fpsr = value;
+ }
+
+ public bool IsAarch32
+ {
+ get => false;
+ set
+ {
+ if (value)
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
+
+ public bool Running { get; private set; }
+
+ private delegate bool SupervisorCallHandler(int imm);
+ private SupervisorCallHandler _svcHandler;
+
+ public NceExecutionContext(ExceptionCallbacks exceptionCallbacks)
+ {
+ _svcHandler = OnSupervisorCall;
+ IntPtr svcHandlerPtr = Marshal.GetFunctionPointerForDelegate(_svcHandler);
+
+ _context = new NceNativeContext();
+
+ ref var storage = ref _context.GetStorage();
+ storage.SvcCallHandler = svcHandlerPtr;
+ storage.InManaged = 1u;
+ storage.CtrEl0 = 0x8444c004; // TODO: Get value from host CPU instead of using guest one?
+
+ Running = true;
+ _exceptionCallbacks = exceptionCallbacks;
+ }
+
+ public ulong GetX(int index) => _context.GetStorage().X[index];
+ public void SetX(int index, ulong value) => _context.GetStorage().X[index] = value;
+
+ public V128 GetV(int index) => _context.GetStorage().V[index];
+ public void SetV(int index, V128 value) => _context.GetStorage().V[index] = value;
+
+ // TODO
+ public bool GetPstateFlag(PState flag) => false;
+ public void SetPstateFlag(PState flag, bool value) { }
+
+ // TODO
+ public bool GetFPstateFlag(FPState flag) => false;
+ public void SetFPstateFlag(FPState flag, bool value) { }
+
+ public void SetStartAddress(ulong address)
+ {
+ ref var storage = ref _context.GetStorage();
+ storage.X[30] = address;
+ storage.HostThreadHandle = NceThreadPal.GetCurrentThreadHandle();
+ }
+
+ public bool OnSupervisorCall(int imm)
+ {
+ _exceptionCallbacks.SupervisorCallback?.Invoke(this, 0UL, imm);
+ return Running;
+ }
+
+ public bool OnInterrupt()
+ {
+ _exceptionCallbacks.InterruptCallback?.Invoke(this);
+ return Running;
+ }
+
+ public void RequestInterrupt()
+ {
+ IntPtr threadHandle = _context.GetStorage().HostThreadHandle;
+ if (threadHandle != IntPtr.Zero)
+ {
+ NceThreadPal.SuspendThread(threadHandle);
+ }
+ }
+
+ public void StopRunning()
+ {
+ Running = false;
+ }
+
+ public void Dispose()
+ {
+ _context.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Cpu/Nce/NceMemoryAllocator.cs b/src/Ryujinx.Cpu/Nce/NceMemoryAllocator.cs
new file mode 100644
index 000000000..34badd7ab
--- /dev/null
+++ b/src/Ryujinx.Cpu/Nce/NceMemoryAllocator.cs
@@ -0,0 +1,13 @@
+using ARMeilleure.Memory;
+using Ryujinx.Memory;
+
+namespace Ryujinx.Cpu.Nce
+{
+ class NceMemoryAllocator : IJitMemoryAllocator
+ {
+ public IJitMemoryBlock Allocate(ulong size) => new NceMemoryBlock(size, MemoryAllocationFlags.None);
+ public IJitMemoryBlock Reserve(ulong size) => new NceMemoryBlock(size, MemoryAllocationFlags.Reserve);
+
+ public ulong GetPageSize() => MemoryBlock.GetPageSize();
+ }
+}
diff --git a/src/Ryujinx.Cpu/Nce/NceMemoryBlock.cs b/src/Ryujinx.Cpu/Nce/NceMemoryBlock.cs
new file mode 100644
index 000000000..a8f0b8e0e
--- /dev/null
+++ b/src/Ryujinx.Cpu/Nce/NceMemoryBlock.cs
@@ -0,0 +1,24 @@
+using ARMeilleure.Memory;
+using Ryujinx.Memory;
+using System;
+
+namespace Ryujinx.Cpu.Nce
+{
+ class NceMemoryBlock : IJitMemoryBlock
+ {
+ private readonly MemoryBlock _impl;
+
+ public IntPtr Pointer => _impl.Pointer;
+
+ public NceMemoryBlock(ulong size, MemoryAllocationFlags flags)
+ {
+ _impl = new MemoryBlock(size, flags);
+ }
+
+ public void Commit(ulong offset, ulong size) => _impl.Commit(offset, size);
+ public void MapAsRx(ulong offset, ulong size) => _impl.Reprotect(offset, size, MemoryPermission.ReadAndExecute);
+ public void MapAsRwx(ulong offset, ulong size) => _impl.Reprotect(offset, size, MemoryPermission.ReadWriteExecute);
+
+ public void Dispose() => _impl.Dispose();
+ }
+}
diff --git a/src/Ryujinx.Cpu/Nce/NceNativeContext.cs b/src/Ryujinx.Cpu/Nce/NceNativeContext.cs
new file mode 100644
index 000000000..4db41f347
--- /dev/null
+++ b/src/Ryujinx.Cpu/Nce/NceNativeContext.cs
@@ -0,0 +1,43 @@
+using ARMeilleure.State;
+using Ryujinx.Common.Memory;
+using Ryujinx.Memory;
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Cpu.Nce
+{
+ class NceNativeContext : IDisposable
+ {
+ public struct NativeCtxStorage
+ {
+ public Array32 X;
+ public Array32 V;
+ public ulong TpidrEl0; // 0x300
+ public ulong TpidrroEl0; // 0x308
+ public uint Pstate; // 0x310
+ public uint Fpcr; // 0x314
+ public uint Fpsr; // 0x318
+ public uint InManaged; // 0x31C
+ public ulong HostSp; // 0x320
+ public IntPtr HostThreadHandle; // 0x328
+ public ulong HostX30; // 0x330
+ public ulong CtrEl0; // 0x338
+ public ulong Reserved340; // 0x340
+ public ulong Reserved348; // 0x348
+ public IntPtr SvcCallHandler; // 0x350
+ }
+
+ private readonly MemoryBlock _block;
+
+ public IntPtr BasePtr => _block.Pointer;
+
+ public NceNativeContext()
+ {
+ _block = new MemoryBlock((ulong)Unsafe.SizeOf());
+ }
+
+ public unsafe ref NativeCtxStorage GetStorage() => ref Unsafe.AsRef((void*)_block.Pointer);
+
+ public void Dispose() => _block.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Cpu/Nce/NceNativeInterface.cs b/src/Ryujinx.Cpu/Nce/NceNativeInterface.cs
new file mode 100644
index 000000000..843af8b5c
--- /dev/null
+++ b/src/Ryujinx.Cpu/Nce/NceNativeInterface.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Cpu.Nce
+{
+ static class NceNativeInterface
+ {
+ private delegate ulong GetTickCounterDelegate();
+ private delegate bool SuspendThreadHandlerDelegate();
+ private static GetTickCounterDelegate _getTickCounter;
+ private static SuspendThreadHandlerDelegate _suspendThreadHandler;
+ private static IntPtr _getTickCounterPtr;
+ private static IntPtr _suspendThreadHandlerPtr;
+
+ [ThreadStatic]
+ private static NceExecutionContext _context;
+
+ [ThreadStatic]
+ private static ITickSource _tickSource;
+
+ static NceNativeInterface()
+ {
+ _getTickCounter = GetTickCounter;
+ _suspendThreadHandler = SuspendThreadHandler;
+ _getTickCounterPtr = Marshal.GetFunctionPointerForDelegate(_getTickCounter);
+ _suspendThreadHandlerPtr = Marshal.GetFunctionPointerForDelegate(_suspendThreadHandler);
+ }
+
+ public static void RegisterThread(NceExecutionContext context, ITickSource tickSource)
+ {
+ _context = context;
+ _tickSource = tickSource;
+ }
+
+ public static ulong GetTickCounter()
+ {
+ return _tickSource.Counter;
+ }
+
+ public static bool SuspendThreadHandler()
+ {
+ return _context.OnInterrupt();
+ }
+
+ public static IntPtr GetTickCounterAccessFunctionPointer()
+ {
+ return _getTickCounterPtr;
+ }
+
+ public static IntPtr GetSuspendThreadHandlerFunctionPointer()
+ {
+ return _suspendThreadHandlerPtr;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Cpu/Nce/NcePatcher.cs b/src/Ryujinx.Cpu/Nce/NcePatcher.cs
new file mode 100644
index 000000000..ee2c57578
--- /dev/null
+++ b/src/Ryujinx.Cpu/Nce/NcePatcher.cs
@@ -0,0 +1,293 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.Memory;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Cpu.Nce
+{
+ static class NcePatcher
+ {
+ private struct Context
+ {
+ public readonly ICpuMemoryManager MemoryManager;
+ public ulong PatchRegionAddress;
+ public ulong PatchRegionSize;
+
+ public Context(ICpuMemoryManager memoryManager, ulong patchRegionAddress, ulong patchRegionSize)
+ {
+ MemoryManager = memoryManager;
+ PatchRegionAddress = patchRegionAddress;
+ PatchRegionSize = patchRegionSize;
+ }
+
+ public ulong GetPatchWriteAddress(int length)
+ {
+ ulong byteLength = (ulong)length * 4;
+
+ if (PatchRegionSize < byteLength)
+ {
+ throw new Exception("No enough space for patch.");
+ }
+
+ ulong address = PatchRegionAddress;
+ PatchRegionAddress += byteLength;
+ PatchRegionSize -= byteLength;
+
+ return address;
+ }
+ }
+
+ public static void Patch(
+ ICpuMemoryManager memoryManager,
+ ulong textAddress,
+ ulong textSize,
+ ulong patchRegionAddress,
+ ulong patchRegionSize)
+ {
+ Context context = new Context(memoryManager, patchRegionAddress, patchRegionSize);
+
+ ulong address = textAddress;
+ while (address < textAddress + textSize)
+ {
+ uint inst = memoryManager.Read(address);
+
+ if ((inst & ~(0xffffu << 5)) == 0xd4000001u) // svc #0
+ {
+ uint svcId = (ushort)(inst >> 5);
+ PatchInstruction(memoryManager, address, WriteSvcPatch(ref context, address, svcId));
+ Logger.Debug?.Print(LogClass.Cpu, $"Patched SVC #{svcId} at 0x{address:X}.");
+ }
+ else if ((inst & ~0x1f) == 0xd53bd060) // mrs x0, tpidrro_el0
+ {
+ uint rd = inst & 0x1f;
+ PatchInstruction(memoryManager, address, WriteMrsTpidrroEl0Patch(ref context, address, rd));
+ Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, tpidrro_el0 at 0x{address:X}.");
+ }
+ else if ((inst & ~0x1f) == 0xd53bd040) // mrs x0, tpidr_el0
+ {
+ uint rd = inst & 0x1f;
+ PatchInstruction(memoryManager, address, WriteMrsTpidrEl0Patch(ref context, address, rd));
+ Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, tpidr_el0 at 0x{address:X}.");
+ }
+ else if ((inst & ~0x1f) == 0xd53b0020 && OperatingSystem.IsMacOS()) // mrs x0, ctr_el0
+ {
+ uint rd = inst & 0x1f;
+ PatchInstruction(memoryManager, address, WriteMrsCtrEl0Patch(ref context, address, rd));
+ Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, ctr_el0 at 0x{address:X}.");
+ }
+ else if ((inst & ~0x1f) == 0xd51bd040) // msr tpidr_el0, x0
+ {
+ uint rd = inst & 0x1f;
+ PatchInstruction(memoryManager, address, WriteMsrTpidrEl0Patch(ref context, address, rd));
+ Logger.Debug?.Print(LogClass.Cpu, $"Patched MSR tpidr_el0, x{rd} at 0x{address:X}.");
+ }
+ else if ((inst & ~0x1f) == 0xd53be020) // mrs x0, cntpct_el0
+ {
+ uint rd = inst & 0x1f;
+ PatchInstruction(memoryManager, address, WriteMrsCntpctEl0Patch(ref context, address, rd));
+ Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, cntpct_el0 at 0x{address:X}.");
+ }
+
+ address += 4;
+ }
+
+ ulong patchRegionConsumed = BitUtils.AlignUp(context.PatchRegionAddress - patchRegionAddress, 0x1000UL);
+ if (patchRegionConsumed != 0)
+ {
+ memoryManager.Reprotect(patchRegionAddress, patchRegionConsumed, MemoryPermission.ReadAndExecute);
+ }
+ }
+
+ private static void PatchInstruction(ICpuMemoryManager memoryManager, ulong instructionAddress, ulong targetAddress)
+ {
+ memoryManager.Write(instructionAddress, 0x14000000u | GetImm26(instructionAddress, targetAddress));
+ }
+
+ private static ulong WriteSvcPatch(ref Context context, ulong svcAddress, uint svcId)
+ {
+ uint[] code = GetCopy(NceAsmTable.SvcPatchCode);
+
+ int movIndex = Array.IndexOf(code, 0xd2800013u);
+
+ WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer);
+
+ int mov2Index = Array.IndexOf(code, 0x52800000u, movIndex + 1);
+
+ ulong targetAddress = context.GetPatchWriteAddress(code.Length);
+
+ code[mov2Index] |= svcId << 5;
+ code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, svcAddress + 4);
+
+ WriteCode(context.MemoryManager, targetAddress, code);
+
+ return targetAddress;
+ }
+
+ private static ulong WriteMrsTpidrroEl0Patch(ref Context context, ulong mrsAddress, uint rd)
+ {
+ uint[] code = GetCopy(NceAsmTable.MrsTpidrroEl0PatchCode);
+
+ int movIndex = Array.IndexOf(code, 0xd2800013u);
+
+ WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer);
+
+ ulong targetAddress = context.GetPatchWriteAddress(code.Length);
+
+ code[code.Length - 2] |= rd;
+ code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, mrsAddress + 4);
+
+ WriteCode(context.MemoryManager, targetAddress, code);
+
+ return targetAddress;
+ }
+
+ private static ulong WriteMrsTpidrEl0Patch(ref Context context, ulong mrsAddress, uint rd)
+ {
+ uint[] code = GetCopy(NceAsmTable.MrsTpidrEl0PatchCode);
+
+ int movIndex = Array.IndexOf(code, 0xd2800013u);
+
+ WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer);
+
+ ulong targetAddress = context.GetPatchWriteAddress(code.Length);
+
+ code[code.Length - 2] |= rd;
+ code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, mrsAddress + 4);
+
+ WriteCode(context.MemoryManager, targetAddress, code);
+
+ return targetAddress;
+ }
+
+ private static ulong WriteMrsCtrEl0Patch(ref Context context, ulong mrsAddress, uint rd)
+ {
+ uint[] code = GetCopy(NceAsmTable.MrsCtrEl0PatchCode);
+
+ int movIndex = Array.IndexOf(code, 0xd2800013u);
+
+ WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer);
+
+ ulong targetAddress = context.GetPatchWriteAddress(code.Length);
+
+ code[code.Length - 2] |= rd;
+ code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, mrsAddress + 4);
+
+ WriteCode(context.MemoryManager, targetAddress, code);
+
+ return targetAddress;
+ }
+
+ private static ulong WriteMsrTpidrEl0Patch(ref Context context, ulong msrAddress, uint rd)
+ {
+ uint r2 = rd == 0 ? 1u : 0u;
+
+ uint[] code = GetCopy(NceAsmTable.MsrTpidrEl0PatchCode);
+
+ code[0] |= rd << 10;
+
+ int movIndex = Array.IndexOf(code, 0xd2800013u);
+
+ WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer);
+
+ ulong targetAddress = context.GetPatchWriteAddress(code.Length);
+
+ code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, msrAddress + 4);
+
+ WriteCode(context.MemoryManager, targetAddress, code);
+
+ return targetAddress;
+ }
+
+ private static ulong WriteMrsCntpctEl0Patch(ref Context context, ulong mrsAddress, uint rd)
+ {
+ uint[] code = GetCopy(NceAsmTable.MrsCntpctEl0PatchCode);
+
+ int movIndex = Array.IndexOf(code, 0xd2800013u);
+
+ WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer);
+
+ int mov2Index = Array.IndexOf(code, 0xD2800000u, movIndex + 1);
+
+ WriteTickCounterAccessFunctionPointer(code, mov2Index);
+
+ ulong targetAddress = context.GetPatchWriteAddress(code.Length);
+
+ code[code.Length - 2] |= rd;
+ code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, mrsAddress + 4);
+
+ WriteCode(context.MemoryManager, targetAddress, code);
+
+ return targetAddress;
+ }
+
+ public static uint[] GenerateExceptionHandlerEntry(IntPtr oldSignalHandlerSegfaultPtr, IntPtr signalHandlerPtr)
+ {
+ uint[] code = GetCopy(NceAsmTable.ExceptionHandlerEntryCode);
+
+ int movIndex = Array.IndexOf(code, 0xd2800018u);
+
+ WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer);
+
+ int mov2Index = Array.IndexOf(code, 0xd2800008u, movIndex + 1);
+
+ WritePointer(code, mov2Index, (ulong)signalHandlerPtr);
+
+ int mov3Index = Array.IndexOf(code, 0xd2800000u, mov2Index + 1);
+
+ WritePointer(code, mov3Index, (ulong)NceNativeInterface.GetSuspendThreadHandlerFunctionPointer());
+
+ int mov4Index = Array.IndexOf(code, 0xd2800003u, mov3Index + 1);
+
+ WritePointer(code, mov4Index, (ulong)oldSignalHandlerSegfaultPtr);
+
+ int cmpIndex = Array.IndexOf(code, 0x7100027fu);
+
+ code[cmpIndex] |= (uint)NceThreadPal.UnixSuspendSignal << 10;
+
+ return code;
+ }
+
+ private static void WriteTickCounterAccessFunctionPointer(uint[] code, int movIndex)
+ {
+ WritePointer(code, movIndex, (ulong)NceNativeInterface.GetTickCounterAccessFunctionPointer());
+ }
+
+ private static void WritePointer(uint[] code, int movIndex, ulong ptr)
+ {
+ code[movIndex] |= (uint)(ushort)ptr << 5;
+ code[movIndex + 1] |= (uint)(ushort)(ptr >> 16) << 5;
+ code[movIndex + 2] |= (uint)(ushort)(ptr >> 32) << 5;
+ code[movIndex + 3] |= (uint)(ushort)(ptr >> 48) << 5;
+ }
+
+ private static uint GetImm26(ulong sourceAddress, ulong targetAddress)
+ {
+ long offset = (long)(targetAddress - sourceAddress);
+ long offsetTrunc = (offset >> 2) & 0x3FFFFFF;
+
+ if ((offsetTrunc << 38) >> 36 != offset)
+ {
+ throw new Exception($"Offset out of range: 0x{sourceAddress:X} -> 0x{targetAddress:X} (0x{offset:X})");
+ }
+
+ return (uint)offsetTrunc;
+ }
+
+ private static uint[] GetCopy(uint[] code)
+ {
+ uint[] codeCopy = new uint[code.Length];
+ code.CopyTo(codeCopy, 0);
+
+ return codeCopy;
+ }
+
+ private static void WriteCode(ICpuMemoryManager memoryManager, ulong address, uint[] code)
+ {
+ for (int i = 0; i < code.Length; i++)
+ {
+ memoryManager.Write(address + (ulong)i * sizeof(uint), code[i]);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Cpu/Nce/NceThreadPal.cs b/src/Ryujinx.Cpu/Nce/NceThreadPal.cs
new file mode 100644
index 000000000..b59507995
--- /dev/null
+++ b/src/Ryujinx.Cpu/Nce/NceThreadPal.cs
@@ -0,0 +1,40 @@
+using System;
+
+namespace Ryujinx.Cpu.Nce
+{
+ static class NceThreadPal
+ {
+ private const int SigUsr2Linux = 12;
+ private const int SigUsr2MacOS = 31;
+
+ public static int UnixSuspendSignal => OperatingSystem.IsMacOS() ? SigUsr2MacOS : SigUsr2Linux;
+
+ public static IntPtr GetCurrentThreadHandle()
+ {
+ if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsAndroid())
+ {
+ return NceThreadPalUnix.GetCurrentThreadHandle();
+ }
+ else
+ {
+ throw new PlatformNotSupportedException();
+ }
+ }
+
+ public static void SuspendThread(IntPtr handle)
+ {
+ if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+ {
+ NceThreadPalUnix.SuspendThread(handle);
+ }
+ else if (OperatingSystem.IsAndroid())
+ {
+ NceThreadPalAndroid.SuspendThread(handle);
+ }
+ else
+ {
+ throw new PlatformNotSupportedException();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Cpu/Nce/NceThreadPalAndroid.cs b/src/Ryujinx.Cpu/Nce/NceThreadPalAndroid.cs
new file mode 100644
index 000000000..65bfd1bfb
--- /dev/null
+++ b/src/Ryujinx.Cpu/Nce/NceThreadPalAndroid.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Cpu.Nce
+{
+ static class NceThreadPalAndroid
+ {
+ [DllImport("libc", SetLastError = true)]
+ private static extern int pthread_kill(IntPtr thread, int sig);
+
+ public static void SuspendThread(IntPtr handle)
+ {
+ int result = pthread_kill(handle, NceThreadPal.UnixSuspendSignal);
+ if (result != 0)
+ {
+ throw new Exception($"Thread kill returned error 0x{result:X}.");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Cpu/Nce/NceThreadPalUnix.cs b/src/Ryujinx.Cpu/Nce/NceThreadPalUnix.cs
new file mode 100644
index 000000000..9f4877615
--- /dev/null
+++ b/src/Ryujinx.Cpu/Nce/NceThreadPalUnix.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Cpu.Nce
+{
+ static class NceThreadPalUnix
+ {
+ [DllImport("libc", SetLastError = true)]
+ private static extern IntPtr pthread_self();
+
+ [DllImport("libc", SetLastError = true)]
+ private static extern int pthread_threadid_np(IntPtr arg0, out ulong tid);
+
+ [DllImport("libpthread", SetLastError = true)]
+ private static extern int pthread_kill(IntPtr thread, int sig);
+
+ public static IntPtr GetCurrentThreadHandle()
+ {
+ return pthread_self();
+ }
+
+ public static ulong GetCurrentThreadId()
+ {
+ pthread_threadid_np(IntPtr.Zero, out ulong tid);
+ return tid;
+ }
+
+ public static void SuspendThread(IntPtr handle)
+ {
+ int result = pthread_kill(handle, NceThreadPal.UnixSuspendSignal);
+ if (result != 0)
+ {
+ throw new Exception($"Thread kill returned error 0x{result:X}.");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Cpu/Nce/NceThreadTable.cs b/src/Ryujinx.Cpu/Nce/NceThreadTable.cs
new file mode 100644
index 000000000..8fe39b7bd
--- /dev/null
+++ b/src/Ryujinx.Cpu/Nce/NceThreadTable.cs
@@ -0,0 +1,97 @@
+using Ryujinx.Memory;
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Cpu.Nce
+{
+ static class NceThreadTable
+ {
+ private const int MaxThreads = 4096;
+
+ private struct Entry
+ {
+ public IntPtr ThreadId;
+ public IntPtr NativeContextPtr;
+
+ public Entry(IntPtr threadId, IntPtr nativeContextPtr)
+ {
+ ThreadId = threadId;
+ NativeContextPtr = nativeContextPtr;
+ }
+ }
+
+ private static MemoryBlock _block;
+
+ public static IntPtr EntriesPointer => _block.Pointer + 8;
+
+ static NceThreadTable()
+ {
+ _block = new MemoryBlock((ulong)Unsafe.SizeOf() * MaxThreads + 8UL);
+ _block.Write(0UL, 0UL);
+ }
+
+ public static int Register(IntPtr threadId, IntPtr nativeContextPtr)
+ {
+ Span entries = GetStorage();
+
+ lock (_block)
+ {
+ ref ulong currentThreadCount = ref GetThreadsCount();
+
+ for (int i = 0; i < MaxThreads; i++)
+ {
+ if (entries[i].ThreadId == IntPtr.Zero)
+ {
+ entries[i] = new Entry(threadId, nativeContextPtr);
+
+ if (currentThreadCount < (ulong)i + 1)
+ {
+ currentThreadCount = (ulong)i + 1;
+ }
+
+ return i;
+ }
+ }
+ }
+
+ throw new Exception($"Number of active threads exceeds limit of {MaxThreads}.");
+ }
+
+ public static void Unregister(int tableIndex)
+ {
+ Span entries = GetStorage();
+
+ lock (_block)
+ {
+ if (entries[tableIndex].ThreadId != IntPtr.Zero)
+ {
+ entries[tableIndex] = default;
+
+ ulong currentThreadCount = GetThreadsCount();
+
+ for (int i = (int)currentThreadCount - 1; i >= 0; i--)
+ {
+ if (entries[i].ThreadId != IntPtr.Zero)
+ {
+ break;
+ }
+
+ currentThreadCount = (ulong)i;
+ }
+
+ GetThreadsCount() = currentThreadCount;
+ }
+ }
+ }
+
+ private static ref ulong GetThreadsCount()
+ {
+ return ref _block.GetRef(0UL);
+ }
+
+ private static unsafe Span GetStorage()
+ {
+ return new Span((void*)_block.GetPointer(8UL, (ulong)Unsafe.SizeOf() * MaxThreads), MaxThreads);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Cpu/Nce/nce.S b/src/Ryujinx.Cpu/Nce/nce.S
new file mode 100644
index 000000000..bf60676af
--- /dev/null
+++ b/src/Ryujinx.Cpu/Nce/nce.S
@@ -0,0 +1,378 @@
+.text
+
+.macro longmov0 reg
+mov \reg, #0
+movk \reg, #0, lsl #16
+movk \reg, #0, lsl #32
+movk \reg, #0, lsl #48
+.endm
+
+// r1 = EntriesPointer
+// r2 = current_thread_id_local
+// r3 = expected_thread_id
+// r4 = ThreadsCount_local
+.macro loadctxptr_reg r1, r2, r3
+longmov0 \r1
+mrs \r2, tpidr_el0
+1:
+ldr \r3, [\r1], #16
+cmp \r2, \r3
+beq 2f
+b 1b
+2:
+ldr \r1, [\r1, #-8]
+.endm
+
+.macro loadctxptr
+loadctxptr_reg x19, x20, x21
+.endm
+
+.global GetTpidrEl0Code
+GetTpidrEl0Code:
+mrs x0, tpidr_el0
+ret
+
+.global ThreadStartCode
+ThreadStartCode:
+stp x19, x20, [sp, #-0x120]!
+stp x21, x22, [sp, #0x10]
+stp x23, x24, [sp, #0x20]
+stp x25, x26, [sp, #0x30]
+stp x27, x28, [sp, #0x40]
+stp x29, x30, [sp, #0x50]
+stp d8, d9, [sp, #0x60]
+stp d10, d11, [sp, #0x70]
+stp d12, d13, [sp, #0x80]
+stp d14, d15, [sp, #0x90]
+stp d16, d17, [sp, #0xA0]
+stp d18, d19, [sp, #0xB0]
+stp d20, d21, [sp, #0xC0]
+stp d22, d23, [sp, #0xD0]
+stp d24, d25, [sp, #0xE0]
+stp d26, d27, [sp, #0xF0]
+stp d28, d29, [sp, #0x100]
+stp d30, d31, [sp, #0x110]
+str wzr, [x0, #0x31C]
+mov x1, sp
+str x1, [x0, #0x320]
+ldp x2, x3, [x0, #0x10]
+ldp x4, x5, [x0, #0x20]
+ldp x6, x7, [x0, #0x30]
+ldp x8, x9, [x0, #0x40]
+ldp x10, x11, [x0, #0x50]
+ldp x12, x13, [x0, #0x60]
+ldp x14, x15, [x0, #0x70]
+ldp x16, x17, [x0, #0x80]
+ldp x18, x19, [x0, #0x90]
+ldp x20, x21, [x0, #0xA0]
+ldp x22, x23, [x0, #0xB0]
+ldp x24, x25, [x0, #0xC0]
+ldp x26, x27, [x0, #0xD0]
+ldp x28, x29, [x0, #0xE0]
+ldp q0, q1, [x0, #0x100]
+ldp q2, q3, [x0, #0x120]
+ldp q4, q5, [x0, #0x140]
+ldp q6, q7, [x0, #0x160]
+ldp q8, q9, [x0, #0x180]
+ldp q10, q11, [x0, #0x1A0]
+ldp q12, q13, [x0, #0x1C0]
+ldp q14, q15, [x0, #0x1E0]
+ldp q16, q17, [x0, #0x200]
+ldp q18, q19, [x0, #0x220]
+ldp q20, q21, [x0, #0x240]
+ldp q22, q23, [x0, #0x260]
+ldp q24, q25, [x0, #0x280]
+ldp q26, q27, [x0, #0x2A0]
+ldp q28, q29, [x0, #0x2C0]
+ldp q30, q31, [x0, #0x2E0]
+ldp x30, x1, [x0, #0xF0]
+mov sp, x1
+ldp x0, x1, [x0, #0x0]
+br x30
+
+// Inputs
+// r1 = EntriesPointer
+// r2 = current_thread_id_local
+// r3 = expected_thread_id
+// r4 = EntriesPointerEnd
+
+// Outputs
+// r1 = EntryPointer or 0x0 on not found
+.macro loadctxptr_safe_reg r1, r2, r3, r4
+longmov0 \r1
+ldr \r4, [\r1, #-8]
+add \r4, \r1, \r4, lsl #4
+mrs \r2, tpidr_el0
+1:
+cmp \r1, \r4
+beq 2f
+ldr \r3, [\r1], #16
+cmp \r2, \r3
+beq 3f
+b 1b
+2:
+mov \r1, 0x0
+b 4f
+3:
+ldr \r1, [\r1, #-8]
+4:
+.endm
+
+.global ExceptionHandlerEntryCode
+ExceptionHandlerEntryCode:
+stp x19, x20, [sp, #-0x40]!
+stp x21, x22, [sp, #0x10]
+stp x23, x24, [sp, #0x20]
+str x25, [sp, #0x30]
+// signo
+mov x19, x0
+// siginfo_t *si
+mov x20, x1
+// void *thread_id
+mov x21, x2
+mov x22, sp
+mov x23, x30
+// x24 = EntriesPointer
+// x1 = si
+// x2 = thread_id
+loadctxptr_safe_reg x24, x1, x2, x25
+cbz x24, 4f
+ldr x0, [x24, 0x320]
+mov sp, x0
+cmp w19, #0
+beq 1f
+mov w0, #1
+str w0, [x24, 0x31C]
+mov x0, x19
+mov x1, x20
+mov x2, x21
+mov x8, #0
+movk x8, #0, lsl #16
+movk x8, #0, lsl #32
+movk x8, #0, lsl #48
+blr x8
+b 2f
+1:
+ldr w0, [x24, 0x31C]
+cbnz w0, 3f
+mov w0, #1
+str w0, [x24, 0x31C]
+mov x0, #0
+movk x0, #0, lsl #16
+movk x0, #0, lsl #32
+movk x0, #0, lsl #48
+blr x0
+2:
+str wzr, [x24, 0x31C]
+3:
+mov sp, x22
+mov x30, x23
+ldp x21, x22, [sp, #0x10]
+ldp x23, x24, [sp, #0x20]
+ldp x25, x26, [sp, #0x30]
+ldp x19, x20, [sp], #0x40
+ret
+4:
+// ThreadId is invalid, forward to other handler.
+mov x0, x19
+mov x1, x20
+mov x2, x21
+mov sp, x22
+ldp x21, x22, [sp, #0x10]
+ldp x23, x24, [sp, #0x20]
+ldr x25, [sp, #0x30]
+ldp x19, x20, [sp], #0x40
+longmov0 x3
+br x3
+
+.global SvcPatchCode
+SvcPatchCode:
+
+stp x19, x20, [sp, #-0x20]!
+str x21, [sp, #0x10]
+loadctxptr
+stp x0, x1, [x19, #0x0]
+stp x2, x3, [x19, #0x10]
+stp x4, x5, [x19, #0x20]
+stp x6, x7, [x19, #0x30]
+stp x8, x9, [x19, #0x40]
+stp x10, x11, [x19, #0x50]
+stp x12, x13, [x19, #0x60]
+stp x14, x15, [x19, #0x70]
+stp x16, x17, [x19, #0x80]
+ldr x21, [sp, #0x10]
+ldp x0, x20, [sp], #0x20
+stp x18, x0, [x19, #0x90]
+stp x20, x21, [x19, #0xA0]
+stp x22, x23, [x19, #0xB0]
+stp x24, x25, [x19, #0xC0]
+stp x26, x27, [x19, #0xD0]
+stp x28, x29, [x19, #0xE0]
+mov x0, sp
+stp x30, x0, [x19, #0xF0]
+stp q0, q1, [x19, #0x100]
+stp q2, q3, [x19, #0x120]
+stp q4, q5, [x19, #0x140]
+stp q6, q7, [x19, #0x160]
+stp q8, q9, [x19, #0x180]
+stp q10, q11, [x19, #0x1A0]
+stp q12, q13, [x19, #0x1C0]
+stp q14, q15, [x19, #0x1E0]
+stp q16, q17, [x19, #0x200]
+stp q18, q19, [x19, #0x220]
+stp q20, q21, [x19, #0x240]
+stp q22, q23, [x19, #0x260]
+stp q24, q25, [x19, #0x280]
+stp q26, q27, [x19, #0x2A0]
+stp q28, q29, [x19, #0x2C0]
+stp q30, q31, [x19, #0x2E0]
+ldr x0, [x19, #0x320]
+mov sp, x0
+mov w0, #1
+str w0, [x19, #0x31C]
+mov w0, #0
+ldr x8, [x19, #0x350]
+blr x8
+cbnz w0, 1f
+ldp d30, d31, [sp, #0x110]
+ldp d28, d29, [sp, #0x100]
+ldp d26, d27, [sp, #0xF0]
+ldp d24, d25, [sp, #0xE0]
+ldp d22, d23, [sp, #0xD0]
+ldp d20, d21, [sp, #0xC0]
+ldp d18, d19, [sp, #0xB0]
+ldp d16, d17, [sp, #0xA0]
+ldp d14, d15, [sp, #0x90]
+ldp d12, d13, [sp, #0x80]
+ldp d10, d11, [sp, #0x70]
+ldp d8, d9, [sp, #0x60]
+ldp x29, x30, [sp, #0x50]
+ldp x27, x28, [sp, #0x40]
+ldp x25, x26, [sp, #0x30]
+ldp x23, x24, [sp, #0x20]
+ldp x21, x22, [sp, #0x10]
+ldp x19, x20, [sp], #0x120
+ret
+1:
+str wzr, [x19, #0x31C]
+ldp x30, x0, [x19, #0xF0]
+mov sp, x0
+ldp x0, x1, [x19, #0x0]
+ldp x2, x3, [x19, #0x10]
+ldp x4, x5, [x19, #0x20]
+ldp x6, x7, [x19, #0x30]
+ldp x8, x9, [x19, #0x40]
+ldp x10, x11, [x19, #0x50]
+ldp x12, x13, [x19, #0x60]
+ldp x14, x15, [x19, #0x70]
+ldp x16, x17, [x19, #0x80]
+ldr x18, [x19, #0x90]
+ldp x20, x21, [x19, #0xA0]
+ldp x22, x23, [x19, #0xB0]
+ldp x24, x25, [x19, #0xC0]
+ldp x26, x27, [x19, #0xD0]
+ldp x28, x29, [x19, #0xE0]
+ldp q0, q1, [x19, #0x100]
+ldp q2, q3, [x19, #0x120]
+ldp q4, q5, [x19, #0x140]
+ldp q6, q7, [x19, #0x160]
+ldp q8, q9, [x19, #0x180]
+ldp q10, q11, [x19, #0x1A0]
+ldp q12, q13, [x19, #0x1C0]
+ldp q14, q15, [x19, #0x1E0]
+ldp q16, q17, [x19, #0x200]
+ldp q18, q19, [x19, #0x220]
+ldp q20, q21, [x19, #0x240]
+ldp q22, q23, [x19, #0x260]
+ldp q24, q25, [x19, #0x280]
+ldp q26, q27, [x19, #0x2A0]
+ldp q28, q29, [x19, #0x2C0]
+ldp q30, q31, [x19, #0x2E0]
+ldr x19, [x19, #0x98]
+b #0
+
+.global MrsTpidrroEl0PatchCode
+MrsTpidrroEl0PatchCode:
+stp xzr, x19, [sp, #-0x20]!
+stp x20, x21, [sp, #0x10]
+loadctxptr
+ldr x19, [x19, #0x308]
+str x19, [sp]
+ldp x20, x21, [sp, #0x10]
+ldr x19, [sp, #8]
+ldr x0, [sp], #0x20
+b #0
+
+.global MrsTpidrEl0PatchCode
+MrsTpidrEl0PatchCode:
+stp xzr, x19, [sp, #-0x20]!
+stp x20, x21, [sp, #0x10]
+loadctxptr
+ldr x19, [x19, #0x300]
+str x19, [sp]
+ldp x20, x21, [sp, #0x10]
+ldr x19, [sp, #8]
+ldr x0, [sp], #0x20
+b #0
+
+.global MrsCtrEl0PatchCode
+MrsCtrEl0PatchCode:
+stp xzr, x19, [sp, #-0x20]!
+stp x20, x21, [sp, #0x10]
+loadctxptr
+ldr x19, [x19, #0x338]
+str x19, [sp]
+ldp x20, x21, [sp, #0x10]
+ldr x19, [sp, #8]
+ldr x0, [sp], #0x20
+b #0
+
+.global MsrTpidrEl0PatchCode
+MsrTpidrEl0PatchCode:
+stp x19, x0, [sp, #-0x20]!
+stp x20, x21, [sp, #0x10]
+loadctxptr
+ldr x20, [sp, #8]
+str x20, [x19, #0x300]
+ldp x20, x21, [sp, #0x10]
+ldr x19, [sp], #0x20
+b #0
+
+.global MrsCntpctEl0PatchCode
+MrsCntpctEl0PatchCode:
+stp x0, x1, [sp, #-0xC0]!
+stp x2, x3, [sp, #0x10]
+stp x4, x5, [sp, #0x20]
+stp x6, x7, [sp, #0x30]
+stp x8, x9, [sp, #0x40]
+stp x10, x11, [sp, #0x50]
+stp x12, x13, [sp, #0x60]
+stp x14, x15, [sp, #0x70]
+stp x16, x17, [sp, #0x80]
+stp x18, x19, [sp, #0x90]
+stp x20, x21, [sp, #0xA0]
+str x30, [sp, #0xB8]
+loadctxptr
+mov w0, #1
+str w0, [x19, #0x31C]
+mov x0, #0
+movk x0, #0, lsl #16
+movk x0, #0, lsl #32
+movk x0, #0, lsl #48
+blr x0
+str wzr, [x19, #0x31C]
+str x0, [sp, #0xB0]
+ldr x30, [sp, #0xB8]
+ldp x20, x21, [sp, #0xA0]
+ldp x18, x19, [sp, #0x90]
+ldp x16, x17, [sp, #0x80]
+ldp x14, x15, [sp, #0x70]
+ldp x12, x13, [sp, #0x60]
+ldp x10, x11, [sp, #0x50]
+ldp x8, x9, [sp, #0x40]
+ldp x6, x7, [sp, #0x30]
+ldp x4, x5, [sp, #0x20]
+ldp x2, x3, [sp, #0x10]
+ldp x0, x1, [sp], #0xB0
+ldr x0, [sp], #0x10
+b #0
diff --git a/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs b/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs
index ad6e688cc..c37fd870e 100644
--- a/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs
+++ b/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs
@@ -70,7 +70,7 @@ namespace Ryujinx.Cpu.Signal
config = new SignalHandlerConfig();
}
- public static void InitializeSignalHandler(ulong pageSize, Func customSignalHandlerFactory = null)
+ public static void InitializeSignalHandler(ulong pageSize, Func customSignalHandlerFactory = null, int userSignal = -1)
{
if (_initialized)
{
@@ -103,7 +103,7 @@ namespace Ryujinx.Cpu.Signal
_signalHandlerPtr = customSignalHandlerFactory(UnixSignalHandlerRegistration.GetSegfaultExceptionHandler().sa_handler, _signalHandlerPtr);
}
- var old = UnixSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr);
+ var old = UnixSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr, userSignal);
config.UnixOldSigaction = (nuint)(ulong)old.sa_handler;
config.UnixOldSigaction3Arg = old.sa_flags & 4;
diff --git a/src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs b/src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs
index e659b236e..80d280508 100644
--- a/src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs
+++ b/src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs
@@ -82,7 +82,7 @@ namespace Ryujinx.Cpu.Signal
return old;
}
- public static SigAction RegisterExceptionHandler(IntPtr action)
+ public static SigAction RegisterExceptionHandler(IntPtr action, int userSignal = -1)
{
int result;
SigAction old;
@@ -150,6 +150,21 @@ namespace Ryujinx.Cpu.Signal
}
}
+ if (userSignal != -1)
+ {
+ result = sigaction(userSignal, ref sig, out SigAction oldu);
+
+ if (oldu.sa_handler != IntPtr.Zero)
+ {
+ throw new InvalidOperationException($"SIG{userSignal} is already in use.");
+ }
+
+ if (result != 0)
+ {
+ throw new InvalidOperationException($"Could not register SIG{userSignal} sigaction. Error: {result}");
+ }
+ }
+
return old;
}
diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContext.cs b/src/Ryujinx.HLE/HOS/ArmProcessContext.cs
index fde489ab7..1735fcaa4 100644
--- a/src/Ryujinx.HLE/HOS/ArmProcessContext.cs
+++ b/src/Ryujinx.HLE/HOS/ArmProcessContext.cs
@@ -16,13 +16,15 @@ namespace Ryujinx.HLE.HOS
ulong codeSize);
}
- class ArmProcessContext : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, IMemoryManager
+ class ArmProcessContext : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, ICpuMemoryManager
{
private readonly ulong _pid;
private readonly GpuContext _gpuContext;
private readonly ICpuContext _cpuContext;
private T _memoryManager;
+ public ulong ReservedSize { get; }
+
public IVirtualMemoryManager AddressSpace => _memoryManager;
public ulong AddressSpaceSize { get; }
@@ -33,7 +35,8 @@ namespace Ryujinx.HLE.HOS
GpuContext gpuContext,
T memoryManager,
ulong addressSpaceSize,
- bool for64Bit)
+ bool for64Bit,
+ ulong reservedSize = 0UL)
{
if (memoryManager is IRefCounted rc)
{
@@ -46,8 +49,8 @@ namespace Ryujinx.HLE.HOS
_gpuContext = gpuContext;
_cpuContext = cpuEngine.CreateCpuContext(memoryManager, for64Bit);
_memoryManager = memoryManager;
-
AddressSpaceSize = addressSpaceSize;
+ ReservedSize = reservedSize;
}
public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks)
@@ -78,6 +81,11 @@ namespace Ryujinx.HLE.HOS
_cpuContext.InvalidateCacheRegion(address, size);
}
+ public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize)
+ {
+ _cpuContext.PatchCodeForNce(textAddress, textSize, patchRegionAddress, patchRegionSize);
+ }
+
public void Dispose()
{
if (_memoryManager is IRefCounted rc)
diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs
index 06b8fd345..fc137f0b5 100644
--- a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs
+++ b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs
@@ -4,6 +4,7 @@ using Ryujinx.Cpu;
using Ryujinx.Cpu.AppleHv;
using Ryujinx.Cpu.Jit;
using Ryujinx.Cpu.LightningJit;
+using Ryujinx.Cpu.Nce;
using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Process;
@@ -46,14 +47,31 @@ namespace Ryujinx.HLE.HOS
public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit)
{
IArmProcessContext processContext;
+ AddressSpace addressSpace = null;
bool isArm64Host = RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
if (OperatingSystem.IsMacOS() && isArm64Host && for64Bit && context.Device.Configuration.UseHypervisor)
{
- var cpuEngine = new HvEngine(_tickSource);
- var memoryManager = new HvMemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler);
- processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManager, addressSpaceSize, for64Bit);
+ if (OperatingSystem.IsMacOS())
+ {
+ var cpuEngine = new HvEngine(_tickSource);
+ var memoryManager = new HvMemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler);
+ processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManager, addressSpaceSize, for64Bit);
+ }
+ else
+ {
+ if (!AddressSpace.TryCreate(context.Memory, addressSpaceSize, MemoryBlock.GetPageSize() == MemoryManagerHostMapped.PageSize, out addressSpace))
+ {
+ throw new Exception("Address space creation failed");
+ }
+
+ Logger.Info?.Print(LogClass.Cpu, $"NCE Base AS Address: 0x{addressSpace.Base.Pointer.ToInt64():X} Size: 0x{addressSpace.AddressSpaceSize:X}");
+
+ var cpuEngine = new NceEngine(_tickSource);
+ var memoryManager = new MemoryManagerNative(addressSpace, context.Memory, addressSpaceSize, invalidAccessHandler);
+ processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManager, addressSpace.AddressSpaceSize, for64Bit, memoryManager.ReservedSize);
+ }
}
else
{
@@ -70,8 +88,6 @@ namespace Ryujinx.HLE.HOS
? new LightningJitEngine(_tickSource)
: new JitEngine(_tickSource);
- AddressSpace addressSpace = null;
-
if (mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe)
{
if (!AddressSpace.TryCreate(context.Memory, addressSpaceSize, MemoryBlock.GetPageSize() == MemoryManagerHostMapped.PageSize, out addressSpace))
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs
index 3a9f64bef..fd226987d 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs
@@ -107,6 +107,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
MemoryRegion memRegion,
ulong address,
ulong size,
+ ulong reservedSize,
KMemoryBlockSlabManager slabManager)
{
if ((uint)addrSpaceType > (uint)AddressSpaceType.Addr39Bits)
@@ -128,6 +129,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
memRegion,
address,
size,
+ reservedSize,
slabManager);
if (result != Result.Success)
@@ -155,6 +157,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
MemoryRegion memRegion,
ulong address,
ulong size,
+ ulong reservedSize,
KMemoryBlockSlabManager slabManager)
{
ulong endAddr = address + size;
@@ -178,7 +181,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
CodeRegionStart = 0x200000;
codeRegionSize = 0x3fe00000;
AslrRegionStart = 0x200000;
- AslrRegionEnd = AslrRegionStart + 0xffe00000;
+ AslrRegionEnd = 0x100000000;
stackAndTlsIoStart = 0x200000;
stackAndTlsIoEnd = 0x40000000;
break;
@@ -191,7 +194,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
CodeRegionStart = 0x8000000;
codeRegionSize = 0x78000000;
AslrRegionStart = 0x8000000;
- AslrRegionEnd = AslrRegionStart + 0xff8000000;
+ AslrRegionEnd = 0x1000000000;
stackAndTlsIoStart = 0x8000000;
stackAndTlsIoEnd = 0x80000000;
break;
@@ -204,7 +207,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
CodeRegionStart = 0x200000;
codeRegionSize = 0x3fe00000;
AslrRegionStart = 0x200000;
- AslrRegionEnd = AslrRegionStart + 0xffe00000;
+ AslrRegionEnd = 0x100000000;
stackAndTlsIoStart = 0x200000;
stackAndTlsIoEnd = 0x40000000;
break;
@@ -222,8 +225,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
codeRegionSize = BitUtils.AlignUp(endAddr, RegionAlignment) - CodeRegionStart;
stackAndTlsIoStart = 0;
stackAndTlsIoEnd = 0;
- AslrRegionStart = 0x8000000;
- addrSpaceEnd = 1UL << addressSpaceWidth;
+ AslrRegionStart = reservedSize + 0x8000000;
+ addrSpaceEnd = reservedSize + (1UL << addressSpaceWidth);
AslrRegionEnd = addrSpaceEnd;
}
else
@@ -234,8 +237,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
tlsIoRegion.Size = 0x1000000000;
CodeRegionStart = BitUtils.AlignDown(address, RegionAlignment);
codeRegionSize = BitUtils.AlignUp(endAddr, RegionAlignment) - CodeRegionStart;
- AslrRegionStart = 0x8000000;
- AslrRegionEnd = AslrRegionStart + 0x7ff8000000;
+ AslrRegionStart = reservedSize + 0x8000000;
+ AslrRegionEnd = 0x8000000000;
stackAndTlsIoStart = 0;
stackAndTlsIoEnd = 0;
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs
index ac36b781b..9918c5d73 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs
@@ -7,11 +7,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
interface IProcessContext : IDisposable
{
IVirtualMemoryManager AddressSpace { get; }
+ ulong ReservedSize { get; }
ulong AddressSpaceSize { get; }
IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks);
void Execute(IExecutionContext context, ulong codeAddress);
void InvalidateCacheRegion(ulong address, ulong size);
+ void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize);
}
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
index 6008548be..be3a6047d 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
@@ -139,7 +139,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
bool aslrEnabled = creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr);
- ulong codeAddress = creationInfo.CodeAddress;
+ ulong codeAddress = creationInfo.CodeAddress + Context.ReservedSize;
ulong codeSize = (ulong)creationInfo.CodePagesCount * KPageTableBase.PageSize;
@@ -154,6 +154,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
memRegion,
codeAddress,
codeSize,
+ Context.ReservedSize,
slabManager);
if (result != Result.Success)
@@ -189,7 +190,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
KResourceLimit resourceLimit,
MemoryRegion memRegion,
IProcessContextFactory contextFactory,
- ThreadStart customThreadStart = null)
+ ThreadStart customThreadStart = null,
+ ulong entrypointOffset = 0UL)
{
ResourceLimit = resourceLimit;
_memRegion = memRegion;
@@ -247,7 +249,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
bool aslrEnabled = creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr);
- ulong codeAddress = creationInfo.CodeAddress;
+ ulong codeAddress = creationInfo.CodeAddress + Context.ReservedSize;
ulong codeSize = codePagesCount * KPageTableBase.PageSize;
@@ -258,6 +260,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
memRegion,
codeAddress,
codeSize,
+ Context.ReservedSize,
slabManager);
if (result != Result.Success)
@@ -303,6 +306,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
CleanUpForError();
}
+ _entrypoint += entrypointOffset;
+
return result;
}
@@ -347,7 +352,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
Flags = creationInfo.Flags;
TitleId = creationInfo.TitleId;
- _entrypoint = creationInfo.CodeAddress;
+ _entrypoint = creationInfo.CodeAddress + Context.ReservedSize;
_imageSize = (ulong)creationInfo.CodePagesCount * KPageTableBase.PageSize;
switch (Flags & ProcessCreationFlags.AddressSpaceMask)
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs
index b4ae6ec4e..d91cef4fa 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs
@@ -7,6 +7,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
class ProcessContext : IProcessContext
{
public IVirtualMemoryManager AddressSpace { get; }
+ public ulong ReservedSize => 0UL;
public ulong AddressSpaceSize { get; }
@@ -30,6 +31,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
}
+ public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize)
+ {
+ }
+
public void Dispose()
{
}
diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs
index a6a1d87e0..7440fb9a4 100644
--- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs
+++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs
@@ -133,6 +133,8 @@ namespace Ryujinx.HLE.Loaders.Processes
return resultCode;
}
+ private const int ReservedPatchSize = 0x100000;
+
public static bool LoadKip(KernelContext context, KipExecutable kip)
{
uint endOffset = kip.DataOffset + (uint)kip.Data.Length;
@@ -197,7 +199,9 @@ namespace Ryujinx.HLE.Loaders.Processes
return false;
}
- result = LoadIntoMemory(process, kip, codeBaseAddress);
+ // TODO: Support NCE of KIPs too.
+ result = LoadIntoMemory(process, kip, codeBaseAddress, 0UL);
+
if (result != Result.Success)
{
Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
@@ -257,6 +261,7 @@ namespace Ryujinx.HLE.Loaders.Processes
_ => "",
}).ToUpper());
+ ulong[] nsoPatch = new ulong[executables.Length];
ulong[] nsoBase = new ulong[executables.Length];
for (int index = 0; index < executables.Length; index++)
@@ -281,6 +286,10 @@ namespace Ryujinx.HLE.Loaders.Processes
nsoSize = BitUtils.AlignUp(nsoSize, KPageTableBase.PageSize);
+ nsoPatch[index] = codeStart + codeSize;
+
+ codeSize += ReservedPatchSize;
+
nsoBase[index] = codeStart + codeSize;
codeSize += nsoSize;
@@ -304,7 +313,7 @@ namespace Ryujinx.HLE.Loaders.Processes
programId,
codeStart,
codePagesCount,
- (ProcessCreationFlags)meta.Flags | ProcessCreationFlags.IsApplication,
+ (ProcessCreationFlags)meta.Flags,
0,
personalMmHeapPagesCount);
@@ -381,7 +390,8 @@ namespace Ryujinx.HLE.Loaders.Processes
MemoryMarshal.Cast(npdm.KernelCapabilityData),
resourceLimit,
memoryRegion,
- processContextFactory);
+ processContextFactory,
+ entrypointOffset: ReservedPatchSize);
if (result != Result.Success)
{
@@ -392,9 +402,13 @@ namespace Ryujinx.HLE.Loaders.Processes
for (int index = 0; index < executables.Length; index++)
{
- Logger.Info?.Print(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}...");
+ ulong nsoPatchAddress = process.Context.ReservedSize + nsoPatch[index];
+ ulong nsoBaseAddress = process.Context.ReservedSize + nsoBase[index];
+
+ Logger.Info?.Print(LogClass.Loader, $"Loading image {index} at 0x{nsoBaseAddress:x16}...");
+
+ result = LoadIntoMemory(process, executables[index], nsoBaseAddress, nsoPatchAddress);
- result = LoadIntoMemory(process, executables[index], nsoBase[index]);
if (result != Result.Success)
{
Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
@@ -433,7 +447,7 @@ namespace Ryujinx.HLE.Loaders.Processes
device.System.State.DesiredTitleLanguage);
}
- public static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress)
+ private static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress, ulong patchAddress)
{
ulong textStart = baseAddress + image.TextOffset;
ulong roStart = baseAddress + image.RoOffset;
@@ -453,6 +467,8 @@ namespace Ryujinx.HLE.Loaders.Processes
process.CpuMemory.Fill(bssStart, image.BssSize, 0);
+ process.Context.PatchCodeForNce(textStart, (ulong)image.Text.Length, patchAddress, ReservedPatchSize);
+
Result SetProcessMemoryPermission(ulong address, ulong size, KMemoryPermission permission)
{
if (size == 0)
diff --git a/src/Ryujinx.Memory/AddressSpaceManager.cs b/src/Ryujinx.Memory/AddressSpaceManager.cs
index 021d33663..a6620b4f7 100644
--- a/src/Ryujinx.Memory/AddressSpaceManager.cs
+++ b/src/Ryujinx.Memory/AddressSpaceManager.cs
@@ -96,6 +96,11 @@ namespace Ryujinx.Memory
}
}
+ ///
+ public void Reprotect(ulong va, ulong size, MemoryPermission permission)
+ {
+ }
+
///
public T Read(ulong va) where T : unmanaged
{
diff --git a/src/Ryujinx.Memory/IVirtualMemoryManager.cs b/src/Ryujinx.Memory/IVirtualMemoryManager.cs
index 9cf3663cf..edfeb79ad 100644
--- a/src/Ryujinx.Memory/IVirtualMemoryManager.cs
+++ b/src/Ryujinx.Memory/IVirtualMemoryManager.cs
@@ -44,6 +44,14 @@ namespace Ryujinx.Memory
/// Size of the range to be unmapped
void Unmap(ulong va, ulong size);
+ ///
+ /// Reprotects a previously mapped range of virtual memory.
+ ///
+ /// Virtual address of the range to be reprotected
+ /// Size of the range to be reprotected
+ /// New protection of the memory range
+ void Reprotect(ulong va, ulong size, MemoryPermission permission);
+
///
/// Reads data from CPU mapped memory.
///
diff --git a/src/Ryujinx.Memory/MemoryManagementUnix.cs b/src/Ryujinx.Memory/MemoryManagementUnix.cs
index 516df5a4b..ac008e697 100644
--- a/src/Ryujinx.Memory/MemoryManagementUnix.cs
+++ b/src/Ryujinx.Memory/MemoryManagementUnix.cs
@@ -16,15 +16,15 @@ namespace Ryujinx.Memory
public static IntPtr Allocate(ulong size, bool forJit)
{
- return AllocateInternal(size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, forJit);
+ return AllocateInternal(size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, forJit, false);
}
public static IntPtr Reserve(ulong size, bool forJit)
{
- return AllocateInternal(size, MmapProts.PROT_NONE, forJit);
+ return AllocateInternal(size, MmapProts.PROT_NONE, forJit, false);
}
- private static IntPtr AllocateInternal(ulong size, MmapProts prot, bool forJit, bool shared = false)
+ private static IntPtr AllocateInternal(ulong size, MmapProts prot, bool forJit, bool shared)
{
MmapFlags flags = MmapFlags.MAP_ANONYMOUS;