IProcessЕсли у вас уже есть свой драйвер, bridge или другой способ читать память, почти никогда не нужно писать "ещё один Memory API". Обычно достаточно собрать два слоя:
IMemoryBackend для raw read/writeIProcess, который описывает процесс и отдаёт MemoryПосле этого поверх вашего backend'а сразу начинают работать типизированное чтение, MemoryOfModule(...), импорты, экспорты, сигнатуры и code cave.
NativeLocalProcess — хороший пример process-wrapper поверх уже готового backend'аLCProcess — хороший ориентир для backend'а, который в основном про чтение и анализKDProcess — пример backend'а с более глубоким control APIIMemoryBackend остаётся очень маленьким:
public interface IMemoryBackend : IDisposable, IMemorySpan
{
bool IsValid { get; }
bool TryReadMemory(IntPtr address, Span<byte> target);
bool TryWriteMemory(IntPtr address, ReadOnlySpan<byte> source);
}
Чаще всего именно сюда ложится ваш вызов драйвера, IOCTL, RPC или bridge к DMA.
Сверху вы делаете IProcess и сразу отдаёте MemoryAccessor:
public sealed class MyDriverProcess : DisposableReactiveObject, IProcess
{
public MyDriverProcess(MyDriverClient driver, int processId, string? processName = null)
{
ProcessId = processId;
ProcessName = processName;
var memoryBackend = new MyDriverMemoryBackend(driver, processId).AddTo(Anchors);
Memory = new MemoryAccessor(Log, memoryBackend).AddTo(Anchors);
}
public string? ProcessName { get; }
public int ProcessId { get; }
public IProcessMemory Memory { get; }
public IReadOnlyList<ProcessModuleInformation> GetProcessModules() => driver.GetModules(ProcessId);
public IReadOnlyList<ProcessThreadInformation> GetThreads() => driver.GetThreads(ProcessId);
public MemoryBasicInformation VirtualQuery(IntPtr address) => driver.VirtualQuery(ProcessId, address);
public IReadOnlyList<MemoryBasicInformation> GetMemoryRegions() => driver.GetMemoryRegions(ProcessId);
}
Ключевая строка здесь одна:
Memory = new MemoryAccessor(Log, memoryBackend);
Именно она подключает весь верхний слой EyeAuras.
Не каждый кастомный IProcess обязан читать память снаружи. Ещё один рабочий вариант - загрузить свою DLL внутрь процесса, поднять там транспорт и дальше общаться с ней по named pipe, shared memory или другому IPC.
В такой схеме внешний код обычно больше не ходит в память напрямую. Вместо этого он отправляет запрос агенту внутри процесса:
Снаружи это всё равно обычно выглядит как тот же самый стек:
IMemoryBackend поверх транспортаIProcess поверх backend'аТо есть верхний слой Memory API почти не меняется. Меняется только то, откуда реально берутся данные.
Такой подход полезен, если вам нужно:
У такого варианта есть и цена:
DLL внутрь процессаЕсли вас интересует именно доставка DLL внутрь процесса, дальше стоит читать DLL inject.
Если хотите, чтобы интеграция была действительно удобной, не ограничивайтесь одним TryReadMemory(...). Практический минимум такой:
MemoryGetProcessModules()GetThreads()VirtualQuery(...)GetMemoryRegions()Без модулей вы теряете нормальный MemoryOfModule(...). Без регионов и VirtualQuery(...) хуже живёт анализ памяти.
Если ваш драйвер умеет больше, поверх IProcess можно объявить и дополнительные capability-интерфейсы:
IProcessControlApi — suspend, allocate, protect, remote threadIProcessSupportsManualMappingIProcessSupportsUserApcТо есть базовый путь такой:
DLL inject и manual mappingЕсли у игры нестандартный layout структур, это обычно решается не новым backend'ом, а отдельным mapper'ом поверх уже готового IProcess.