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 FFXIVClientStructs.FFXIV.Client.System.String;
using static Dalamud.NativeFunctions;
// Heavily inspired from Reloaded (https://github.com/Reloaded-Project/Reloaded.Memory)
namespace Dalamud.Memory
{
///
/// A simple class that provides read/write access to arbitrary memory.
///
public static unsafe class MemoryHelper
{
private static SeStringManager seStringManager;
private static IntPtr handle;
#region Read
///
/// Reads a generic type from a specified memory address.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The memory address to read from.
/// The read in struct.
public static T Read(IntPtr memoryAddress) where T : unmanaged
=> Read(memoryAddress, false);
///
/// Reads a generic type from a specified memory address.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The memory address to read from.
/// Set this to true to enable struct marshalling.
/// The read in struct.
public static T Read(IntPtr memoryAddress, bool marshal)
{
return marshal
? Marshal.PtrToStructure(memoryAddress)
: Unsafe.Read((void*)memoryAddress);
}
///
/// Reads a byte array from a specified memory address.
///
/// The memory address to read from.
/// The amount of bytes to read starting from the memoryAddress.
/// The read in byte array.
public static byte[] ReadRaw(IntPtr memoryAddress, int length)
{
var value = new byte[length];
Marshal.Copy(memoryAddress, value, 0, value.Length);
return value;
}
///
/// Reads a generic type array from a specified memory address.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The memory address to read from.
/// The amount of array items to read.
/// The read in struct array.
public static T[] Read(IntPtr memoryAddress, int arrayLength) where T : unmanaged
=> Read(memoryAddress, arrayLength, false);
///
/// Reads a generic type array from a specified memory address.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The memory address to read from.
/// The amount of array items to read.
/// Set this to true to enable struct marshalling.
/// The read in struct array.
public static T[] Read(IntPtr memoryAddress, int arrayLength, bool marshal)
{
var structSize = SizeOf(marshal);
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;
}
///
/// Reads a null-terminated byte array from a specified memory address.
///
/// The memory address to read from.
/// The read in byte array.
public static unsafe byte[] ReadRawNullTerminated(IntPtr memoryAddress)
{
var byteCount = 0;
while (*(byte*)(memoryAddress + byteCount) != 0x00)
{
byteCount++;
}
return ReadRaw(memoryAddress, byteCount);
}
#endregion
#region Read(out)
///
/// Reads a generic type from a specified memory address.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The memory address to read from.
/// Local variable to receive the read in struct.
public static void Read(IntPtr memoryAddress, out T value) where T : unmanaged
=> value = Read(memoryAddress);
///
/// Reads a generic type from a specified memory address.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The memory address to read from.
/// Local variable to receive the read in struct.
/// Set this to true to enable struct marshalling.
public static void Read(IntPtr memoryAddress, out T value, bool marshal)
=> value = Read(memoryAddress, marshal);
///
/// Reads raw data from a specified memory address.
///
/// The memory address to read from.
/// The amount of bytes to read starting from the memoryAddress.
/// Local variable to receive the read in bytes.
public static void ReadRaw(IntPtr memoryAddress, int length, out byte[] value)
=> value = ReadRaw(memoryAddress, length);
///
/// Reads a generic type array from a specified memory address.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The memory address to read from.
/// The amount of array items to read.
/// The read in struct array.
public static void Read(IntPtr memoryAddress, int arrayLength, out T[] value) where T : unmanaged
=> value = Read(memoryAddress, arrayLength);
///
/// Reads a generic type array from a specified memory address.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The memory address to read from.
/// The amount of array items to read.
/// Set this to true to enable struct marshalling.
/// The read in struct array.
public static void Read(IntPtr memoryAddress, int arrayLength, bool marshal, out T[] value)
=> value = Read(memoryAddress, arrayLength, marshal);
#endregion
#region ReadString
///
/// Read a UTF-8 encoded string from a specified memory address.
///
///
/// Attention! If this is an SeString, use the to decode or the applicable helper method.
///
/// The memory address to read from.
/// The read in string.
public static string ReadString(IntPtr memoryAddress)
=> ReadString(memoryAddress, 256);
///
/// Read a UTF-8 encoded string from a specified memory address.
///
///
/// Attention! If this is an SeString, use the to decode or the applicable helper method.
///
/// The memory address to read from.
/// The maximum length of the string.
/// The read in string.
public static string ReadString(IntPtr memoryAddress, int maxLength)
=> ReadString(memoryAddress, Encoding.UTF8, maxLength);
///
/// Read a string with the given encoding from a specified memory address.
///
///
/// Attention! If this is an SeString, use the to decode or the applicable helper method.
///
/// The memory address to read from.
/// The encoding to use to decode the string.
/// The read in string.
public static string ReadString(IntPtr memoryAddress, Encoding encoding)
=> ReadString(memoryAddress, encoding, 256);
///
/// Read a string with the given encoding from a specified memory address.
///
///
/// Attention! If this is an SeString, use the to decode or the applicable helper method.
///
/// The memory address to read from.
/// The encoding to use to decode the string.
/// The maximum length of the string.
/// The read in string.
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;
}
///
/// Read a null-terminated SeString from a specified memory address.
///
/// The memory address to read from.
/// The read in string.
public static SeString ReadSeStringNullTerminated(IntPtr memoryAddress)
{
var buffer = ReadRawNullTerminated(memoryAddress);
return seStringManager.Parse(buffer);
}
///
/// Read an SeString from a specified memory address.
///
/// The memory address to read from.
/// The maximum length of the string.
/// The read in string.
public static SeString ReadSeString(IntPtr memoryAddress, int maxLength)
{
ReadRaw(memoryAddress, maxLength, out var buffer);
var eos = Array.IndexOf(buffer, (byte)0);
if (eos < 0)
{
return seStringManager.Parse(buffer);
}
else
{
var newBuffer = new byte[eos];
Buffer.BlockCopy(buffer, 0, newBuffer, 0, eos);
return seStringManager.Parse(newBuffer);
}
}
///
/// Read an SeString from a specified Utf8String structure.
///
/// The memory address to read from.
/// The read in string.
public static unsafe SeString ReadSeString(Utf8String* utf8String)
{
if (utf8String == null)
return string.Empty;
var ptr = utf8String->StringPtr;
if (ptr == null)
return string.Empty;
var len = Math.Max(utf8String->BufUsed, utf8String->StringLength);
return ReadSeString((IntPtr)ptr, (int)len);
}
#endregion
#region ReadString(out)
///
/// Read a UTF-8 encoded string from a specified memory address.
///
///
/// Attention! If this is an SeString, use the to decode or the applicable helper method.
///
/// The memory address to read from.
/// The read in string.
public static void ReadString(IntPtr memoryAddress, out string value)
=> value = ReadString(memoryAddress);
///
/// Read a UTF-8 encoded string from a specified memory address.
///
///
/// Attention! If this is an SeString, use the to decode or the applicable helper method.
///
/// The memory address to read from.
/// The read in string.
/// The maximum length of the string.
public static void ReadString(IntPtr memoryAddress, out string value, int maxLength)
=> value = ReadString(memoryAddress, maxLength);
///
/// Read a string with the given encoding from a specified memory address.
///
///
/// Attention! If this is an SeString, use the to decode or the applicable helper method.
///
/// The memory address to read from.
/// The encoding to use to decode the string.
/// The read in string.
public static void ReadString(IntPtr memoryAddress, Encoding encoding, out string value)
=> value = ReadString(memoryAddress, encoding);
///
/// Read a string with the given encoding from a specified memory address.
///
///
/// Attention! If this is an SeString, use the to decode or the applicable helper method.
///
/// The memory address to read from.
/// The encoding to use to decode the string.
/// The maximum length of the string.
/// The read in string.
public static void ReadString(IntPtr memoryAddress, Encoding encoding, int maxLength, out string value)
=> value = ReadString(memoryAddress, encoding, maxLength);
///
/// Read a null-terminated SeString from a specified memory address.
///
/// The memory address to read from.
/// The read in SeString.
public static void ReadSeStringNullTerminated(IntPtr memoryAddress, out SeString value)
=> value = ReadSeStringNullTerminated(memoryAddress);
///
/// Read an SeString from a specified memory address.
///
/// The memory address to read from.
/// The maximum length of the string.
/// The read in SeString.
public static void ReadSeString(IntPtr memoryAddress, int maxLength, out SeString value)
=> value = ReadSeString(memoryAddress, maxLength);
///
/// Read an SeString from a specified Utf8String structure.
///
/// The memory address to read from.
/// The read in string.
public static unsafe void ReadSeString(Utf8String* utf8String, out SeString value)
=> value = ReadSeString(utf8String);
#endregion
#region Write
///
/// Writes a generic type to a specified memory address.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The memory address to read from.
/// The item to write to the address.
public static void Write(IntPtr memoryAddress, T item) where T : unmanaged
=> Write(memoryAddress, item);
///
/// Writes a generic type to a specified memory address.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The memory address to read from.
/// The item to write to the address.
/// Set this to true to enable struct marshalling.
public static void Write(IntPtr memoryAddress, T item, bool marshal)
{
if (marshal)
Marshal.StructureToPtr(item, memoryAddress, false);
else
Unsafe.Write((void*)memoryAddress, item);
}
///
/// Writes raw data to a specified memory address.
///
/// The memory address to read from.
/// The bytes to write to memoryAddress.
public static void WriteRaw(IntPtr memoryAddress, byte[] data)
{
Marshal.Copy(data, 0, memoryAddress, data.Length);
}
///
/// Writes a generic type array to a specified memory address.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The memory address to write to.
/// The array of items to write to the address.
public static void Write(IntPtr memoryAddress, T[] items) where T : unmanaged
=> Write(memoryAddress, items, false);
///
/// Writes a generic type array to a specified memory address.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The memory address to write to.
/// The array of items to write to the address.
/// Set this to true to enable struct marshalling.
public static void Write(IntPtr memoryAddress, T[] items, bool marshal)
{
var structSize = SizeOf(marshal);
for (var i = 0; i < items.Length; i++)
{
var address = memoryAddress + (structSize * i);
Write(address, items[i], marshal);
}
}
#endregion
#region WriteString
///
/// Write a UTF-8 encoded string to a specified memory address.
///
///
/// Attention! If this is an SeString, use the to encode or the applicable helper method.
///
/// The memory address to write to.
/// The string to write.
public static void WriteString(IntPtr memoryAddress, string value)
=> WriteString(memoryAddress, value, Encoding.UTF8);
///
/// Write a string with the given encoding to a specified memory address.
///
///
/// Attention! If this is an SeString, use the to encode or the applicable helper method.
///
/// The memory address to write to.
/// The string to write.
/// The encoding to use.
public static void WriteString(IntPtr memoryAddress, string value, Encoding encoding)
{
if (string.IsNullOrEmpty(value))
return;
var bytes = encoding.GetBytes(value + '\0');
WriteRaw(memoryAddress, bytes);
}
///
/// Write an SeString to a specified memory address.
///
/// The memory address to write to.
/// The SeString to write.
public static void WriteSeString(IntPtr memoryAddress, SeString value)
{
if (value is null)
return;
WriteRaw(memoryAddress, value.Encode());
}
#endregion
#region ApiWrappers
///
/// Allocates fixed size of memory inside the target memory source via Windows API calls.
/// Returns the address of newly allocated memory.
///
/// Amount of bytes to be allocated.
/// Address to the newly allocated memory.
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;
}
///
/// Allocates fixed size of memory inside the target memory source via Windows API calls.
/// Returns the address of newly allocated memory.
///
/// Amount of bytes to be allocated.
/// Address to the newly allocated memory.
public static void Allocate(int length, out IntPtr memoryAddress)
=> memoryAddress = Allocate(length);
///
/// Frees memory previously allocated with Allocate via Windows API calls.
///
/// The address of the memory to free.
/// True if the operation is successful.
public static bool Free(IntPtr memoryAddress)
{
return VirtualFree(memoryAddress, UIntPtr.Zero, AllocationType.Release);
}
///
/// Changes the page permissions for a specified combination of address and length via Windows API calls.
///
/// The memory address for which to change page permissions for.
/// The region size for which to change permissions for.
/// The new permissions to set.
/// The old page permissions.
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;
}
///
/// Changes the page permissions for a specified combination of address and length via Windows API calls.
///
/// The memory address for which to change page permissions for.
/// The region size for which to change permissions for.
/// The new permissions to set.
/// The old page permissions.
public static void ChangePermission(IntPtr memoryAddress, int length, MemoryProtection newPermissions, out MemoryProtection oldPermissions)
=> oldPermissions = ChangePermission(memoryAddress, length, newPermissions);
///
/// Changes the page permissions for a specified combination of address and element from which to deduce size via Windows API calls.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The memory address for which to change page permissions for.
/// The struct element from which the region size to change permissions for will be calculated.
/// The new permissions to set.
/// Set to true to calculate the size of the struct after marshalling instead of before.
/// The old page permissions.
public static MemoryProtection ChangePermission(IntPtr memoryAddress, ref T baseElement, MemoryProtection newPermissions, bool marshal)
=> ChangePermission(memoryAddress, SizeOf(marshal), newPermissions);
///
/// Reads raw data from a specified memory address via Windows API calls.
/// This is noticably slower than Unsafe or Marshal.
///
/// The memory address to read from.
/// The amount of bytes to read starting from the memoryAddress.
/// The read in bytes.
public static byte[] ReadProcessMemory(IntPtr memoryAddress, int length)
{
var value = new byte[length];
ReadProcessMemory(memoryAddress, ref value);
return value;
}
///
/// Reads raw data from a specified memory address via Windows API calls.
/// This is noticably slower than Unsafe or Marshal.
///
/// The memory address to read from.
/// The amount of bytes to read starting from the memoryAddress.
/// The read in bytes.
public static void ReadProcessMemory(IntPtr memoryAddress, int length, out byte[] value)
=> value = ReadProcessMemory(memoryAddress, length);
///
/// Reads raw data from a specified memory address via Windows API calls.
/// This is noticably slower than Unsafe or Marshal.
///
/// The memory address to read from.
/// The read in bytes.
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})");
}
///
/// Writes raw data to a specified memory address via Windows API calls.
/// This is noticably slower than Unsafe or Marshal.
///
/// The memory address to write to.
/// The bytes to write to memoryAddress.
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
///
/// Returns the size of a specific primitive or struct type.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The size of the primitive or struct.
public static int SizeOf()
=> SizeOf(false);
///
/// Returns the size of a specific primitive or struct type.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// If set to true; will return the size of an element after marshalling.
/// The size of the primitive or struct.
public static int SizeOf(bool marshal)
=> marshal ? Marshal.SizeOf() : Unsafe.SizeOf();
///
/// Returns the size of a specific primitive or struct type.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The number of array elements present.
/// The size of the primitive or struct array.
public static int SizeOf(int elementCount) where T : unmanaged
=> SizeOf() * elementCount;
///
/// Returns the size of a specific primitive or struct type.
///
/// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute.
/// The number of array elements present.
/// If set to true; will return the size of an element after marshalling.
/// The size of the primitive or struct array.
public static int SizeOf(int elementCount, bool marshal)
=> SizeOf(marshal) * elementCount;
#endregion
///
/// Initialize with static access to Dalamud.
///
/// The Dalamud instance.
internal static void Initialize(Dalamud dalamud)
{
seStringManager = dalamud.SeStringManager;
handle = Process.GetCurrentProcess().Handle;
}
}
}