Files
whitemagic/WhiteMagic/ExternalReader.cs
T
kbe 6b34e0fab8 task 2.3-2.4: implement MemoryBase + ExternalReader with tests
Add abstract MemoryBase base class with typed Read<T>/Write<T>, array
IO, string IO, and relative/absolute addressing. Uses MarshalCache<T>
to branch between blittable (MemoryMarshal) and marshal-required paths.

Add ExternalReader (out-of-process via ReadProcessMemory/WriteProcessMemory)
with SafeMemoryHandle lifecycle management.

11 new tests: ImageBase, Read/Write of int/byte/long/struct, byte array,
int array, struct array, invalid address, dispose, double-dispose.
All passing (total: 30).
2026-07-21 17:05:29 +02:00

87 lines
2.4 KiB
C#

using System.Diagnostics;
using System.Runtime.InteropServices;
using WhiteMagic.Native;
namespace WhiteMagic;
/// <summary>
/// Out-of-process memory reader that accesses the target's memory through
/// <see cref="NativeMethods.ReadProcessMemory"/> and
/// <see cref="NativeMethods.WriteProcessMemory"/>.
/// </summary>
public sealed class ExternalReader : MemoryBase
{
private readonly SafeMemoryHandle _handle;
private readonly IntPtr _imageBase;
private bool _disposed;
/// <summary>
/// Opens a process for external memory access.
/// </summary>
/// <param name="process">The target process.</param>
/// <param name="desiredAccess">The access rights to request. Defaults to
/// <see cref="ProcessAccess.AllAccess"/>.</param>
public ExternalReader(Process process, ProcessAccess desiredAccess = ProcessAccess.AllAccess)
{
_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}");
}
_imageBase = process.MainModule?.BaseAddress ?? IntPtr.Zero;
}
/// <inheritdoc />
public override IntPtr ImageBase => _imageBase;
/// <inheritdoc />
public override SafeMemoryHandle Handle => _handle;
/// <inheritdoc />
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;
}
/// <inheritdoc />
public override int WriteBytes(IntPtr address, ReadOnlySpan<byte> 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;
}
/// <inheritdoc />
public override void Dispose()
{
if (!_disposed)
{
_disposed = true;
_handle.Dispose();
}
}
}