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