diff --git a/Dalamud/Game/Internal/AntiDebug.cs b/Dalamud/Game/Internal/AntiDebug.cs index acc65094d..3cb169311 100644 --- a/Dalamud/Game/Internal/AntiDebug.cs +++ b/Dalamud/Game/Internal/AntiDebug.cs @@ -31,8 +31,8 @@ namespace Dalamud.Game.Internal this.original = new byte[this.nop.Length]; if (DebugCheckAddress != IntPtr.Zero && !IsEnabled) { Log.Information($"Overwriting Debug Check @ 0x{DebugCheckAddress.ToInt64():X}"); - Marshal.Copy(DebugCheckAddress, this.original, 0, this.nop.Length); - Marshal.Copy(this.nop, 0, DebugCheckAddress, this.nop.Length); + SafeMemory.ReadBytes(DebugCheckAddress, this.nop.Length, out this.original); + SafeMemory.WriteBytes(DebugCheckAddress, this.nop); } else { Log.Information("DebugCheck already overwritten?"); } diff --git a/Dalamud/SafeMemory.cs b/Dalamud/SafeMemory.cs new file mode 100644 index 000000000..e38c2d333 --- /dev/null +++ b/Dalamud/SafeMemory.cs @@ -0,0 +1,258 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Dalamud +{ + /// + /// Class facilitating safe memory access + /// + 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, SizeOf(), 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(); + var result = new T[count]; + + var readSucceeded = true; + for (var i = 0; i < count; i++) { + var success = Read(address + i * size, out var res); + + if (!success) + readSucceeded = false; + + result[i] = res; + } + + return !readSucceeded ? null : 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(SizeOf()); + 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 = SizeOf(); + for (var i = 0; i < objArray.Length; i++) + if (!Write(address + i * size, objArray[i])) + return false; + return true; + } + + /// + /// 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() + { + var type = typeof(T); + if (type.IsEnum) + type = type.GetEnumUnderlyingType(); + return 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 IntPtr hGlobal; + private readonly int size; + + public LocalMemory(int size) + { + this.size = size; + this.hGlobal = Marshal.AllocHGlobal(this.size); + } + + public byte[] Read() + { + var bytes = new byte[this.size]; + Marshal.Copy(this.hGlobal, bytes, 0, this.size); + return bytes; + } + + public T Read() => (T)Marshal.PtrToStructure(this.hGlobal, typeof(T)); + public void Write(byte[] data, int index = 0) => Marshal.Copy(data, index, this.hGlobal, this.size); + public void Write(T data) => Marshal.StructureToPtr(data, this.hGlobal, false); + ~LocalMemory() => Dispose(); + + public void Dispose() + { + Marshal.FreeHGlobal(this.hGlobal); + this.hGlobal = IntPtr.Zero; + GC.SuppressFinalize(this); + } + } + } +}