using System.ComponentModel;
using System.Runtime.InteropServices;
using WhiteMagic.Native;
namespace WhiteMagic.Memory;
///
/// Represents a chunk of remote memory subdivided into named regions.
///
public sealed class AllocatedMemory : IDisposable
{
private readonly MemoryBase _memory;
private readonly IntPtr _baseAddress;
private readonly int _size;
private readonly Dictionary _regions;
private bool _disposed;
///
/// Creates a new allocated memory chunk.
///
/// The memory accessor.
/// The size of the allocation in bytes.
/// The initial memory protection.
/// Allocation fails.
public AllocatedMemory(MemoryBase memory, int size, MemoryProtectionType protection = MemoryProtectionType.ExecuteReadWrite)
{
ArgumentNullException.ThrowIfNull(memory);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(size);
_memory = memory;
_size = size;
_regions = new Dictionary();
// 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}).");
}
}
///
/// Gets the base address of the allocated memory.
///
public IntPtr BaseAddress => _baseAddress;
///
/// Gets the size of the allocation in bytes.
///
public int Size => _size;
///
/// Adds a named region at a specific offset within the allocation.
///
/// The unique name for the region.
/// The offset from the base address.
/// A region with this name already exists.
/// Offset is outside the allocation bounds.
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;
}
///
/// Gets the absolute address of a named region.
///
/// The region name.
/// The absolute address of the region.
/// No region with this name exists.
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;
}
///
/// Reads a value of type from a named region.
///
/// The value type.
/// The region name.
/// The value read from memory.
/// No region with this name exists.
public T Read(string name) where T : struct
{
ObjectDisposedException.ThrowIf(_disposed, this);
IntPtr address = AddressOf(name);
return _memory.Read(address);
}
///
/// Writes a value of type to a named region.
///
/// The value type.
/// The region name.
/// The value to write.
/// if all bytes were written.
/// No region with this name exists.
public bool Write(string name, T value) where T : struct
{
ObjectDisposedException.ThrowIf(_disposed, this);
IntPtr address = AddressOf(name);
return _memory.Write(address, value);
}
///
/// Reads bytes from a named region.
///
/// The region name.
/// The number of bytes to read.
/// The bytes read from memory.
/// No region with this name exists.
public byte[] ReadBytes(string name, int count)
{
ObjectDisposedException.ThrowIf(_disposed, this);
IntPtr address = AddressOf(name);
return _memory.ReadBytes(address, count);
}
///
/// Writes bytes to a named region.
///
/// The region name.
/// The bytes to write.
/// The number of bytes written.
/// No region with this name exists.
public int WriteBytes(string name, ReadOnlySpan bytes)
{
ObjectDisposedException.ThrowIf(_disposed, this);
IntPtr address = AddressOf(name);
return _memory.WriteBytes(address, bytes);
}
///
/// Frees the allocated memory.
///
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
// Free using VirtualFreeEx
if (_baseAddress != IntPtr.Zero)
{
NativeMethods.VirtualFreeEx(
_memory.Handle,
_baseAddress,
0,
MemoryFreeType.Release);
}
_regions.Clear();
}
}
}