using System.Diagnostics;
using Process = System.Diagnostics.Process;
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 readonly bool _is64Bit;
private readonly int _processId;
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(System.Diagnostics.Process process, ProcessAccess desiredAccess = DefaultAccess)
{
_processId = process.Id;
_handle = NativeMethods.OpenProcess(desiredAccess, false, _processId);
if (_handle.IsInvalid)
{
int error = Marshal.GetLastPInvokeError();
throw new InvalidOperationException(
$"OpenProcess failed for PID {_processId}: error {error}");
}
// Derive target bitness. A 64-bit host sees a 32-bit target as WOW64.
// A 32-bit host can only open 32-bit targets. If the API fails, fall
// back to the current process bitness (self-open path).
if (!NativeMethods.IsWow64Process(_handle, out bool wow64))
{
wow64 = false;
}
_is64Bit = Environment.Is64BitProcess && !wow64;
// Process.MainModule throws Win32Exception for a bitness-mismatched or protected
// target. A missing image base must not sink the whole reader — callers can still
// use absolute addresses when ImageBase is unknown.
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 bool Is64Bit => _is64Bit;
///
public override int ProcessId => _processId;
///
public override byte[] ReadBytes(IntPtr address, int count, bool isRelative = false)
{
if (isRelative)
address = GetAbsolute(address);
return RpmHelper.ReadBytes(_handle, address, count);
}
///
public override int WriteBytes(IntPtr address, ReadOnlySpan bytes, bool isRelative = false)
{
if (isRelative)
address = GetAbsolute(address);
return RpmHelper.WriteBytes(_handle, address, bytes);
}
///
public override void Dispose()
{
if (!_disposed)
{
_disposed = true;
base.Dispose();
}
}
}