Fix InputSimulator wParam, PeHeaderParser double-parse, and other review issues

Bug fixes:
- InputSimulator: Pass correct button state (MK_LBUTTON/MK_RBUTTON) in wParam for button-down messages instead of 0.
- PeHeaderParser: ParseOptionalHeader now reads only the optional header, not section headers (fixes double-parse waste).
- EntryPoint: Removed useless isPe32Plus branch (AddressOfEntryPoint is at offset 16 in both PE32 and PE32+).
- RemoteWindow: Handle null foreground window case in Activate to avoid calling GetWindowThreadProcessId with HWND 0.
- RemotePointer: Remove dead null-conditional operators (encoding ??) since encoding is non-nullable.

Constants added:
- SystemMethods: MkLButton (0x0001) and MkRButton (0x0002) for mouse button state flags.

Tests: 199 passing, 4 integration/interactive skipped.
This commit is contained in:
kbe
2026-07-22 01:28:42 +02:00
parent 614806f5f1
commit c40f3fd791
5 changed files with 60 additions and 23 deletions
+42 -16
View File
@@ -64,21 +64,8 @@ public sealed class PeHeaderParser
if (optionalHeader is null)
return IntPtr.Zero;
// Entry point is at different offsets for PE32 vs PE32+
bool isPe32Plus = IsPe32Plus();
if (isPe32Plus)
{
// PE32+: AddressOfEntryPoint is at offset 16 in OPTIONAL_HEADER (64-bit)
return (IntPtr)BitConverter.ToUInt32(
optionalHeader.AsSpan(16, 4));
}
else
{
// PE32: AddressOfEntryPoint is at offset 16 in OPTIONAL_HEADER (32-bit)
return (IntPtr)BitConverter.ToUInt32(
optionalHeader.AsSpan(16, 4));
}
// Entry point RVA is at offset 16 in the optional header (both PE32 and PE32+)
return (IntPtr)BitConverter.ToUInt32(optionalHeader.AsSpan(16, 4));
}
}
@@ -185,7 +172,46 @@ public sealed class PeHeaderParser
/// </summary>
private (byte[]? OptionalHeader, byte[][]? SectionHeaders) ParseOptionalHeader()
{
return ParseOptionalHeaderAndSectionHeaders();
// Read DOS header (first 64 bytes)
byte[] dosHeader = _memory.ReadBytes(_baseAddress, 64);
if (dosHeader.Length < 64)
throw new InvalidDataException("Failed to read DOS header.");
// Verify DOS signature "MZ"
if (dosHeader[0] != 0x4D || dosHeader[1] != 0x5A)
throw new InvalidDataException("Invalid DOS signature (not a PE file).");
// PE header offset is at 0x3C in DOS header
int peOffset = BitConverter.ToInt32(dosHeader, 0x3C);
if (peOffset < 0 || peOffset > 0x1000) // Sanity check
throw new InvalidDataException($"Invalid PE offset: {peOffset}");
// Read PE signature (4 bytes: "PE\0\0")
IntPtr peSigAddr = _baseAddress + peOffset;
byte[] peSignature = _memory.ReadBytes(peSigAddr, 4);
if (peSignature.Length < 4)
throw new InvalidDataException("Failed to read PE signature.");
if (peSignature[0] != 0x50 || peSignature[1] != 0x45 ||
peSignature[2] != 0x00 || peSignature[3] != 0x00)
throw new InvalidDataException("Invalid PE signature.");
// COFF header follows PE signature (20 bytes)
IntPtr coffAddr = peSigAddr + 4;
byte[] coffHeader = _memory.ReadBytes(coffAddr, 20);
if (coffHeader.Length < 20)
throw new InvalidDataException("Failed to read COFF header.");
// SizeOfOptionalHeader is at offset 16 in COFF header
ushort sizeOfOptionalHeader = BitConverter.ToUInt16(coffHeader, 16);
// Optional header follows COFF header
IntPtr optAddr = coffAddr + 20;
byte[] optionalHeader = _memory.ReadBytes(optAddr, sizeOfOptionalHeader);
if (optionalHeader.Length < sizeOfOptionalHeader)
throw new InvalidDataException("Failed to read optional header.");
return (optionalHeader, null);
}
/// <summary>
+5 -4
View File
@@ -53,16 +53,17 @@ public sealed class InputSimulator
nint lParam = MakeLong(x, y);
(uint down, uint up) = button switch
(uint down, uint downWParam) = button switch
{
MouseButton.Left => (NativeMethods.WmLButtonDown, NativeMethods.WmLButtonUp),
MouseButton.Right => (NativeMethods.WmRButtonDown, NativeMethods.WmRButtonUp),
MouseButton.Left => (NativeMethods.WmLButtonDown, NativeMethods.MkLButton),
MouseButton.Right => (NativeMethods.WmRButtonDown, NativeMethods.MkRButton),
_ => throw new ArgumentOutOfRangeException(nameof(button)),
};
if (!NativeMethods.PostMessageW(hWnd, down, 0, lParam))
if (!NativeMethods.PostMessageW(hWnd, down, downWParam, lParam))
return false;
uint up = button == MouseButton.Left ? NativeMethods.WmLButtonUp : NativeMethods.WmRButtonUp;
return NativeMethods.PostMessageW(hWnd, up, 0, lParam);
}
+4
View File
@@ -114,6 +114,10 @@ internal static partial class NativeMethods
internal const uint WmRButtonDown = 0x0204;
internal const uint WmRButtonUp = 0x0205;
// Mouse button state flags for WM_*BUTTONDOWN messages
internal const uint MkLButton = 0x0001;
internal const uint MkRButton = 0x0002;
internal static readonly IntPtr HwndTop = IntPtr.Zero;
internal const uint SwpShowWindow = 0x0040;
+3 -2
View File
@@ -35,15 +35,16 @@ public sealed class RemotePointer
/// <summary>Reads a null-terminated string at <c>BaseAddress + offset</c>.</summary>
public string ReadString(Encoding encoding, int maxLength = 512, nint offset = 0)
{
return _memory.ReadString(BaseAddress + offset, encoding ?? Encoding.UTF8, maxLength);
return _memory.ReadString(BaseAddress + offset, encoding, maxLength);
}
/// <summary>Writes a null-terminated string at <c>BaseAddress + offset</c>.</summary>
public bool WriteString(string value, Encoding encoding, nint offset = 0)
{
return _memory.WriteString(BaseAddress + offset, value, encoding ?? Encoding.UTF8);
return _memory.WriteString(BaseAddress + offset, value, encoding);
}
/// <summary>Returns a new <see cref="RemotePointer"/> with the offset added.</summary>
public RemotePointer this[nint offset] => new RemotePointer(_memory, BaseAddress + offset);
}
+6 -1
View File
@@ -72,11 +72,16 @@ public sealed class RemoteWindow
{
IntPtr foreground = NativeMethods.GetForegroundWindow();
uint targetThread = NativeMethods.GetWindowThreadProcessId(Handle, out _);
uint foregroundThread = NativeMethods.GetWindowThreadProcessId(foreground, out _);
if (targetThread == 0)
return false;
// If there's no foreground window, or we're already in the foreground thread, just set it
if (foreground == IntPtr.Zero)
return NativeMethods.SetForegroundWindow(Handle);
uint foregroundThread = NativeMethods.GetWindowThreadProcessId(foreground, out _);
if (targetThread == foregroundThread)
return NativeMethods.SetForegroundWindow(Handle);