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