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; } } }