using System.Diagnostics; using System.Runtime.InteropServices; using WhiteMagic.Native; namespace WhiteMagic; /// /// In-process memory reader that accesses the owning process's memory through /// and /// on a handle to the current /// process. Unlike the unsafe-deref approach, this fails softly (returns /// empty / zero bytes) on invalid or protected addresses instead of crashing /// the host process with an . /// public sealed class InProcessReader : MemoryBase { private readonly SafeMemoryHandle _handle; private readonly IntPtr _imageBase; private bool _disposed; /// /// Creates an in-process reader for the current process. /// public InProcessReader() { Process current = Process.GetCurrentProcess(); _handle = NativeMethods.OpenProcess( ProcessAccess.VmRead | ProcessAccess.VmWrite | ProcessAccess.VmOperation | ProcessAccess.QueryInformation, false, current.Id); if (_handle.IsInvalid) { int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException( $"OpenProcess failed for PID {current.Id}: error {error}"); } _imageBase = current.MainModule?.BaseAddress ?? IntPtr.Zero; } /// public override IntPtr ImageBase => _imageBase; /// public override SafeMemoryHandle Handle => _handle; /// public override byte[] ReadBytes(IntPtr address, int count, bool isRelative = false) { if (isRelative) address = GetAbsolute(address); byte[] buffer = new byte[count]; if (!NativeMethods.ReadProcessMemory(_handle, address, buffer, count, out nint bytesRead)) { return []; } if ((int)bytesRead != count) { Array.Resize(ref buffer, (int)bytesRead); } return buffer; } /// public override int WriteBytes(IntPtr address, ReadOnlySpan bytes, bool isRelative = false) { if (isRelative) address = GetAbsolute(address); if (!NativeMethods.WriteProcessMemory(_handle, address, bytes, bytes.Length, out nint written)) { return 0; } return (int)written; } /// public override void Dispose() { if (!_disposed) { _disposed = true; _handle.Dispose(); } } }