using System.Runtime.InteropServices; using WhiteMagic.Native; namespace WhiteMagicTest.Native; /// /// Integration tests that exercise the P/Invoke surface against the current /// process. They prove the marshalling signatures are correct end-to-end. /// public class NativeSurfaceTests { private static SafeMemoryHandle OpenSelf(ProcessAccess access) { SafeMemoryHandle handle = NativeMethods.OpenProcess(access, false, Environment.ProcessId); Assert.False(handle.IsInvalid, $"OpenProcess failed: {Marshal.GetLastPInvokeError()}"); return handle; } [Fact] public void OpenProcess_on_self_returns_valid_handle_and_closes_on_dispose() { SafeMemoryHandle handle = OpenSelf(ProcessAccess.QueryInformation); Assert.False(handle.IsClosed); handle.Dispose(); Assert.True(handle.IsClosed); } [Fact] public void ReadProcessMemory_reads_a_known_value_from_own_memory() { int value = 0x1BADB002; GCHandle pin = GCHandle.Alloc(value, GCHandleType.Pinned); try { using SafeMemoryHandle handle = OpenSelf(ProcessAccess.VmRead | ProcessAccess.QueryInformation); Span buffer = stackalloc byte[sizeof(int)]; bool ok = NativeMethods.ReadProcessMemory( handle, pin.AddrOfPinnedObject(), buffer, buffer.Length, out nint read); Assert.True(ok, $"ReadProcessMemory failed: {Marshal.GetLastPInvokeError()}"); Assert.Equal(sizeof(int), (int)read); Assert.Equal(value, BitConverter.ToInt32(buffer)); } finally { pin.Free(); } } [Fact] public void WriteProcessMemory_writes_a_value_into_own_memory() { int slot = 0; GCHandle pin = GCHandle.Alloc(slot, GCHandleType.Pinned); try { using SafeMemoryHandle handle = OpenSelf( ProcessAccess.VmWrite | ProcessAccess.VmOperation | ProcessAccess.QueryInformation); ReadOnlySpan bytes = BitConverter.GetBytes(0x5EED); bool ok = NativeMethods.WriteProcessMemory( handle, pin.AddrOfPinnedObject(), bytes, bytes.Length, out nint written); Assert.True(ok, $"WriteProcessMemory failed: {Marshal.GetLastPInvokeError()}"); Assert.Equal(bytes.Length, (int)written); Assert.Equal(0x5EED, Marshal.ReadInt32(pin.AddrOfPinnedObject())); } finally { pin.Free(); } } [Fact] public void VirtualAllocEx_commits_then_protects_then_frees() { using SafeMemoryHandle handle = OpenSelf(ProcessAccess.VmOperation | ProcessAccess.QueryInformation); IntPtr region = NativeMethods.VirtualAllocEx( handle, IntPtr.Zero, 0x1000, MemoryAllocationType.Commit | MemoryAllocationType.Reserve, MemoryProtectionType.ReadWrite); Assert.NotEqual(IntPtr.Zero, region); bool protect = NativeMethods.VirtualProtectEx( handle, region, 0x1000, MemoryProtectionType.ExecuteReadWrite, out MemoryProtectionType old); Assert.True(protect, $"VirtualProtectEx failed: {Marshal.GetLastPInvokeError()}"); Assert.Equal(MemoryProtectionType.ReadWrite, old); bool free = NativeMethods.VirtualFreeEx(handle, region, 0, MemoryFreeType.Release); Assert.True(free, $"VirtualFreeEx failed: {Marshal.GetLastPInvokeError()}"); } [Fact] public void LoadLibrary_then_GetProcAddress_resolves_an_export() { IntPtr module = NativeMethods.LoadLibrary("kernel32.dll"); Assert.NotEqual(IntPtr.Zero, module); IntPtr proc = NativeMethods.GetProcAddress(module, "CloseHandle"); Assert.NotEqual(IntPtr.Zero, proc); } [Theory] [InlineData(typeof(Context32), 716)] [InlineData(typeof(Context64), 1232)] public void Thread_context_struct_has_the_exact_native_size(Type contextType, int expectedSize) { Assert.Equal(expectedSize, Marshal.SizeOf(contextType)); } }