aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Memory
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Memory')
-rw-r--r--Ryujinx.Memory/MemoryAllocationFlags.cs28
-rw-r--r--Ryujinx.Memory/MemoryBlock.cs276
-rw-r--r--Ryujinx.Memory/MemoryConstants.cs9
-rw-r--r--Ryujinx.Memory/MemoryManagement.cs108
-rw-r--r--Ryujinx.Memory/MemoryManagementUnix.cs75
-rw-r--r--Ryujinx.Memory/MemoryManagementWindows.cs106
-rw-r--r--Ryujinx.Memory/MemoryPermission.cs46
-rw-r--r--Ryujinx.Memory/MemoryProtectionException.cs23
-rw-r--r--Ryujinx.Memory/Ryujinx.Memory.csproj23
9 files changed, 694 insertions, 0 deletions
diff --git a/Ryujinx.Memory/MemoryAllocationFlags.cs b/Ryujinx.Memory/MemoryAllocationFlags.cs
new file mode 100644
index 00000000..94025d38
--- /dev/null
+++ b/Ryujinx.Memory/MemoryAllocationFlags.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace Ryujinx.Memory
+{
+ /// <summary>
+ /// Flags that controls allocation and other properties of the memory block memory.
+ /// </summary>
+ [Flags]
+ public enum MemoryAllocationFlags
+ {
+ /// <summary>
+ /// No special allocation settings.
+ /// </summary>
+ None = 0,
+
+ /// <summary>
+ /// Reserve a region of memory on the process address space,
+ /// without actually allocation any backing memory.
+ /// </summary>
+ Reserve = 1 << 0,
+
+ /// <summary>
+ /// Enables read and write tracking of the memory block.
+ /// This currently does nothing and is reserved for future use.
+ /// </summary>
+ Tracked = 1 << 1
+ }
+}
diff --git a/Ryujinx.Memory/MemoryBlock.cs b/Ryujinx.Memory/MemoryBlock.cs
new file mode 100644
index 00000000..850fb115
--- /dev/null
+++ b/Ryujinx.Memory/MemoryBlock.cs
@@ -0,0 +1,276 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading;
+
+namespace Ryujinx.Memory
+{
+ /// <summary>
+ /// Represents a block of contiguous physical guest memory.
+ /// </summary>
+ public sealed class MemoryBlock : IDisposable
+ {
+ private IntPtr _pointer;
+
+ /// <summary>
+ /// Pointer to the memory block data.
+ /// </summary>
+ public IntPtr Pointer => _pointer;
+
+ /// <summary>
+ /// Size of the memory block.
+ /// </summary>
+ public ulong Size { get; }
+
+ /// <summary>
+ /// Initializes a new instance of the memory block class.
+ /// </summary>
+ /// <param name="size">Size of the memory block</param>
+ /// <param name="flags">Flags that control memory block memory allocation</param>
+ /// <exception cref="OutOfMemoryException">Throw when there's no enough memory to allocate the requested size</exception>
+ /// <exception cref="PlatformNotSupportedException">Throw when the current platform is not supported</exception>
+ public MemoryBlock(ulong size, MemoryAllocationFlags flags = MemoryAllocationFlags.None)
+ {
+ if (flags.HasFlag(MemoryAllocationFlags.Reserve))
+ {
+ _pointer = MemoryManagement.Reserve(size);
+ }
+ else
+ {
+ _pointer = MemoryManagement.Allocate(size);
+ }
+
+ Size = size;
+ }
+
+ /// <summary>
+ /// Commits a region of memory that has previously been reserved.
+ /// This can be used to allocate memory on demand.
+ /// </summary>
+ /// <param name="offset">Starting offset of the range to be committed</param>
+ /// <param name="size">Size of the range to be committed</param>
+ /// <returns>True if the operation was successful, false otherwise</returns>
+ /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception>
+ public bool Commit(ulong offset, ulong size)
+ {
+ return MemoryManagement.Commit(GetPointerInternal(offset, size), size);
+ }
+
+ /// <summary>
+ /// Reprotects a region of memory.
+ /// </summary>
+ /// <param name="offset">Starting offset of the range to be reprotected</param>
+ /// <param name="size">Size of the range to be reprotected</param>
+ /// <param name="permission">New memory permissions</param>
+ /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception>
+ /// <exception cref="MemoryProtectionException">Throw when <paramref name="permission"/> is invalid</exception>
+ public void Reprotect(ulong offset, ulong size, MemoryPermission permission)
+ {
+ MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission);
+ }
+
+ /// <summary>
+ /// Reads bytes from the memory block.
+ /// </summary>
+ /// <param name="offset">Starting offset of the range being read</param>
+ /// <param name="data">Span where the bytes being read will be copied to</param>
+ /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Throw when the memory region specified for the the data is out of range</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Read(ulong offset, Span<byte> data)
+ {
+ GetSpan(offset, data.Length).CopyTo(data);
+ }
+
+ /// <summary>
+ /// Reads data from the memory block.
+ /// </summary>
+ /// <typeparam name="T">Type of the data</typeparam>
+ /// <param name="offset">Offset where the data is located</param>
+ /// <returns>Data at the specified address</returns>
+ /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Throw when the memory region specified for the the data is out of range</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public T Read<T>(ulong offset) where T : unmanaged
+ {
+ return GetRef<T>(offset);
+ }
+
+ /// <summary>
+ /// Writes bytes to the memory block.
+ /// </summary>
+ /// <param name="offset">Starting offset of the range being written</param>
+ /// <param name="data">Span where the bytes being written will be copied from</param>
+ /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Throw when the memory region specified for the the data is out of range</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Write(ulong offset, ReadOnlySpan<byte> data)
+ {
+ data.CopyTo(GetSpan(offset, data.Length));
+ }
+
+ /// <summary>
+ /// Writes data to the memory block.
+ /// </summary>
+ /// <typeparam name="T">Type of the data being written</typeparam>
+ /// <param name="offset">Offset to write the data into</param>
+ /// <param name="data">Data to be written</param>
+ /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Throw when the memory region specified for the the data is out of range</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Write<T>(ulong offset, T data) where T : unmanaged
+ {
+ GetRef<T>(offset) = data;
+ }
+
+ /// <summary>
+ /// Copies data from one memory location to another.
+ /// </summary>
+ /// <param name="srcOffset">Source offset to read the data from</param>
+ /// <param name="dstOffset">Destination offset to write the data into</param>
+ /// <param name="size">Size of the copy in bytes</param>
+ /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Throw when <paramref name="srcOffset"/>, <paramref name="dstOffset"/> or <paramref name="size"/> is out of range</exception>
+ public void Copy(ulong srcOffset, ulong dstOffset, ulong size)
+ {
+ const int MaxChunkSize = 1 << 30;
+
+ for (ulong offset = 0; offset < size; offset += MaxChunkSize)
+ {
+ int copySize = (int)Math.Min(MaxChunkSize, size - offset);
+
+ Write(dstOffset + offset, GetSpan(srcOffset + offset, copySize));
+ }
+ }
+
+ /// <summary>
+ /// Fills a region of memory with zeros.
+ /// </summary>
+ /// <param name="offset">Offset of the region to fill with zeros</param>
+ /// <param name="size">Size in bytes of the region to fill</param>
+ /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception>
+ public void ZeroFill(ulong offset, ulong size)
+ {
+ const int MaxChunkSize = 1 << 30;
+
+ for (ulong subOffset = 0; subOffset < size; subOffset += MaxChunkSize)
+ {
+ int copySize = (int)Math.Min(MaxChunkSize, size - subOffset);
+
+ GetSpan(offset + subOffset, copySize).Fill(0);
+ }
+ }
+
+ /// <summary>
+ /// Gets a reference of the data at a given memory block region.
+ /// </summary>
+ /// <typeparam name="T">Data type</typeparam>
+ /// <param name="offset">Offset of the memory region</param>
+ /// <returns>A reference to the given memory region data</returns>
+ /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe ref T GetRef<T>(ulong offset) where T : unmanaged
+ {
+ IntPtr ptr = _pointer;
+
+ if (ptr == IntPtr.Zero)
+ {
+ ThrowObjectDisposed();
+ }
+
+ int size = Unsafe.SizeOf<T>();
+
+ ulong endOffset = offset + (ulong)size;
+
+ if (endOffset > Size || endOffset < offset)
+ {
+ ThrowArgumentOutOfRange();
+ }
+
+ return ref Unsafe.AsRef<T>((void*)PtrAddr(ptr, offset));
+ }
+
+ /// <summary>
+ /// Gets the pointer of a given memory block region.
+ /// </summary>
+ /// <param name="offset">Start offset of the memory region</param>
+ /// <param name="size">Size in bytes of the region</param>
+ /// <returns>The pointer to the memory region</returns>
+ /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public IntPtr GetPointer(ulong offset, int size) => GetPointerInternal(offset, (ulong)size);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private IntPtr GetPointerInternal(ulong offset, ulong size)
+ {
+ IntPtr ptr = _pointer;
+
+ if (ptr == IntPtr.Zero)
+ {
+ ThrowObjectDisposed();
+ }
+
+ ulong endOffset = offset + size;
+
+ if (endOffset > Size || endOffset < offset)
+ {
+ ThrowArgumentOutOfRange();
+ }
+
+ return PtrAddr(ptr, offset);
+ }
+
+ /// <summary>
+ /// Gets the span of a given memory block region.
+ /// </summary>
+ /// <param name="offset">Start offset of the memory region</param>
+ /// <param name="size">Size in bytes of the region</param>
+ /// <returns>Span of the memory region</returns>
+ /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe Span<byte> GetSpan(ulong offset, int size)
+ {
+ return new Span<byte>((void*)GetPointer(offset, size), size);
+ }
+
+ /// <summary>
+ /// Adds a 64-bits offset to a native pointer.
+ /// </summary>
+ /// <param name="pointer">Native pointer</param>
+ /// <param name="offset">Offset to add</param>
+ /// <returns>Native pointer with the added offset</returns>
+ private IntPtr PtrAddr(IntPtr pointer, ulong offset)
+ {
+ return (IntPtr)(pointer.ToInt64() + (long)offset);
+ }
+
+ /// <summary>
+ /// Frees the memory allocated for this memory block.
+ /// </summary>
+ /// <remarks>
+ /// It's an error to use the memory block after disposal.
+ /// </remarks>
+ public void Dispose() => FreeMemory();
+
+ ~MemoryBlock() => FreeMemory();
+
+ private void FreeMemory()
+ {
+ IntPtr ptr = Interlocked.Exchange(ref _pointer, IntPtr.Zero);
+
+ // If pointer is null, the memory was already freed or never allocated.
+ if (ptr != IntPtr.Zero)
+ {
+ MemoryManagement.Free(ptr);
+ }
+ }
+
+ private void ThrowObjectDisposed() => throw new ObjectDisposedException(nameof(MemoryBlock));
+ private void ThrowArgumentOutOfRange() => throw new ArgumentOutOfRangeException();
+ }
+}
diff --git a/Ryujinx.Memory/MemoryConstants.cs b/Ryujinx.Memory/MemoryConstants.cs
new file mode 100644
index 00000000..fc6f87e0
--- /dev/null
+++ b/Ryujinx.Memory/MemoryConstants.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Memory
+{
+ static class MemoryConstants
+ {
+ public const int PageBits = 12;
+ public const int PageSize = 1 << PageBits;
+ public const int PageMask = PageSize - 1;
+ }
+}
diff --git a/Ryujinx.Memory/MemoryManagement.cs b/Ryujinx.Memory/MemoryManagement.cs
new file mode 100644
index 00000000..2525bef7
--- /dev/null
+++ b/Ryujinx.Memory/MemoryManagement.cs
@@ -0,0 +1,108 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Memory
+{
+ public static class MemoryManagement
+ {
+ public static IntPtr Allocate(ulong size)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ IntPtr sizeNint = new IntPtr((long)size);
+
+ return MemoryManagementWindows.Allocate(sizeNint);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
+ RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ return MemoryManagementUnix.Allocate(size);
+ }
+ else
+ {
+ throw new PlatformNotSupportedException();
+ }
+ }
+
+ public static IntPtr Reserve(ulong size)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ IntPtr sizeNint = new IntPtr((long)size);
+
+ return MemoryManagementWindows.Reserve(sizeNint);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
+ RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ return MemoryManagementUnix.Reserve(size);
+ }
+ else
+ {
+ throw new PlatformNotSupportedException();
+ }
+ }
+
+ public static bool Commit(IntPtr address, ulong size)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ IntPtr sizeNint = new IntPtr((long)size);
+
+ return MemoryManagementWindows.Commit(address, sizeNint);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
+ RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ return MemoryManagementUnix.Commit(address, size);
+ }
+ else
+ {
+ throw new PlatformNotSupportedException();
+ }
+ }
+
+ public static void Reprotect(IntPtr address, ulong size, MemoryPermission permission)
+ {
+ bool result;
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ IntPtr sizeNint = new IntPtr((long)size);
+
+ result = MemoryManagementWindows.Reprotect(address, sizeNint, permission);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
+ RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ result = MemoryManagementUnix.Reprotect(address, size, permission);
+ }
+ else
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ if (!result)
+ {
+ throw new MemoryProtectionException(permission);
+ }
+ }
+
+ public static bool Free(IntPtr address)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return MemoryManagementWindows.Free(address);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
+ RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ return MemoryManagementUnix.Free(address);
+ }
+ else
+ {
+ throw new PlatformNotSupportedException();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Memory/MemoryManagementUnix.cs b/Ryujinx.Memory/MemoryManagementUnix.cs
new file mode 100644
index 00000000..81096867
--- /dev/null
+++ b/Ryujinx.Memory/MemoryManagementUnix.cs
@@ -0,0 +1,75 @@
+using Mono.Unix.Native;
+using System;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.Memory
+{
+ static class MemoryManagementUnix
+ {
+ private static readonly ConcurrentDictionary<IntPtr, ulong> _allocations = new ConcurrentDictionary<IntPtr, ulong>();
+
+ public static IntPtr Allocate(ulong size)
+ {
+ return AllocateInternal(size, MmapProts.PROT_READ | MmapProts.PROT_WRITE);
+ }
+
+ public static IntPtr Reserve(ulong size)
+ {
+ return AllocateInternal(size, MmapProts.PROT_NONE);
+ }
+
+ private static IntPtr AllocateInternal(ulong size, MmapProts prot)
+ {
+ const MmapFlags flags = MmapFlags.MAP_PRIVATE | MmapFlags.MAP_ANONYMOUS;
+
+ IntPtr ptr = Syscall.mmap(IntPtr.Zero, size, prot, flags, -1, 0);
+
+ if (ptr == new IntPtr(-1L))
+ {
+ throw new OutOfMemoryException();
+ }
+
+ if (!_allocations.TryAdd(ptr, size))
+ {
+ // This should be impossible, kernel shouldn't return an already mapped address.
+ throw new InvalidOperationException();
+ }
+
+ return ptr;
+ }
+
+ public static bool Commit(IntPtr address, ulong size)
+ {
+ return Syscall.mprotect(address, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE) == 0;
+ }
+
+ public static bool Reprotect(IntPtr address, ulong size, MemoryPermission permission)
+ {
+ return Syscall.mprotect(address, size, GetProtection(permission)) == 0;
+ }
+
+ private static MmapProts GetProtection(MemoryPermission permission)
+ {
+ return permission switch
+ {
+ MemoryPermission.None => MmapProts.PROT_NONE,
+ MemoryPermission.Read => MmapProts.PROT_READ,
+ MemoryPermission.ReadAndWrite => MmapProts.PROT_READ | MmapProts.PROT_WRITE,
+ MemoryPermission.ReadAndExecute => MmapProts.PROT_READ | MmapProts.PROT_EXEC,
+ MemoryPermission.ReadWriteExecute => MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC,
+ MemoryPermission.Execute => MmapProts.PROT_EXEC,
+ _ => throw new MemoryProtectionException(permission)
+ };
+ }
+
+ public static bool Free(IntPtr address)
+ {
+ if (_allocations.TryRemove(address, out ulong size))
+ {
+ return Syscall.munmap(address, size) == 0;
+ }
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Memory/MemoryManagementWindows.cs b/Ryujinx.Memory/MemoryManagementWindows.cs
new file mode 100644
index 00000000..9513bb54
--- /dev/null
+++ b/Ryujinx.Memory/MemoryManagementWindows.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Memory
+{
+ static class MemoryManagementWindows
+ {
+ [Flags]
+ private enum AllocationType : uint
+ {
+ Commit = 0x1000,
+ Reserve = 0x2000,
+ Decommit = 0x4000,
+ Release = 0x8000,
+ Reset = 0x80000,
+ Physical = 0x400000,
+ TopDown = 0x100000,
+ WriteWatch = 0x200000,
+ LargePages = 0x20000000
+ }
+
+ [Flags]
+ private enum MemoryProtection : uint
+ {
+ NoAccess = 0x01,
+ ReadOnly = 0x02,
+ ReadWrite = 0x04,
+ WriteCopy = 0x08,
+ Execute = 0x10,
+ ExecuteRead = 0x20,
+ ExecuteReadWrite = 0x40,
+ ExecuteWriteCopy = 0x80,
+ GuardModifierflag = 0x100,
+ NoCacheModifierflag = 0x200,
+ WriteCombineModifierflag = 0x400
+ }
+
+ [DllImport("kernel32.dll")]
+ private static extern IntPtr VirtualAlloc(
+ IntPtr lpAddress,
+ IntPtr dwSize,
+ AllocationType flAllocationType,
+ MemoryProtection flProtect);
+
+ [DllImport("kernel32.dll")]
+ private static extern bool VirtualProtect(
+ IntPtr lpAddress,
+ IntPtr dwSize,
+ MemoryProtection flNewProtect,
+ out MemoryProtection lpflOldProtect);
+
+ [DllImport("kernel32.dll")]
+ private static extern bool VirtualFree(IntPtr lpAddress, IntPtr dwSize, AllocationType dwFreeType);
+
+ public static IntPtr Allocate(IntPtr size)
+ {
+ return AllocateInternal(size, AllocationType.Reserve | AllocationType.Commit);
+ }
+
+ public static IntPtr Reserve(IntPtr size)
+ {
+ return AllocateInternal(size, AllocationType.Reserve);
+ }
+
+ private static IntPtr AllocateInternal(IntPtr size, AllocationType flags = 0)
+ {
+ IntPtr ptr = VirtualAlloc(IntPtr.Zero, size, flags, MemoryProtection.ReadWrite);
+
+ if (ptr == IntPtr.Zero)
+ {
+ throw new OutOfMemoryException();
+ }
+
+ return ptr;
+ }
+
+ public static bool Commit(IntPtr location, IntPtr size)
+ {
+ return VirtualAlloc(location, size, AllocationType.Commit, MemoryProtection.ReadWrite) != IntPtr.Zero;
+ }
+
+ public static bool Reprotect(IntPtr address, IntPtr size, MemoryPermission permission)
+ {
+ return VirtualProtect(address, size, GetProtection(permission), out _);
+ }
+
+ private static MemoryProtection GetProtection(MemoryPermission permission)
+ {
+ return permission switch
+ {
+ MemoryPermission.None => MemoryProtection.NoAccess,
+ MemoryPermission.Read => MemoryProtection.ReadOnly,
+ MemoryPermission.ReadAndWrite => MemoryProtection.ReadWrite,
+ MemoryPermission.ReadAndExecute => MemoryProtection.ExecuteRead,
+ MemoryPermission.ReadWriteExecute => MemoryProtection.ExecuteReadWrite,
+ MemoryPermission.Execute => MemoryProtection.Execute,
+ _ => throw new MemoryProtectionException(permission)
+ };
+ }
+
+ public static bool Free(IntPtr address)
+ {
+ return VirtualFree(address, IntPtr.Zero, AllocationType.Release);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Memory/MemoryPermission.cs b/Ryujinx.Memory/MemoryPermission.cs
new file mode 100644
index 00000000..38f2d909
--- /dev/null
+++ b/Ryujinx.Memory/MemoryPermission.cs
@@ -0,0 +1,46 @@
+using System;
+
+namespace Ryujinx.Memory
+{
+ /// <summary>
+ /// Memory access permission control.
+ /// </summary>
+ [Flags]
+ public enum MemoryPermission
+ {
+ /// <summary>
+ /// No access is allowed on the memory region.
+ /// </summary>
+ None = 0,
+
+ /// <summary>
+ /// Allow reads on the memory region.
+ /// </summary>
+ Read = 1 << 0,
+
+ /// <summary>
+ /// Allow writes on the memory region.
+ /// </summary>
+ Write = 1 << 1,
+
+ /// <summary>
+ /// Allow code execution on the memory region.
+ /// </summary>
+ Execute = 1 << 2,
+
+ /// <summary>
+ /// Allow reads and writes on the memory region.
+ /// </summary>
+ ReadAndWrite = Read | Write,
+
+ /// <summary>
+ /// Allow reads and code execution on the memory region.
+ /// </summary>
+ ReadAndExecute = Read | Execute,
+
+ /// <summary>
+ /// Allow reads, writes, and code execution on the memory region.
+ /// </summary>
+ ReadWriteExecute = Read | Write | Execute
+ }
+}
diff --git a/Ryujinx.Memory/MemoryProtectionException.cs b/Ryujinx.Memory/MemoryProtectionException.cs
new file mode 100644
index 00000000..27e950a1
--- /dev/null
+++ b/Ryujinx.Memory/MemoryProtectionException.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace Ryujinx.Memory
+{
+ class MemoryProtectionException : Exception
+ {
+ public MemoryProtectionException()
+ {
+ }
+
+ public MemoryProtectionException(MemoryPermission permission) : base($"Failed to set memory protection to \"{permission}\".")
+ {
+ }
+
+ public MemoryProtectionException(string message) : base(message)
+ {
+ }
+
+ public MemoryProtectionException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+ }
+}
diff --git a/Ryujinx.Memory/Ryujinx.Memory.csproj b/Ryujinx.Memory/Ryujinx.Memory.csproj
new file mode 100644
index 00000000..a5669b32
--- /dev/null
+++ b/Ryujinx.Memory/Ryujinx.Memory.csproj
@@ -0,0 +1,23 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
+ </ItemGroup>
+
+</Project>