using System.Diagnostics; using System.Runtime.InteropServices; using WhiteMagic.Native; namespace WhiteMagic; /// /// Out-of-process memory reader that accesses the target's memory through /// and /// . /// public sealed class ExternalReader : MemoryBase { private readonly SafeMemoryHandle _handle; private readonly IntPtr _imageBase; private bool _disposed; /// /// The default access rights: enough to read, write, allocate, query, run a remote /// thread, and wait on it. This deliberately omits , /// which over-requests and makes OpenProcess fail on protected processes where /// these narrower rights would succeed. /// public const ProcessAccess DefaultAccess = ProcessAccess.VmRead | ProcessAccess.VmWrite | ProcessAccess.VmOperation | ProcessAccess.QueryInformation | ProcessAccess.CreateThread | ProcessAccess.Synchronize; /// /// Opens a process for external memory access. /// /// The target process. /// The access rights to request. Defaults to /// . public ExternalReader(Process process, ProcessAccess desiredAccess = DefaultAccess) { _handle = NativeMethods.OpenProcess(desiredAccess, false, process.Id); if (_handle.IsInvalid) { int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException( $"OpenProcess failed for PID {process.Id}: error {error}"); } // Process.MainModule throws Win32Exception for a bitness-mismatched or protected // target; a missing image base must not sink the whole reader. try { _imageBase = process.MainModule?.BaseAddress ?? IntPtr.Zero; } catch (System.ComponentModel.Win32Exception) { _imageBase = 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(); } } }