Magic the magic happen

This commit is contained in:
Raymond Lynch 2021-07-11 16:32:29 -04:00
parent 84769ae5b7
commit 658eedca37
188 changed files with 10329 additions and 3549 deletions

View file

@ -0,0 +1,47 @@
using System;
using System.Runtime.Serialization;
namespace Dalamud.Memory.Exceptions
{
/// <summary>
/// An exception thrown when VirtualAlloc fails.
/// </summary>
public class MemoryAllocationException : MemoryException
{
/// <summary>
/// Initializes a new instance of the <see cref="MemoryAllocationException"/> class.
/// </summary>
public MemoryAllocationException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryAllocationException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public MemoryAllocationException(string message)
: base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryAllocationException"/> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception.</param>
public MemoryAllocationException(string message, Exception innerException)
: base(message, innerException)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryAllocationException"/> class.
/// </summary>
/// <param name="info">The object that holds the serialized data about the exception being thrown.</param>
/// <param name="context">The object that contains contextual information about the source or destination.</param>
protected MemoryAllocationException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}

View file

@ -0,0 +1,47 @@
using System;
using System.Runtime.Serialization;
namespace Dalamud.Memory.Exceptions
{
/// <summary>
/// The base exception when thrown from Dalamud.Memory.
/// </summary>
public abstract class MemoryException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="MemoryException"/> class.
/// </summary>
public MemoryException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public MemoryException(string message)
: base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryException"/> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception.</param>
public MemoryException(string message, Exception innerException)
: base(message, innerException)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryException"/> class.
/// </summary>
/// <param name="info">The object that holds the serialized data about the exception being thrown.</param>
/// <param name="context">The object that contains contextual information about the source or destination.</param>
protected MemoryException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}

View file

@ -0,0 +1,47 @@
using System;
using System.Runtime.Serialization;
namespace Dalamud.Memory.Exceptions
{
/// <summary>
/// An exception thrown when VirtualProtect fails.
/// </summary>
public class MemoryPermissionException : MemoryException
{
/// <summary>
/// Initializes a new instance of the <see cref="MemoryPermissionException"/> class.
/// </summary>
public MemoryPermissionException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryPermissionException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public MemoryPermissionException(string message)
: base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryPermissionException"/> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception.</param>
public MemoryPermissionException(string message, Exception innerException)
: base(message, innerException)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryPermissionException"/> class.
/// </summary>
/// <param name="info">The object that holds the serialized data about the exception being thrown.</param>
/// <param name="context">The object that contains contextual information about the source or destination.</param>
protected MemoryPermissionException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}

View file

@ -0,0 +1,47 @@
using System;
using System.Runtime.Serialization;
namespace Dalamud.Memory.Exceptions
{
/// <summary>
/// An exception thrown when ReadProcessMemory fails.
/// </summary>
public class MemoryReadException : MemoryException
{
/// <summary>
/// Initializes a new instance of the <see cref="MemoryReadException"/> class.
/// </summary>
public MemoryReadException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryReadException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public MemoryReadException(string message)
: base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryReadException"/> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception.</param>
public MemoryReadException(string message, Exception innerException)
: base(message, innerException)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryReadException"/> class.
/// </summary>
/// <param name="info">The object that holds the serialized data about the exception being thrown.</param>
/// <param name="context">The object that contains contextual information about the source or destination.</param>
protected MemoryReadException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}

View file

@ -0,0 +1,47 @@
using System;
using System.Runtime.Serialization;
namespace Dalamud.Memory.Exceptions
{
/// <summary>
/// An exception thrown when WriteProcessMemory fails.
/// </summary>
public class MemoryWriteException : MemoryException
{
/// <summary>
/// Initializes a new instance of the <see cref="MemoryWriteException"/> class.
/// </summary>
public MemoryWriteException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryWriteException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public MemoryWriteException(string message)
: base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryWriteException"/> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception.</param>
public MemoryWriteException(string message, Exception innerException)
: base(message, innerException)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryWriteException"/> class.
/// </summary>
/// <param name="info">The object that holds the serialized data about the exception being thrown.</param>
/// <param name="context">The object that contains contextual information about the source or destination.</param>
protected MemoryWriteException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}

View file

@ -0,0 +1,610 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory.Exceptions;
using static Dalamud.NativeFunctions;
// Heavily inspired from Reloaded (https://github.com/Reloaded-Project/Reloaded.Memory)
namespace Dalamud.Memory
{
/// <summary>
/// A simple class that provides read/write access to arbitrary memory.
/// </summary>
public static unsafe class MemoryHelper
{
private static SeStringManager seStringManager;
private static IntPtr handle;
#region Read
/// <summary>
/// Reads a generic type from a specified memory address.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <returns>The read in struct.</returns>
public static T Read<T>(IntPtr memoryAddress) where T : unmanaged
=> Read<T>(memoryAddress, false);
/// <summary>
/// Reads a generic type from a specified memory address.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="marshal">Set this to true to enable struct marshalling.</param>
/// <returns>The read in struct.</returns>
public static T Read<T>(IntPtr memoryAddress, bool marshal)
{
return marshal
? Marshal.PtrToStructure<T>(memoryAddress)
: Unsafe.Read<T>((void*)memoryAddress);
}
/// <summary>
/// Reads a generic type from a specified memory address.
/// </summary>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="length">The amount of bytes to read starting from the memoryAddress.</param>
/// <returns>The read in struct.</returns>
public static byte[] ReadRaw(IntPtr memoryAddress, int length)
{
var value = new byte[length];
Marshal.Copy(memoryAddress, value, 0, value.Length);
return value;
}
/// <summary>
/// Reads a generic type array from a specified memory address.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="arrayLength">The amount of array items to read.</param>
/// <returns>The read in struct array.</returns>
public static T[] Read<T>(IntPtr memoryAddress, int arrayLength) where T : unmanaged
=> Read<T>(memoryAddress, arrayLength, false);
/// <summary>
/// Reads a generic type array from a specified memory address.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="arrayLength">The amount of array items to read.</param>
/// <param name="marshal">Set this to true to enable struct marshalling.</param>
/// <returns>The read in struct array.</returns>
public static T[] Read<T>(IntPtr memoryAddress, int arrayLength, bool marshal)
{
var structSize = SizeOf<T>();
var value = new T[arrayLength];
for (var i = 0; i < arrayLength; i++)
{
var address = memoryAddress + (structSize * i);
Read(address, out T result, marshal);
value[i] = result;
}
return value;
}
#endregion
#region Read(out)
/// <summary>
/// Reads a generic type from a specified memory address.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="value">Local variable to receive the read in struct.</param>
public static void Read<T>(IntPtr memoryAddress, out T value) where T : unmanaged
=> value = Read<T>(memoryAddress);
/// <summary>
/// Reads a generic type from a specified memory address.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="value">Local variable to receive the read in struct.</param>
/// <param name="marshal">Set this to true to enable struct marshalling.</param>
public static void Read<T>(IntPtr memoryAddress, out T value, bool marshal)
=> value = Read<T>(memoryAddress, marshal);
/// <summary>
/// Reads raw data from a specified memory address.
/// </summary>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="length">The amount of bytes to read starting from the memoryAddress.</param>
/// <param name="value">Local variable to receive the read in bytes.</param>
public static void ReadRaw(IntPtr memoryAddress, int length, out byte[] value)
=> value = ReadRaw(memoryAddress, length);
/// <summary>
/// Reads a generic type array from a specified memory address.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="arrayLength">The amount of array items to read.</param>
/// <param name="value">The read in struct array.</param>
public static void Read<T>(IntPtr memoryAddress, int arrayLength, out T[] value) where T : unmanaged
=> value = Read<T>(memoryAddress, arrayLength);
/// <summary>
/// Reads a generic type array from a specified memory address.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="arrayLength">The amount of array items to read.</param>
/// <param name="marshal">Set this to true to enable struct marshalling.</param>
/// <param name="value">The read in struct array.</param>
public static void Read<T>(IntPtr memoryAddress, int arrayLength, bool marshal, out T[] value)
=> value = Read<T>(memoryAddress, arrayLength, marshal);
#endregion
#region ReadString
/// <summary>
/// Read a UTF-8 encoded string from a specified memory address.
/// </summary>
/// <remarks>
/// Attention! If this is an SeString, use the <see cref="SeStringManager"/> to decode or the applicable helper method.
/// </remarks>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <returns>The read in string.</returns>
public static string ReadString(IntPtr memoryAddress)
=> ReadString(memoryAddress, 256);
/// <summary>
/// Read a UTF-8 encoded string from a specified memory address.
/// </summary>
/// <remarks>
/// Attention! If this is an SeString, use the <see cref="SeStringManager"/> to decode or the applicable helper method.
/// </remarks>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="maxLength">The maximum length of the string.</param>
/// <returns>The read in string.</returns>
public static string ReadString(IntPtr memoryAddress, int maxLength)
=> ReadString(memoryAddress, Encoding.UTF8, maxLength);
/// <summary>
/// Read a string with the given encoding from a specified memory address.
/// </summary>
/// <remarks>
/// Attention! If this is an SeString, use the <see cref="SeStringManager"/> to decode or the applicable helper method.
/// </remarks>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="encoding">The encoding to use to decode the string.</param>
/// <returns>The read in string.</returns>
public static string ReadString(IntPtr memoryAddress, Encoding encoding)
=> ReadString(memoryAddress, encoding, 256);
/// <summary>
/// Read a string with the given encoding from a specified memory address.
/// </summary>
/// <remarks>
/// Attention! If this is an SeString, use the <see cref="SeStringManager"/> to decode or the applicable helper method.
/// </remarks>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="encoding">The encoding to use to decode the string.</param>
/// <param name="maxLength">The maximum length of the string.</param>
/// <returns>The read in string.</returns>
public static string ReadString(IntPtr memoryAddress, Encoding encoding, int maxLength)
{
if (maxLength <= 0)
return string.Empty;
ReadRaw(memoryAddress, maxLength, out var buffer);
var data = encoding.GetString(buffer);
var eosPos = data.IndexOf('\0');
return eosPos >= 0 ? data.Substring(0, eosPos) : data;
}
/// <summary>
/// Read an SeString from a specified memory address.
/// </summary>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <returns>The read in string.</returns>
public static SeString ReadSeString(IntPtr memoryAddress)
=> ReadSeString(memoryAddress, 256);
/// <summary>
/// Read an SeString from a specified memory address.
/// </summary>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="maxLength">The maximum length of the string.</param>
/// <returns>The read in string.</returns>
public static SeString ReadSeString(IntPtr memoryAddress, int maxLength)
{
ReadRaw(memoryAddress, maxLength, out var buffer);
return seStringManager.Parse(buffer);
}
#endregion
#region ReadString(out)
/// <summary>
/// Read a UTF-8 encoded string from a specified memory address.
/// </summary>
/// <remarks>
/// Attention! If this is an SeString, use the <see cref="SeStringManager"/> to decode or the applicable helper method.
/// </remarks>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="value">The read in string.</param>
public static void ReadString(IntPtr memoryAddress, out string value)
=> value = ReadString(memoryAddress);
/// <summary>
/// Read a UTF-8 encoded string from a specified memory address.
/// </summary>
/// <remarks>
/// Attention! If this is an SeString, use the <see cref="SeStringManager"/> to decode or the applicable helper method.
/// </remarks>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="value">The read in string.</param>
/// <param name="maxLength">The maximum length of the string.</param>
public static void ReadString(IntPtr memoryAddress, out string value, int maxLength)
=> value = ReadString(memoryAddress, maxLength);
/// <summary>
/// Read a string with the given encoding from a specified memory address.
/// </summary>
/// <remarks>
/// Attention! If this is an SeString, use the <see cref="SeStringManager"/> to decode or the applicable helper method.
/// </remarks>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="encoding">The encoding to use to decode the string.</param>
/// <param name="value">The read in string.</param>
public static void ReadString(IntPtr memoryAddress, Encoding encoding, out string value)
=> value = ReadString(memoryAddress, encoding);
/// <summary>
/// Read a string with the given encoding from a specified memory address.
/// </summary>
/// <remarks>
/// Attention! If this is an SeString, use the <see cref="SeStringManager"/> to decode or the applicable helper method.
/// </remarks>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="encoding">The encoding to use to decode the string.</param>
/// <param name="maxLength">The maximum length of the string.</param>
/// <param name="value">The read in string.</param>
public static void ReadString(IntPtr memoryAddress, Encoding encoding, int maxLength, out string value)
=> value = ReadString(memoryAddress, encoding, maxLength);
/// <summary>
/// Read an SeString from a specified memory address.
/// </summary>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="value">The read in SeString.</param>
public static void ReadSeString(IntPtr memoryAddress, out SeString value)
=> value = ReadSeString(memoryAddress);
/// <summary>
/// Read an SeString from a specified memory address.
/// </summary>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="maxLength">The maximum length of the string.</param>
/// <param name="value">The read in SeString.</param>
public static void ReadSeString(IntPtr memoryAddress, int maxLength, out SeString value)
=> value = ReadSeString(memoryAddress, maxLength);
#endregion
#region Write
/// <summary>
/// Writes a generic type to a specified memory address.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="item">The item to write to the address.</param>
public static void Write<T>(IntPtr memoryAddress, T item) where T : unmanaged
=> Write(memoryAddress, item);
/// <summary>
/// Writes a generic type to a specified memory address.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="item">The item to write to the address.</param>
/// <param name="marshal">Set this to true to enable struct marshalling.</param>
public static void Write<T>(IntPtr memoryAddress, T item, bool marshal)
{
if (marshal)
Marshal.StructureToPtr(item, memoryAddress, false);
else
Unsafe.Write((void*)memoryAddress, item);
}
/// <summary>
/// Writes raw data to a specified memory address.
/// </summary>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="data">The bytes to write to memoryAddress.</param>
public static void WriteRaw(IntPtr memoryAddress, byte[] data)
{
Marshal.Copy(data, 0, memoryAddress, data.Length);
}
/// <summary>
/// Writes a generic type array to a specified memory address.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="memoryAddress">The memory address to write to.</param>
/// <param name="items">The array of items to write to the address.</param>
public static void Write<T>(IntPtr memoryAddress, T[] items) where T : unmanaged
=> Write(memoryAddress, items, false);
/// <summary>
/// Writes a generic type array to a specified memory address.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="memoryAddress">The memory address to write to.</param>
/// <param name="items">The array of items to write to the address.</param>
/// <param name="marshal">Set this to true to enable struct marshalling.</param>
public static void Write<T>(IntPtr memoryAddress, T[] items, bool marshal)
{
var structSize = SizeOf<T>(marshal);
for (var i = 0; i < items.Length; i++)
{
var address = memoryAddress + (structSize * i);
Write(address, items[i], marshal);
}
}
#endregion
#region WriteString
/// <summary>
/// Write a UTF-8 encoded string to a specified memory address.
/// </summary>
/// <remarks>
/// Attention! If this is an SeString, use the <see cref="SeStringManager"/> to encode or the applicable helper method.
/// </remarks>
/// <param name="memoryAddress">The memory address to write to.</param>
/// <param name="value">The string to write.</param>
public static void WriteString(IntPtr memoryAddress, string value)
=> WriteString(memoryAddress, value, Encoding.UTF8);
/// <summary>
/// Write a string with the given encoding to a specified memory address.
/// </summary>
/// <remarks>
/// Attention! If this is an SeString, use the <see cref="SeStringManager"/> to encode or the applicable helper method.
/// </remarks>
/// <param name="memoryAddress">The memory address to write to.</param>
/// <param name="value">The string to write.</param>
/// <param name="encoding">The encoding to use.</param>
public static void WriteString(IntPtr memoryAddress, string value, Encoding encoding)
{
if (string.IsNullOrEmpty(value))
return;
var bytes = encoding.GetBytes(value + '\0');
WriteRaw(memoryAddress, bytes);
}
/// <summary>
/// Write an SeString to a specified memory address.
/// </summary>
/// <param name="memoryAddress">The memory address to write to.</param>
/// <param name="value">The SeString to write.</param>
public static void WriteSeString(IntPtr memoryAddress, SeString value)
{
if (value is null)
return;
WriteRaw(memoryAddress, value.Encode());
}
#endregion
#region ApiWrappers
/// <summary>
/// Allocates fixed size of memory inside the target memory source via Windows API calls.
/// Returns the address of newly allocated memory.
/// </summary>
/// <param name="length">Amount of bytes to be allocated.</param>
/// <returns>Address to the newly allocated memory.</returns>
public static IntPtr Allocate(int length)
{
var address = VirtualAlloc(
IntPtr.Zero,
(UIntPtr)length,
AllocationType.Commit | AllocationType.Reserve,
MemoryProtection.ExecuteReadWrite);
if (address == IntPtr.Zero)
throw new MemoryAllocationException($"Unable to allocate {length} bytes.");
return address;
}
/// <summary>
/// Allocates fixed size of memory inside the target memory source via Windows API calls.
/// Returns the address of newly allocated memory.
/// </summary>
/// <param name="length">Amount of bytes to be allocated.</param>
/// <param name="memoryAddress">Address to the newly allocated memory.</param>
public static void Allocate(int length, out IntPtr memoryAddress)
=> memoryAddress = Allocate(length);
/// <summary>
/// Frees memory previously allocated with Allocate via Windows API calls.
/// </summary>
/// <param name="memoryAddress">The address of the memory to free.</param>
/// <returns>True if the operation is successful.</returns>
public static bool Free(IntPtr memoryAddress)
{
return VirtualFree(memoryAddress, UIntPtr.Zero, AllocationType.Release);
}
/// <summary>
/// Changes the page permissions for a specified combination of address and length via Windows API calls.
/// </summary>
/// <param name="memoryAddress">The memory address for which to change page permissions for.</param>
/// <param name="length">The region size for which to change permissions for.</param>
/// <param name="newPermissions">The new permissions to set.</param>
/// <returns>The old page permissions.</returns>
public static MemoryProtection ChangePermission(IntPtr memoryAddress, int length, MemoryProtection newPermissions)
{
var result = VirtualProtect(memoryAddress, (UIntPtr)length, newPermissions, out var oldPermissions);
if (!result)
throw new MemoryPermissionException($"Unable to change permissions at 0x{memoryAddress.ToInt64():X} of length {length} and permission {newPermissions} (result={result})");
var last = Marshal.GetLastWin32Error();
if (last > 0)
throw new MemoryPermissionException($"Unable to change permissions at 0x{memoryAddress.ToInt64():X} of length {length} and permission {newPermissions} (error={last})");
return oldPermissions;
}
/// <summary>
/// Changes the page permissions for a specified combination of address and length via Windows API calls.
/// </summary>
/// <param name="memoryAddress">The memory address for which to change page permissions for.</param>
/// <param name="length">The region size for which to change permissions for.</param>
/// <param name="newPermissions">The new permissions to set.</param>
/// <param name="oldPermissions">The old page permissions.</param>
public static void ChangePermission(IntPtr memoryAddress, int length, MemoryProtection newPermissions, out MemoryProtection oldPermissions)
=> oldPermissions = ChangePermission(memoryAddress, length, newPermissions);
/// <summary>
/// Changes the page permissions for a specified combination of address and element from which to deduce size via Windows API calls.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="memoryAddress">The memory address for which to change page permissions for.</param>
/// <param name="baseElement">The struct element from which the region size to change permissions for will be calculated.</param>
/// <param name="newPermissions">The new permissions to set.</param>
/// <param name="marshal">Set to true to calculate the size of the struct after marshalling instead of before.</param>
/// <returns>The old page permissions.</returns>
public static MemoryProtection ChangePermission<T>(IntPtr memoryAddress, ref T baseElement, MemoryProtection newPermissions, bool marshal)
=> ChangePermission(memoryAddress, SizeOf<T>(marshal), newPermissions);
/// <summary>
/// Reads raw data from a specified memory address via Windows API calls.
/// This is noticably slower than Unsafe or Marshal.
/// </summary>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="length">The amount of bytes to read starting from the memoryAddress.</param>
/// <returns>The read in bytes.</returns>
public static byte[] ReadProcessMemory(IntPtr memoryAddress, int length)
{
var value = new byte[length];
ReadProcessMemory(memoryAddress, ref value);
return value;
}
/// <summary>
/// Reads raw data from a specified memory address via Windows API calls.
/// This is noticably slower than Unsafe or Marshal.
/// </summary>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="length">The amount of bytes to read starting from the memoryAddress.</param>
/// <param name="value">The read in bytes.</param>
public static void ReadProcessMemory(IntPtr memoryAddress, int length, out byte[] value)
=> value = ReadProcessMemory(memoryAddress, length);
/// <summary>
/// Reads raw data from a specified memory address via Windows API calls.
/// This is noticably slower than Unsafe or Marshal.
/// </summary>
/// <param name="memoryAddress">The memory address to read from.</param>
/// <param name="value">The read in bytes.</param>
public static void ReadProcessMemory(IntPtr memoryAddress, ref byte[] value)
{
var length = value.Length;
var result = NativeFunctions.ReadProcessMemory(handle, memoryAddress, value, length, out _);
if (!result)
throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})");
var last = Marshal.GetLastWin32Error();
if (last > 0)
throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})");
}
/// <summary>
/// Writes raw data to a specified memory address via Windows API calls.
/// This is noticably slower than Unsafe or Marshal.
/// </summary>
/// <param name="memoryAddress">The memory address to write to.</param>
/// <param name="data">The bytes to write to memoryAddress.</param>
public static void WriteProcessMemory(IntPtr memoryAddress, byte[] data)
{
var length = data.Length;
var result = NativeFunctions.WriteProcessMemory(handle, memoryAddress, data, length, out _);
if (!result)
throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})");
var last = Marshal.GetLastWin32Error();
if (last > 0)
throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})");
}
#endregion
#region Sizing
/// <summary>
/// Returns the size of a specific primitive or struct type.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <returns>The size of the primitive or struct.</returns>
public static int SizeOf<T>()
=> SizeOf<T>(false);
/// <summary>
/// Returns the size of a specific primitive or struct type.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="marshal">If set to true; will return the size of an element after marshalling.</param>
/// <returns>The size of the primitive or struct.</returns>
public static int SizeOf<T>(bool marshal)
=> marshal ? Marshal.SizeOf<T>() : Unsafe.SizeOf<T>();
/// <summary>
/// Returns the size of a specific primitive or struct type.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="elementCount">The number of array elements present.</param>
/// <returns>The size of the primitive or struct array.</returns>
public static int SizeOf<T>(int elementCount) where T : unmanaged
=> SizeOf<T>() * elementCount;
/// <summary>
/// Returns the size of a specific primitive or struct type.
/// </summary>
/// <typeparam name="T">An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.</typeparam>
/// <param name="elementCount">The number of array elements present.</param>
/// <param name="marshal">If set to true; will return the size of an element after marshalling.</param>
/// <returns>The size of the primitive or struct array.</returns>
public static int SizeOf<T>(int elementCount, bool marshal)
=> SizeOf<T>(marshal) * elementCount;
#endregion
/// <summary>
/// Initialize with static access to Dalamud.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
internal static void Initialize(Dalamud dalamud)
{
seStringManager = dalamud.SeStringManager;
handle = Process.GetCurrentProcess().Handle;
}
}
}

View file

@ -0,0 +1,117 @@
using System;
// This is a copy from NativeFunctions.MemoryProtection
namespace Dalamud.Memory
{
/// <summary>
/// PAGE_* from memoryapi.
/// </summary>
[Flags]
public enum MemoryProtection
{
// Copied from NativeFunctions to expose to the user.
/// <summary>
/// Enables execute access to the committed region of pages. An attempt to write to the committed region results
/// in an access violation. This flag is not supported by the CreateFileMapping function.
/// </summary>
Execute = 0x10,
/// <summary>
/// Enables execute or read-only access to the committed region of pages. An attempt to write to the committed region
/// results in an access violation.
/// </summary>
ExecuteRead = 0x20,
/// <summary>
/// Enables execute, read-only, or read/write access to the committed region of pages.
/// </summary>
ExecuteReadWrite = 0x40,
/// <summary>
/// Enables execute, read-only, or copy-on-write access to a mapped view of a file mapping object. An attempt to
/// write to a committed copy-on-write page results in a private copy of the page being made for the process. The
/// private page is marked as PAGE_EXECUTE_READWRITE, and the change is written to the new page. This flag is not
/// supported by the VirtualAlloc or VirtualAllocEx functions.
/// </summary>
ExecuteWriteCopy = 0x80,
/// <summary>
/// Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed
/// region results in an access violation. This flag is not supported by the CreateFileMapping function.
/// </summary>
NoAccess = 0x01,
/// <summary>
/// Enables read-only access to the committed region of pages. An attempt to write to the committed region results
/// in an access violation. If Data Execution Prevention is enabled, an attempt to execute code in the committed
/// region results in an access violation.
/// </summary>
ReadOnly = 0x02,
/// <summary>
/// Enables read-only or read/write access to the committed region of pages. If Data Execution Prevention is enabled,
/// attempting to execute code in the committed region results in an access violation.
/// </summary>
ReadWrite = 0x04,
/// <summary>
/// Enables read-only or copy-on-write access to a mapped view of a file mapping object. An attempt to write to
/// a committed copy-on-write page results in a private copy of the page being made for the process. The private
/// page is marked as PAGE_READWRITE, and the change is written to the new page. If Data Execution Prevention is
/// enabled, attempting to execute code in the committed region results in an access violation. This flag is not
/// supported by the VirtualAlloc or VirtualAllocEx functions.
/// </summary>
WriteCopy = 0x08,
/// <summary>
/// Sets all locations in the pages as invalid targets for CFG. Used along with any execute page protection like
/// PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. Any indirect call to locations
/// in those pages will fail CFG checks and the process will be terminated. The default behavior for executable
/// pages allocated is to be marked valid call targets for CFG. This flag is not supported by the VirtualProtect
/// or CreateFileMapping functions.
/// </summary>
TargetsInvalid = 0x40000000,
/// <summary>
/// Pages in the region will not have their CFG information updated while the protection changes for VirtualProtect.
/// For example, if the pages in the region was allocated using PAGE_TARGETS_INVALID, then the invalid information
/// will be maintained while the page protection changes. This flag is only valid when the protection changes to
/// an executable type like PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY.
/// The default behavior for VirtualProtect protection change to executable is to mark all locations as valid call
/// targets for CFG.
/// </summary>
TargetsNoUpdate = TargetsInvalid,
/// <summary>
/// Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a
/// STATUS_GUARD_PAGE_VIOLATION exception and turn off the guard page status. Guard pages thus act as a one-time
/// access alarm. For more information, see Creating Guard Pages. When an access attempt leads the system to turn
/// off guard page status, the underlying page protection takes over. If a guard page exception occurs during a
/// system service, the service typically returns a failure status indicator. This value cannot be used with
/// PAGE_NOACCESS. This flag is not supported by the CreateFileMapping function.
/// </summary>
Guard = 0x100,
/// <summary>
/// Sets all pages to be non-cachable. Applications should not use this attribute except when explicitly required
/// for a device. Using the interlocked functions with memory that is mapped with SEC_NOCACHE can result in an
/// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_NOCACHE flag cannot be used with the PAGE_GUARD, PAGE_NOACCESS,
/// or PAGE_WRITECOMBINE flags. The PAGE_NOCACHE flag can be used only when allocating private memory with the
/// VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable non-cached memory access for shared
/// memory, specify the SEC_NOCACHE flag when calling the CreateFileMapping function.
/// </summary>
NoCache = 0x200,
/// <summary>
/// Sets all pages to be write-combined. Applications should not use this attribute except when explicitly required
/// for a device. Using the interlocked functions with memory that is mapped as write-combined can result in an
/// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_WRITECOMBINE flag cannot be specified with the PAGE_NOACCESS,
/// PAGE_GUARD, and PAGE_NOCACHE flags. The PAGE_WRITECOMBINE flag can be used only when allocating private memory
/// with the VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable write-combined memory access
/// for shared memory, specify the SEC_WRITECOMBINE flag when calling the CreateFileMapping function.
/// </summary>
WriteCombine = 0x400,
}
}