mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-07 16:34:36 +01:00
* Use new Lock objects * Fix CA1513: Use ObjectDisposedException.ThrowIf * Fix CA1860: Avoid using 'Enumerable.Any()' extension method * Fix IDE0028: Use collection initializers or expressions * Fix CA2263: Prefer generic overload when type is known * Fix CA1862: Use the 'StringComparison' method overloads to perform case-insensitive string comparisons * Fix IDE0270: Null check can be simplified * Fix IDE0280: Use 'nameof' * Fix IDE0009: Add '.this' * Fix IDE0007: Use 'var' instead of explicit type * Fix IDE0062: Make local function static * Fix CA1859: Use concrete types when possible for improved performance * Fix IDE0066: Use switch expression Only applied to where it doesn't look horrendous. * Use is over switch * Fix CA1847: Use String.Contains(char) instead of String.Contains(string) with single characters * Fix SYSLIB1045: Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time. * Fix CA1866: Use 'string.EndsWith(char)' instead of 'string.EndsWith(string)' when you have a string with a single char * Fix IDE0057: Substring can be simplified * Fix IDE0059: Remove unnecessary value assignment * Fix CA1510: Use ArgumentNullException throw helper * Fix IDE0300: Use collection expression for array * Fix IDE0250: Struct can be made 'readonly' * Fix IDE0018: Inline variable declaration * Fix CA1850: Prefer static HashData method over ComputeHash * Fi CA1872: Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' * Update ModuleLog instantiations * Organize usings
321 lines
11 KiB
C#
321 lines
11 KiB
C#
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
|
|
using Windows.Win32.Foundation;
|
|
|
|
namespace Dalamud;
|
|
|
|
/// <summary>
|
|
/// Class facilitating safe memory access.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Attention! The performance of these methods is severely worse than regular <see cref="Marshal"/> calls.
|
|
/// Please consider using those instead in performance-critical code.
|
|
/// </remarks>
|
|
public static class SafeMemory
|
|
{
|
|
private static readonly HANDLE Handle;
|
|
|
|
static SafeMemory()
|
|
{
|
|
Handle = Windows.Win32.PInvoke.GetCurrentProcess();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read a byte array from the current process.
|
|
/// </summary>
|
|
/// <param name="address">The address to read from.</param>
|
|
/// <param name="count">The amount of bytes to read.</param>
|
|
/// <param name="buffer">The result buffer.</param>
|
|
/// <returns>Whether the read succeeded.</returns>
|
|
public static unsafe bool ReadBytes(IntPtr address, int count, out byte[] buffer)
|
|
{
|
|
if (Handle.IsNull)
|
|
{
|
|
buffer = [];
|
|
return false;
|
|
}
|
|
|
|
buffer = new byte[count <= 0 ? 0 : count];
|
|
fixed (byte* p = buffer)
|
|
{
|
|
UIntPtr bytesRead;
|
|
if (!Windows.Win32.PInvoke.ReadProcessMemory(
|
|
Handle,
|
|
address.ToPointer(),
|
|
p,
|
|
new UIntPtr((uint)count),
|
|
&bytesRead))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a byte array to the current process.
|
|
/// </summary>
|
|
/// <param name="address">The address to write to.</param>
|
|
/// <param name="buffer">The buffer to write.</param>
|
|
/// <returns>Whether the write succeeded.</returns>
|
|
public static unsafe bool WriteBytes(IntPtr address, byte[] buffer)
|
|
{
|
|
if (Handle.IsNull)
|
|
return false;
|
|
|
|
if (buffer.Length == 0)
|
|
return true;
|
|
|
|
UIntPtr bytesWritten;
|
|
fixed (byte* p = buffer)
|
|
{
|
|
if (!Windows.Win32.PInvoke.WriteProcessMemory(
|
|
Handle,
|
|
address.ToPointer(),
|
|
p,
|
|
new UIntPtr((uint)buffer.Length),
|
|
&bytesWritten))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read an object of the specified struct from the current process.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the struct.</typeparam>
|
|
/// <param name="address">The address to read from.</param>
|
|
/// <param name="result">The resulting object.</param>
|
|
/// <returns>Whether the read succeeded.</returns>
|
|
public static bool Read<T>(IntPtr address, out T result) where T : struct
|
|
{
|
|
if (!ReadBytes(address, SizeCache<T>.Size, out var buffer))
|
|
{
|
|
result = default;
|
|
return false;
|
|
}
|
|
|
|
using var mem = new LocalMemory(buffer.Length);
|
|
mem.Write(buffer);
|
|
|
|
result = mem.Read<T>();
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read an array of objects of the specified struct from the current process.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the struct.</typeparam>
|
|
/// <param name="address">The address to read from.</param>
|
|
/// <param name="count">The length of the array.</param>
|
|
/// <returns>An array of the read objects, or null if any entry of the array failed to read.</returns>
|
|
public static T[]? Read<T>(IntPtr address, int count) where T : struct
|
|
{
|
|
var size = SizeOf<T>();
|
|
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<T>(i * size);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a struct to the current process.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the struct.</typeparam>
|
|
/// <param name="address">The address to write to.</param>
|
|
/// <param name="obj">The object to write.</param>
|
|
/// <returns>Whether the write succeeded.</returns>
|
|
public static bool Write<T>(IntPtr address, T obj) where T : struct
|
|
{
|
|
using var mem = new LocalMemory(SizeCache<T>.Size);
|
|
mem.Write(obj);
|
|
return WriteBytes(address, mem.Read());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write an array of structs to the current process.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the structs.</typeparam>
|
|
/// <param name="address">The address to write to.</param>
|
|
/// <param name="objArray">The array to write.</param>
|
|
/// <returns>Whether the write succeeded.</returns>
|
|
public static bool Write<T>(IntPtr address, T[] objArray) where T : struct
|
|
{
|
|
if (objArray == null || objArray.Length == 0)
|
|
return true;
|
|
var size = SizeCache<T>.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());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read a string from the current process(UTF-8).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="address">The address to read from.</param>
|
|
/// <param name="maxLength">The maximum length of the string.</param>
|
|
/// <returns>The read string, or null in case the read was not successful.</returns>
|
|
public static string? ReadString(IntPtr address, int maxLength = 256)
|
|
{
|
|
return ReadString(address, Encoding.UTF8, maxLength);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read a string from the current process(UTF-8).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="address">The address to read from.</param>
|
|
/// <param name="encoding">The encoding to use to decode the string.</param>
|
|
/// <param name="maxLength">The maximum length of the string.</param>
|
|
/// <returns>The read string, or null in case the read was not successful.</returns>
|
|
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[..eosPos];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a string to the current process.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="address">The address to write to.</param>
|
|
/// <param name="str">The string to write.</param>
|
|
/// <returns>Whether the write succeeded.</returns>
|
|
public static bool WriteString(IntPtr address, string str)
|
|
{
|
|
return WriteString(address, str, Encoding.UTF8);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a string to the current process.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="address">The address to write to.</param>
|
|
/// <param name="str">The string to write.</param>
|
|
/// <param name="encoding">The encoding to use.</param>
|
|
/// <returns>Whether the write succeeded.</returns>
|
|
public static bool WriteString(IntPtr address, string str, Encoding encoding)
|
|
{
|
|
if (string.IsNullOrEmpty(str))
|
|
return true;
|
|
return WriteBytes(address, encoding.GetBytes(str + "\0"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Marshals data from an unmanaged block of memory to a managed object.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type to create.</typeparam>
|
|
/// <param name="addr">The address to read from.</param>
|
|
/// <returns>The read object, or null, if it could not be read.</returns>
|
|
public static T? PtrToStructure<T>(IntPtr addr) where T : struct => (T?)PtrToStructure(addr, typeof(T));
|
|
|
|
/// <summary>
|
|
/// Marshals data from an unmanaged block of memory to a managed object.
|
|
/// </summary>
|
|
/// <param name="addr">The address to read from.</param>
|
|
/// <param name="type">The type to create.</param>
|
|
/// <returns>The read object, or null, if it could not be read.</returns>
|
|
public static object? PtrToStructure(IntPtr addr, Type type)
|
|
{
|
|
var size = Marshal.SizeOf(type);
|
|
|
|
if (!ReadBytes(addr, size, out var buffer))
|
|
return null;
|
|
|
|
var mem = new LocalMemory(size);
|
|
mem.Write(buffer);
|
|
|
|
return mem.Read(type);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the size of the passed type.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type to inspect.</typeparam>
|
|
/// <returns>The size of the passed type.</returns>
|
|
public static int SizeOf<T>() where T : struct
|
|
{
|
|
return SizeCache<T>.Size;
|
|
}
|
|
|
|
private static class SizeCache<T>
|
|
{
|
|
// 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 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<T>(int offset = 0) => Marshal.PtrToStructure<T>(this.hGlobal + offset);
|
|
|
|
public object? Read(Type type, int offset = 0) => Marshal.PtrToStructure(this.hGlobal + offset, type);
|
|
|
|
public void Write(byte[] data, int index = 0) => Marshal.Copy(data, index, this.hGlobal, this.size);
|
|
|
|
public void Write<T>(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);
|
|
}
|
|
}
|
|
}
|