Implemented: - Core: UTF-16 ReadString boundary/alignment fix, target bitness and process id on MemoryBase - function interception: PatchManager, DetourManager, InstructionAnalyzer, MainThreadDispatcher - Execution: BackgroundTaskExecutor, InProcessInvoker - High-level: Magic facade, RemotePointer, async wrappers - Discovery/external code loading/Window groundwork (PEB/TEB, pattern scanning, raw allocations, DLL external code loading, window/input) Tests: 180 passing, 4 integration/interactive tests skipped.
100 lines
3.2 KiB
C#
100 lines
3.2 KiB
C#
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using WhiteMagic.Native;
|
|
|
|
namespace WhiteMagic;
|
|
|
|
/// <summary>
|
|
/// In-process memory reader that accesses the owning process's memory through
|
|
/// <see cref="NativeMethods.ReadProcessMemory"/> and
|
|
/// <see cref="NativeMethods.WriteProcessMemory"/> 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 <see cref="AccessViolationException"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This is functionally equivalent to <see cref="ExternalReader"/> opened on the current
|
|
/// process. It exists as a distinct type because the design (see
|
|
/// <c>openspec/changes/whitemagic-foundation/design.md</c> D1) treats "injected in-process"
|
|
/// as a separate mode from "external". The two modes will diverge further once the
|
|
/// <c>InProcessInvoker</c> delegate-call path lands.
|
|
/// </remarks>
|
|
public sealed class InProcessReader : MemoryBase
|
|
{
|
|
private readonly SafeMemoryHandle _handle;
|
|
private readonly IntPtr _imageBase;
|
|
private readonly int _processId;
|
|
private bool _disposed;
|
|
|
|
/// <summary>
|
|
/// Creates an in-process reader for the current process.
|
|
/// </summary>
|
|
public InProcessReader()
|
|
{
|
|
Process current = Process.GetCurrentProcess();
|
|
_processId = current.Id;
|
|
_handle = NativeMethods.OpenProcess(
|
|
ProcessAccess.VmRead | ProcessAccess.VmWrite | ProcessAccess.VmOperation
|
|
| ProcessAccess.QueryInformation | ProcessAccess.CreateThread | ProcessAccess.Synchronize,
|
|
false,
|
|
current.Id);
|
|
if (_handle.IsInvalid)
|
|
{
|
|
int error = Marshal.GetLastPInvokeError();
|
|
throw new InvalidOperationException(
|
|
$"OpenProcess failed for PID {current.Id}: error {error}");
|
|
}
|
|
|
|
// Process.MainModule rarely throws on the current process, but guard it
|
|
// nonetheless for parity with ExternalReader.
|
|
try
|
|
{
|
|
_imageBase = current.MainModule?.BaseAddress ?? IntPtr.Zero;
|
|
}
|
|
catch (System.ComponentModel.Win32Exception)
|
|
{
|
|
_imageBase = IntPtr.Zero;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override IntPtr ImageBase => _imageBase;
|
|
|
|
/// <inheritdoc />
|
|
public override SafeMemoryHandle Handle => _handle;
|
|
|
|
/// <inheritdoc />
|
|
public override bool Is64Bit => Environment.Is64BitProcess;
|
|
|
|
/// <inheritdoc />
|
|
public override int ProcessId => _processId;
|
|
|
|
/// <inheritdoc />
|
|
public override byte[] ReadBytes(IntPtr address, int count, bool isRelative = false)
|
|
{
|
|
if (isRelative)
|
|
address = GetAbsolute(address);
|
|
|
|
return RpmHelper.ReadBytes(_handle, address, count);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override int WriteBytes(IntPtr address, ReadOnlySpan<byte> bytes, bool isRelative = false)
|
|
{
|
|
if (isRelative)
|
|
address = GetAbsolute(address);
|
|
|
|
return RpmHelper.WriteBytes(_handle, address, bytes);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Dispose()
|
|
{
|
|
if (!_disposed)
|
|
{
|
|
_disposed = true;
|
|
base.Dispose();
|
|
}
|
|
}
|
|
}
|