Implement core diagnostic memory layer, execution helpers, and high-level facade slices
Implemented: - Core: UTF-16 ReadString boundary/alignment fix, target bitness and process id on MemoryBase - function interception: PatchManager, DetourManager, InstructionAnalyzer, MainThreadDispatcher - Execution: BackgroundTaskExecutor, InProcessInvoker - High-level: Magic facade, RemotePointer, async wrappers - Discovery/external code loading/Window groundwork (PEB/TEB, pattern scanning, raw allocations, DLL external code loading, window/input) Tests: 180 passing, 4 integration/interactive tests skipped.
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using WhiteMagic.Native;
|
||||
|
||||
namespace WhiteMagic.Windows;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper around a native window handle that supports querying and mutating
|
||||
/// common window properties.
|
||||
/// </summary>
|
||||
public sealed class RemoteWindow
|
||||
{
|
||||
private const int MaxTextLength = 512;
|
||||
|
||||
/// <summary>Creates a wrapper for the specified window handle.</summary>
|
||||
public RemoteWindow(IntPtr handle)
|
||||
{
|
||||
if (handle == IntPtr.Zero)
|
||||
throw new ArgumentException("Window handle cannot be zero.", nameof(handle));
|
||||
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
/// <summary>The native window handle.</summary>
|
||||
public IntPtr Handle { get; }
|
||||
|
||||
/// <summary>The window class name.</summary>
|
||||
public string ClassName => GetClassName(Handle);
|
||||
|
||||
/// <summary>The current window text.</summary>
|
||||
public string Text => GetWindowText(Handle);
|
||||
|
||||
/// <summary>The process identifier that owns the window.</summary>
|
||||
public uint ProcessId => GetWindowProcessId(Handle);
|
||||
|
||||
/// <summary>Gets or sets the window title.</summary>
|
||||
public string Title
|
||||
{
|
||||
get => GetWindowText(Handle);
|
||||
set
|
||||
{
|
||||
if (value is null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
|
||||
if (!NativeMethods.SetWindowTextW(Handle, value))
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException(
|
||||
$"SetWindowText failed for window {Handle} with error {error}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary><see langword="true"/> if this window is currently the foreground window.</summary>
|
||||
public bool IsActive => NativeMethods.GetForegroundWindow() == Handle;
|
||||
|
||||
/// <summary>Moves and resizes the window.</summary>
|
||||
public bool MoveResize(int x, int y, int width, int height)
|
||||
{
|
||||
return NativeMethods.SetWindowPos(
|
||||
Handle,
|
||||
NativeMethods.HwndTop,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
NativeMethods.SwpShowWindow);
|
||||
}
|
||||
|
||||
/// <summary>Activates the window and brings it to the foreground.</summary>
|
||||
public bool Activate()
|
||||
{
|
||||
IntPtr foreground = NativeMethods.GetForegroundWindow();
|
||||
uint targetThread = NativeMethods.GetWindowThreadProcessId(Handle, out _);
|
||||
uint foregroundThread = NativeMethods.GetWindowThreadProcessId(foreground, out _);
|
||||
|
||||
if (targetThread == 0)
|
||||
return false;
|
||||
|
||||
if (targetThread == foregroundThread)
|
||||
return NativeMethods.SetForegroundWindow(Handle);
|
||||
|
||||
if (!NativeMethods.AttachThreadInput(foregroundThread, targetThread, true))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
return NativeMethods.SetForegroundWindow(Handle);
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.AttachThreadInput(foregroundThread, targetThread, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Flashes the window in the caption and taskbar button.</summary>
|
||||
public bool Flash()
|
||||
{
|
||||
var info = new FlashWindowInfo
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<FlashWindowInfo>(),
|
||||
hwnd = Handle,
|
||||
dwFlags = NativeMethods.FlashwAll,
|
||||
uCount = 3,
|
||||
dwTimeout = 0,
|
||||
};
|
||||
|
||||
return NativeMethods.FlashWindowEx(ref info);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("RemoteWindow(");
|
||||
sb.Append(Handle.ToString("X"));
|
||||
sb.Append(", ");
|
||||
sb.Append(ClassName);
|
||||
sb.Append(")");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string GetClassName(IntPtr handle)
|
||||
{
|
||||
var buffer = new char[256];
|
||||
int length = NativeMethods.GetClassNameW(handle, buffer, buffer.Length);
|
||||
if (length <= 0)
|
||||
return string.Empty;
|
||||
|
||||
return new string(buffer, 0, length);
|
||||
}
|
||||
|
||||
private static string GetWindowText(IntPtr handle)
|
||||
{
|
||||
var buffer = new char[MaxTextLength];
|
||||
int length = NativeMethods.GetWindowTextW(handle, buffer, buffer.Length);
|
||||
if (length <= 0)
|
||||
return string.Empty;
|
||||
|
||||
return new string(buffer, 0, length);
|
||||
}
|
||||
|
||||
private static uint GetWindowProcessId(IntPtr handle)
|
||||
{
|
||||
NativeMethods.GetWindowThreadProcessId(handle, out uint processId);
|
||||
return processId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using WhiteMagic.Native;
|
||||
|
||||
namespace WhiteMagic.Windows;
|
||||
|
||||
/// <summary>
|
||||
/// Factory for enumerating and locating <see cref="RemoteWindow"/> instances.
|
||||
/// </summary>
|
||||
public static class WindowFactory
|
||||
{
|
||||
/// <summary>Enumerates all top-level windows.</summary>
|
||||
public static unsafe IEnumerable<RemoteWindow> GetWindows()
|
||||
{
|
||||
var handles = new List<IntPtr>();
|
||||
GCHandle gch = GCHandle.Alloc(handles);
|
||||
try
|
||||
{
|
||||
delegate* unmanaged[Stdcall]<IntPtr, IntPtr, int> callback = &EnumWindowsCallback;
|
||||
NativeMethods.EnumWindows((nint)callback, GCHandle.ToIntPtr(gch));
|
||||
}
|
||||
finally
|
||||
{
|
||||
gch.Free();
|
||||
}
|
||||
|
||||
return handles.Select(static h => new RemoteWindow(h));
|
||||
}
|
||||
|
||||
/// <summary>Returns all top-level windows with the specified class name.</summary>
|
||||
public static IEnumerable<RemoteWindow> GetWindowsByClassName(string className)
|
||||
{
|
||||
if (className is null)
|
||||
throw new ArgumentNullException(nameof(className));
|
||||
|
||||
return GetWindows().Where(w => w.ClassName.Equals(className, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <summary>Returns all top-level windows owned by the specified process.</summary>
|
||||
public static IEnumerable<RemoteWindow> GetWindowsByProcessId(int processId)
|
||||
{
|
||||
return GetWindows().Where(w => w.ProcessId == (uint)processId);
|
||||
}
|
||||
|
||||
/// <summary>Returns the first top-level window with the specified class name.</summary>
|
||||
public static RemoteWindow? GetWindowByClassName(string className)
|
||||
{
|
||||
return GetWindowsByClassName(className).FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the main window of a process. When <see cref="Process.MainWindowHandle"/>
|
||||
/// is unavailable, falls back to the first enumerated window owned by the process.
|
||||
/// </summary>
|
||||
public static RemoteWindow? GetMainWindow(System.Diagnostics.Process process)
|
||||
{
|
||||
if (process is null)
|
||||
throw new ArgumentNullException(nameof(process));
|
||||
|
||||
if (process.MainWindowHandle != IntPtr.Zero)
|
||||
return new RemoteWindow(process.MainWindowHandle);
|
||||
|
||||
return GetWindowsByProcessId(process.Id).FirstOrDefault();
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
|
||||
private static int EnumWindowsCallback(IntPtr hWnd, IntPtr lParam)
|
||||
{
|
||||
var handles = (List<IntPtr>)GCHandle.FromIntPtr(lParam).Target!;
|
||||
handles.Add(hWnd);
|
||||
return 1; // Continue enumeration.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user