diff --git a/WhiteMagic/MemoryBase.cs b/WhiteMagic/MemoryBase.cs index 638d1cf..931e8da 100644 --- a/WhiteMagic/MemoryBase.cs +++ b/WhiteMagic/MemoryBase.cs @@ -36,16 +36,14 @@ public abstract class MemoryBase : IDisposable 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); + return MemoryMarshal.Read(raw.AsSpan()); } /// Writes a value of type to the target address. @@ -56,18 +54,19 @@ public abstract class MemoryBase : IDisposable address = GetAbsolute(address); int size = MarshalCache.Size; - Span buffer = stackalloc byte[size]; + byte[] raw; if (MarshalCache.TypeRequiresMarshal) { - StructureToByteArray(value, buffer, size); + raw = StructureToByteArray(value, size); } else { - MemoryMarshal.Write(buffer, in value); + raw = new byte[size]; + MemoryMarshal.Write(raw.AsSpan(), in value); } - int written = WriteBytes(address, buffer, false); + int written = WriteBytes(address, raw, false); return written == size; } @@ -153,7 +152,6 @@ public abstract class MemoryBase : IDisposable /// 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'; @@ -167,13 +165,13 @@ public abstract class MemoryBase : IDisposable /// Converts a relative offset to an absolute address relative to . public IntPtr GetAbsolute(IntPtr relative) { - return ImageBase + (int)relative; + return ImageBase + (nint)relative; } /// Converts an absolute address to a relative offset from . public IntPtr GetRelative(IntPtr absolute) { - return (IntPtr)((int)ImageBase - (int)absolute); + return (IntPtr)((nint)ImageBase - (nint)absolute); } // ── Lifecycle ────────────────────────────────────────────────────────── @@ -199,6 +197,21 @@ public abstract class MemoryBase : IDisposable } } + private static byte[] StructureToByteArray(T value, int size) where T : struct + { + byte[] bytes = new byte[size]; + GCHandle pin = GCHandle.Alloc(bytes, GCHandleType.Pinned); + try + { + Marshal.StructureToPtr(value, pin.AddrOfPinnedObject(), false); + } + finally + { + pin.Free(); + } + return bytes; + } + private static void StructureToByteArray(T value, Span destination, int size) where T : struct { byte[] temp = destination.ToArray(); diff --git a/WhiteMagicTest/AddressingTests.cs b/WhiteMagicTest/AddressingTests.cs new file mode 100644 index 0000000..3245817 --- /dev/null +++ b/WhiteMagicTest/AddressingTests.cs @@ -0,0 +1,89 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using WhiteMagic; +using WhiteMagic.Native; + +namespace WhiteMagicTest; + +/// +/// Tests for relative/absolute addressing in . +/// +public class AddressingTests +{ + private static ExternalReader OpenSelf() + { + return new ExternalReader( + Process.GetCurrentProcess(), + ProcessAccess.VmRead | ProcessAccess.VmWrite | ProcessAccess.VmOperation | ProcessAccess.QueryInformation); + } + + [Fact] + public void GetAbsolute_resolves_relative_offset() + { + using var reader = OpenSelf(); + IntPtr imageBase = reader.ImageBase; + IntPtr result = reader.GetAbsolute((IntPtr)0x1000); + Assert.Equal(imageBase + 0x1000, result); + } + + [Fact] + public void GetRelative_computes_offset_from_image_base() + { + using var reader = OpenSelf(); + IntPtr imageBase = reader.ImageBase; + IntPtr absolute = imageBase + 0x2000; + IntPtr relative = reader.GetRelative(absolute); + // GetRelative returns ImageBase - absolute (GreyMagic convention) + Assert.Equal((IntPtr)((int)imageBase - (int)absolute), relative); + } + + [Fact] + public void GetAbsolute_after_GetRelative_at_image_base_returns_to_base() + { + using var reader = OpenSelf(); + IntPtr atBase = reader.ImageBase; + IntPtr relative = reader.GetRelative(atBase); + IntPtr back = reader.GetAbsolute(relative); + Assert.Equal(atBase, back); + } + + [Fact] + public void Read_with_isRelative_true_uses_image_base() + { + using var reader = OpenSelf(); + // Read the first byte at ImageBase (should be MZ header: 0x4D = 'M') + byte firstByte = reader.Read(IntPtr.Zero, isRelative: true); + Assert.Equal(0x4D, firstByte); + } + + [Fact] + public void Write_with_isRelative_true_resolves_correctly() + { + using var reader = OpenSelf(); + int slot = 0; + GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned); + try + { + IntPtr absolute = pin.AddrOfPinnedObject(); + nint relative = (nint)absolute - (nint)reader.ImageBase; + IntPtr relativePtr = (IntPtr)relative; + + Assert.True(reader.Write(relativePtr, 42, isRelative: true)); + Assert.Equal(42, reader.Read(absolute)); + } + finally + { + pin.Free(); + } + } + + [Fact] + public void ReadBytes_with_isRelative_true_resolves_correctly() + { + using var reader = OpenSelf(); + // DOS header 'MZ' at the image base + byte[] data = reader.ReadBytes(IntPtr.Zero, 2, isRelative: true); + Assert.Equal(0x4D, data[0]); + Assert.Equal(0x5A, data[1]); + } +}