Implement core diagnostic memory layer, execution helpers, and high-level facade slices
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.
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using WhiteMagic.Native;
|
||||
|
||||
namespace WhiteMagic.Memory;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a chunk of remote memory subdivided into named regions.
|
||||
/// </summary>
|
||||
public sealed class AllocatedMemory : IDisposable
|
||||
{
|
||||
private readonly MemoryBase _memory;
|
||||
private readonly IntPtr _baseAddress;
|
||||
private readonly int _size;
|
||||
private readonly Dictionary<string, int> _regions;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new allocated memory chunk.
|
||||
/// </summary>
|
||||
/// <param name="memory">The memory accessor.</param>
|
||||
/// <param name="size">The size of the allocation in bytes.</param>
|
||||
/// <param name="protection">The initial memory protection.</param>
|
||||
/// <exception cref="Win32Exception">Allocation fails.</exception>
|
||||
public AllocatedMemory(MemoryBase memory, int size, MemoryProtectionType protection = MemoryProtectionType.ExecuteReadWrite)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(memory);
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(size);
|
||||
|
||||
_memory = memory;
|
||||
_size = size;
|
||||
_regions = new Dictionary<string, int>();
|
||||
|
||||
// Allocate using VirtualAllocEx
|
||||
_baseAddress = NativeMethods.VirtualAllocEx(
|
||||
memory.Handle,
|
||||
IntPtr.Zero,
|
||||
size,
|
||||
MemoryAllocationType.Commit | MemoryAllocationType.Reserve,
|
||||
protection);
|
||||
|
||||
if (_baseAddress == IntPtr.Zero)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new Win32Exception(error, $"VirtualAllocEx failed (size={size}).");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the base address of the allocated memory.
|
||||
/// </summary>
|
||||
public IntPtr BaseAddress => _baseAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the allocation in bytes.
|
||||
/// </summary>
|
||||
public int Size => _size;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a named region at a specific offset within the allocation.
|
||||
/// </summary>
|
||||
/// <param name="name">The unique name for the region.</param>
|
||||
/// <param name="offset">The offset from the base address.</param>
|
||||
/// <exception cref="ArgumentException">A region with this name already exists.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Offset is outside the allocation bounds.</exception>
|
||||
public void AddRegion(string name, int offset)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(offset);
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(offset, _size);
|
||||
|
||||
if (_regions.ContainsKey(name))
|
||||
throw new ArgumentException($"Region '{name}' already exists.", nameof(name));
|
||||
|
||||
_regions[name] = offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the absolute address of a named region.
|
||||
/// </summary>
|
||||
/// <param name="name">The region name.</param>
|
||||
/// <returns>The absolute address of the region.</returns>
|
||||
/// <exception cref="ArgumentException">No region with this name exists.</exception>
|
||||
public IntPtr AddressOf(string name)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
|
||||
if (!_regions.TryGetValue(name, out int offset))
|
||||
throw new ArgumentException($"Region '{name}' does not exist.", nameof(name));
|
||||
|
||||
return _baseAddress + offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a value of type <typeparamref name="T"/> from a named region.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The value type.</typeparam>
|
||||
/// <param name="name">The region name.</param>
|
||||
/// <returns>The value read from memory.</returns>
|
||||
/// <exception cref="ArgumentException">No region with this name exists.</exception>
|
||||
public T Read<T>(string name) where T : struct
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
IntPtr address = AddressOf(name);
|
||||
return _memory.Read<T>(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a value of type <typeparamref name="T"/> to a named region.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The value type.</typeparam>
|
||||
/// <param name="name">The region name.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
/// <returns><see langword="true"/> if all bytes were written.</returns>
|
||||
/// <exception cref="ArgumentException">No region with this name exists.</exception>
|
||||
public bool Write<T>(string name, T value) where T : struct
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
IntPtr address = AddressOf(name);
|
||||
return _memory.Write(address, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads bytes from a named region.
|
||||
/// </summary>
|
||||
/// <param name="name">The region name.</param>
|
||||
/// <param name="count">The number of bytes to read.</param>
|
||||
/// <returns>The bytes read from memory.</returns>
|
||||
/// <exception cref="ArgumentException">No region with this name exists.</exception>
|
||||
public byte[] ReadBytes(string name, int count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
IntPtr address = AddressOf(name);
|
||||
return _memory.ReadBytes(address, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes bytes to a named region.
|
||||
/// </summary>
|
||||
/// <param name="name">The region name.</param>
|
||||
/// <param name="bytes">The bytes to write.</param>
|
||||
/// <returns>The number of bytes written.</returns>
|
||||
/// <exception cref="ArgumentException">No region with this name exists.</exception>
|
||||
public int WriteBytes(string name, ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
IntPtr address = AddressOf(name);
|
||||
return _memory.WriteBytes(address, bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees the allocated memory.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
// Free using VirtualFreeEx
|
||||
if (_baseAddress != IntPtr.Zero)
|
||||
{
|
||||
NativeMethods.VirtualFreeEx(
|
||||
_memory.Handle,
|
||||
_baseAddress,
|
||||
0,
|
||||
MemoryFreeType.Release);
|
||||
}
|
||||
|
||||
_regions.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user