using System; using System.Linq; using System.Runtime.InteropServices; using WhiteMagic.Native; namespace WhiteMagic.Hooking; /// /// A single reversible byte patch. Captures the original bytes when applied, /// restores them when removed, and reports its state by comparing live memory. /// public sealed class Patch : IDisposable { private readonly MemoryBase _memory; /// The unique name of this patch. public string Name { get; } /// The address the patch overwrites. public IntPtr Address { get; } /// The bytes written by the patch. public byte[] PatchBytes { get; } /// The bytes captured before the patch was applied. public byte[]? OriginalBytes { get; private set; } /// /// when the live bytes at match /// . /// public bool IsApplied { get { byte[] current = _memory.ReadBytes(Address, PatchBytes.Length); return current.SequenceEqual(PatchBytes); } } internal Patch(MemoryBase memory, string name, IntPtr address, byte[] patchBytes) { ArgumentNullException.ThrowIfNull(patchBytes); _memory = memory; Name = name; Address = address; PatchBytes = patchBytes; } /// Captures the original bytes and writes the patch bytes. public void Apply() { if (IsApplied) return; OriginalBytes = _memory.ReadBytes(Address, PatchBytes.Length); if (!NativeMethods.VirtualProtectEx( _memory.Handle, Address, PatchBytes.Length, MemoryProtectionType.ExecuteReadWrite, out MemoryProtectionType oldProtect)) { int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException($"Failed to change target memory protection: error {error}"); } try { _memory.WriteBytes(Address, PatchBytes); } finally { NativeMethods.VirtualProtectEx( _memory.Handle, Address, PatchBytes.Length, oldProtect, out _); } } /// Restores the original bytes if they were captured. public void Remove() { if (OriginalBytes is null) return; if (!NativeMethods.VirtualProtectEx( _memory.Handle, Address, OriginalBytes.Length, MemoryProtectionType.ExecuteReadWrite, out MemoryProtectionType oldProtect)) { int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException($"Failed to change target memory protection: error {error}"); } try { _memory.WriteBytes(Address, OriginalBytes); } finally { NativeMethods.VirtualProtectEx( _memory.Handle, Address, OriginalBytes.Length, oldProtect, out _); } OriginalBytes = null; } /// public void Dispose() { Remove(); } }