fix 7 correctness and cleanup issues

Blocking fixes:
1. Read<T> now returns default(T) on failed/partial read instead of crash
   (applies to single Read<T>, array Read<T>, and ReadBytes)
2. InProcessReader uses ReadProcessMemory via handle instead of unsafe
   Buffer.MemoryCopy — fails soft on bad address instead of AV'ing
3. GetRelative now returns absolute - ImageBase (inverse of GetAbsolute).
   Fix round-trip test to validate at arbitrary offsets, not just ImageBase
4. ReadString reads in 64-byte chunks with encoding-aware null-terminator
   pattern matching (handles UTF-16's 2-byte null, UTF-32's 4-byte null)

Cleanup:
5. InProcessReader validates handle on open and uses RPM through it
   (handle is no longer unused)
6. Array marshal read: pin raw buffer once, PtrToStructure at offset
7. StructureToByteArray(Span) delegates to byte[] overload, no duplicate

New tests: 4 invalid-address grace tests (returns default/empty/false).
All 57 passing.
This commit is contained in:
kbe
2026-07-21 19:24:18 +02:00
parent 6eb78e7974
commit 8374650aac
4 changed files with 182 additions and 63 deletions
+34 -8
View File
@@ -1,8 +1,8 @@
using WhiteMagic.Native;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using WhiteMagic;
using WhiteMagic.Native;
namespace WhiteMagicTest;
@@ -31,7 +31,6 @@ public class MemoryBaseTests
{
using var reader = OpenSelf();
// Pin a local int to use as our "remote" address
int slot = 0;
GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned);
try
@@ -91,10 +90,7 @@ public class MemoryBaseTests
try
{
IntPtr addr = pin.AddrOfPinnedObject();
// Write a new value
Assert.True(reader.Write(addr, new TestStruct { X = 100, Y = 200 }));
var result = reader.Read<TestStruct>(addr);
Assert.Equal(100, result.X);
Assert.Equal(200, result.Y);
@@ -140,7 +136,6 @@ public class MemoryBaseTests
int[] expected = [10, 20, 30, 40];
Assert.True(reader.Write(addr, expected));
int[] actual = reader.Read<int>(addr, 4);
Assert.Equal(expected, actual);
}
@@ -168,7 +163,6 @@ public class MemoryBaseTests
};
Assert.True(reader.Write(addr, expected));
var actual = reader.Read<TestStruct>(addr, 4);
Assert.Equal(expected, actual);
}
@@ -199,7 +193,39 @@ public class MemoryBaseTests
{
var reader = OpenSelf();
reader.Dispose();
reader.Dispose(); // Should not throw
reader.Dispose();
}
// ── Graceful failure on invalid addresses ───────────────────────────────
[Fact]
public void Read_int_on_invalid_address_returns_default()
{
using var reader = OpenSelf();
Assert.Equal(0, reader.Read<int>(IntPtr.Zero));
}
[Fact]
public void Read_struct_on_invalid_address_returns_default()
{
using var reader = OpenSelf();
var result = reader.Read<TestStruct>(IntPtr.Zero);
Assert.Equal(0, result.X);
Assert.Equal(0, result.Y);
}
[Fact]
public void Read_int_array_on_invalid_address_returns_empty()
{
using var reader = OpenSelf();
Assert.Empty(reader.Read<int>(IntPtr.Zero, 10));
}
[Fact]
public void Read_bytes_on_invalid_address_returns_empty()
{
using var reader = OpenSelf();
Assert.Empty(reader.ReadBytes(IntPtr.Zero, 10));
}
}