using WhiteMagic.Native;
using System.Runtime.InteropServices;
using System.Text;
namespace WhiteMagic;
///
/// Abstract base for all memory-access readers and writers. Provides typed
/// /, array IO, string IO, and
/// relative/absolute addressing. Subclasses implement the concrete
/// and methods.
///
public abstract class MemoryBase : IDisposable
{
/// The base address of the target process's main module.
public abstract IntPtr ImageBase { get; }
/// The native handle to the target process.
public abstract SafeMemoryHandle Handle { get; }
// ── Raw byte IO ────────────────────────────────────────────────────────
/// Reads a sequence of bytes from the target address.
public abstract byte[] ReadBytes(IntPtr address, int count, bool isRelative = false);
/// Writes a sequence of bytes to the target address.
/// The number of bytes written.
public abstract int WriteBytes(IntPtr address, ReadOnlySpan bytes, bool isRelative = false);
// ── Typed IO ───────────────────────────────────────────────────────────
/// Reads a value of type from the target address.
public T Read(IntPtr address, bool isRelative = false) where T : struct
{
if (isRelative)
address = GetAbsolute(address);
int size = MarshalCache.Size;
Span buffer = stackalloc byte[size];
byte[] raw = ReadBytes(address, size);
raw.CopyTo(buffer);
if (MarshalCache.TypeRequiresMarshal)
{
return MarshalByteArrayToStructure(raw);
}
return MemoryMarshal.Read(buffer);
}
/// Writes a value of type to the target address.
/// if all bytes were written.
public bool Write(IntPtr address, T value, bool isRelative = false) where T : struct
{
if (isRelative)
address = GetAbsolute(address);
int size = MarshalCache.Size;
Span buffer = stackalloc byte[size];
if (MarshalCache.TypeRequiresMarshal)
{
StructureToByteArray(value, buffer, size);
}
else
{
MemoryMarshal.Write(buffer, in value);
}
int written = WriteBytes(address, buffer, false);
return written == size;
}
/// Reads an array of values of type from the target address.
public T[] Read(IntPtr address, int count, bool isRelative = false) where T : struct
{
if (isRelative)
address = GetAbsolute(address);
int elementSize = MarshalCache.Size;
int totalSize = elementSize * count;
byte[] raw = ReadBytes(address, totalSize);
var result = new T[count];
if (MarshalCache.TypeRequiresMarshal)
{
for (int i = 0; i < count; i++)
{
var elementBytes = new ReadOnlySpan(raw, i * elementSize, elementSize);
result[i] = MarshalByteArrayToStructure(elementBytes.ToArray());
}
}
else
{
ReadOnlySpan span = raw;
for (int i = 0; i < count; i++)
{
result[i] = MemoryMarshal.Read(span.Slice(i * elementSize, elementSize));
}
}
return result;
}
/// Writes an array of values of type to the target address.
/// if all bytes were written.
public bool Write(IntPtr address, T[] values, bool isRelative = false) where T : struct
{
if (isRelative)
address = GetAbsolute(address);
if (values is null || values.Length == 0)
return true;
int elementSize = MarshalCache.Size;
int totalSize = elementSize * values.Length;
byte[] raw = new byte[totalSize];
Span span = raw;
for (int i = 0; i < values.Length; i++)
{
Span slice = span.Slice(i * elementSize, elementSize);
if (MarshalCache.TypeRequiresMarshal)
{
StructureToByteArray(values[i], slice, elementSize);
}
else
{
MemoryMarshal.Write(slice, in values[i]);
}
}
int written = WriteBytes(address, raw, false);
return written == totalSize;
}
// ── String IO ──────────────────────────────────────────────────────────
/// Reads a null-terminated string from the target address.
public virtual string ReadString(IntPtr address, Encoding encoding, int maxLength = 512, bool relative = false)
{
byte[] buffer = ReadBytes(address, maxLength, relative);
string decoded = encoding.GetString(buffer);
int nullIndex = decoded.IndexOf('\0');
if (nullIndex >= 0)
{
return decoded[..nullIndex];
}
return decoded;
}
/// Writes a null-terminated string to the target address.
public virtual bool WriteString(IntPtr address, string value, Encoding encoding, bool relative = false)
{
// Ensure null terminator
if (value.Length == 0 || value[^1] != '\0')
value += '\0';
byte[] bytes = encoding.GetBytes(value);
int written = WriteBytes(address, bytes, relative);
return written == bytes.Length;
}
// ── Addressing ─────────────────────────────────────────────────────────
/// Converts a relative offset to an absolute address relative to .
public IntPtr GetAbsolute(IntPtr relative)
{
return ImageBase + (int)relative;
}
/// Converts an absolute address to a relative offset from .
public IntPtr GetRelative(IntPtr absolute)
{
return (IntPtr)((int)ImageBase - (int)absolute);
}
// ── Lifecycle ──────────────────────────────────────────────────────────
///
public virtual void Dispose()
{
Handle?.Dispose();
}
// ── Private helpers ────────────────────────────────────────────────────
private static T MarshalByteArrayToStructure(byte[] bytes) where T : struct
{
GCHandle pin = GCHandle.Alloc(bytes, GCHandleType.Pinned);
try
{
return Marshal.PtrToStructure(pin.AddrOfPinnedObject());
}
finally
{
pin.Free();
}
}
private static void StructureToByteArray(T value, Span destination, int size) where T : struct
{
byte[] temp = destination.ToArray();
GCHandle pin = GCHandle.Alloc(temp, GCHandleType.Pinned);
try
{
Marshal.StructureToPtr(value, pin.AddrOfPinnedObject(), false);
temp.CopyTo(destination);
}
finally
{
pin.Free();
}
}
}