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(); } } }