using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using JetBrains.Annotations; namespace Dalamud { /// /// Class facilitating safe memory access. /// /// /// Attention! The performance of these methods is severely worse than regular calls. /// Please consider using these instead in performance-critical code. /// public static class SafeMemory { private static readonly IntPtr Handle; private static readonly IntPtr MainModule; static SafeMemory() { Handle = Imports.GetCurrentProcess(); MainModule = Process.GetCurrentProcess().MainModule?.BaseAddress ?? IntPtr.Zero; } /// /// Read a byte array from the current process. /// /// The address to read from. /// The amount of bytes to read. /// The result buffer. /// Whether or not the read succeeded. public static bool ReadBytes(IntPtr address, int count, out byte[] buffer) { buffer = new byte[count <= 0 ? 0 : count]; return Imports.ReadProcessMemory(Handle, address, buffer, buffer.Length, out _); } /// /// Write a byte array to the current process. /// /// The address to write to. /// The buffer to write. /// Whether or not the write succeeded. public static bool WriteBytes(IntPtr address, byte[] buffer) { return Imports.WriteProcessMemory(Handle, address, buffer, buffer.Length, out _); } /// /// Read an object of the specified struct from the current process. /// /// The type of the struct. /// The address to read from. /// The resulting object. /// Whether or not the read succeeded. public static bool Read(IntPtr address, out T result) where T : struct { if (!ReadBytes(address, SizeCache.Size, out var buffer)) { result = default; return false; } using var mem = new LocalMemory(buffer.Length); mem.Write(buffer); result = mem.Read(); return true; } /// /// Read an array of objects of the specified struct from the current process. /// /// The type of the struct. /// The address to read from. /// The length of the array. /// An array of the read objects, or null if any entry of the array failed to read. [CanBeNull] public static T[] Read(IntPtr address, int count) where T : struct { var size = SizeOf(); if (!ReadBytes(address, count * size, out var buffer)) return null; var result = new T[count]; using var mem = new LocalMemory(buffer.Length); mem.Write(buffer); for (var i = 0; i < result.Length; i++) result[i] = mem.Read(i * size); return result; } /// /// Write a struct to the current process. /// /// The type of the struct. /// The address to write to. /// The object to write. /// Whether or not the write succeeded. public static bool Write(IntPtr address, T obj) where T : struct { using var mem = new LocalMemory(SizeCache.Size); mem.Write(obj); return WriteBytes(address, mem.Read()); } /// /// Write an array of structs to the current process. /// /// The type of the structs. /// The address to write to. /// The array to write. /// Whether or not the write succeeded. public static bool Write(IntPtr address, T[] objArray) where T : struct { if (objArray == null || objArray.Length == 0) return true; var size = SizeCache.Size; using var mem = new LocalMemory(objArray.Length * size); for (var i = 0; i < objArray.Length; i++) mem.Write(objArray[i], i * size); return WriteBytes(address, mem.Read()); } /// /// Read a string from the current process(UTF-8). /// /// /// Attention! This will use the .NET Encoding.UTF8 class to decode the text. /// If you read a FFXIV string, please use ReadBytes and parse the string with the applicable class, /// since Encoding.UTF8 destroys the FFXIV payload structure. /// /// The address to read from. /// The maximum length of the string. /// The read string, or null in case the read was not successful. [CanBeNull] public static string ReadString(IntPtr address, int maxLength = 256) { return ReadString(address, Encoding.UTF8, maxLength); } /// /// Read a string from the current process(UTF-8). /// /// /// Attention! This will use the .NET Encoding class to decode the text. /// If you read a FFXIV string, please use ReadBytes and parse the string with the applicable class, /// since Encoding may destroy the FFXIV payload structure. /// /// The address to read from. /// The encoding to use to decode the string. /// The maximum length of the string. /// The read string, or null in case the read was not successful. [CanBeNull] public static string ReadString(IntPtr address, Encoding encoding, int maxLength = 256) { if (!ReadBytes(address, maxLength, out var buffer)) return null; var data = encoding.GetString(buffer); var eosPos = data.IndexOf('\0'); return eosPos == -1 ? data : data.Substring(0, eosPos); } /// /// Write a string to the current process. /// /// /// Attention! This will use the .NET Encoding class to encode the text. /// If you read a FFXIV string, please use WriteBytes with the applicable encoded SeString, /// since Encoding may destroy the FFXIV payload structure. /// /// The address to write to. /// The string to write. /// Whether or not the write succeeded. public static bool WriteString(IntPtr address, string str) { return WriteString(address, str, Encoding.UTF8); } /// /// Write a string to the current process. /// /// /// Attention! This will use the .NET Encoding class to encode the text. /// If you read a FFXIV string, please use WriteBytes with the applicable encoded SeString, /// since Encoding may destroy the FFXIV payload structure. /// /// The address to write to. /// The string to write. /// The encoding to use. /// Whether or not the write succeeded. public static bool WriteString(IntPtr address, string str, Encoding encoding) { if (string.IsNullOrEmpty(str)) return true; return WriteBytes(address, encoding.GetBytes(str + "\0")); } /// /// Get the size of the passed type. /// /// The type to inspect. /// The size of the passed type. public static int SizeOf() where T : struct { return SizeCache.Size; } private static class SizeCache { // ReSharper disable once StaticMemberInGenericType public static readonly int Size; static SizeCache() { var type = typeof(T); if (type.IsEnum) type = type.GetEnumUnderlyingType(); Size = Type.GetTypeCode(type) == TypeCode.Boolean ? 1 : Marshal.SizeOf(type); } } private static class Imports { [DllImport("kernel32", SetLastError = true)] public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead); [DllImport("kernel32", SetLastError = true)] public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten); [DllImport("kernel32", SetLastError = false)] public static extern IntPtr GetCurrentProcess(); } private sealed class LocalMemory : IDisposable { private readonly int size; private IntPtr hGlobal; public LocalMemory(int size) { this.size = size; this.hGlobal = Marshal.AllocHGlobal(this.size); } ~LocalMemory() => this.Dispose(); public byte[] Read() { var bytes = new byte[this.size]; Marshal.Copy(this.hGlobal, bytes, 0, this.size); return bytes; } public T Read(int offset = 0) => (T)Marshal.PtrToStructure(this.hGlobal + offset, typeof(T)); public void Write(byte[] data, int index = 0) => Marshal.Copy(data, index, this.hGlobal, this.size); public void Write(T data, int offset = 0) => Marshal.StructureToPtr(data, this.hGlobal + offset, false); public void Dispose() { Marshal.FreeHGlobal(this.hGlobal); this.hGlobal = IntPtr.Zero; GC.SuppressFinalize(this); } } } }