Add process discovery helpers and Magic facade accessors
Introduces ApplicationFinder (by name/title/handle), Magic.Open overloads, and Magic.Threads/Regions/QueryRegion accessors. Closes section 3 of add-thread-region-finder and updates tasks/comparison doc.
This commit is contained in:
+48
-1
@@ -1,7 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Process = System.Diagnostics.Process;
|
||||
using WhiteMagic.Execution;
|
||||
using WhiteMagic.Hooking;
|
||||
using WhiteMagic.Memory;
|
||||
using WhiteMagic.ProcessDiscovery;
|
||||
using WhiteMagic.Thread;
|
||||
using WhiteMagic.Windows;
|
||||
|
||||
namespace WhiteMagic;
|
||||
|
||||
@@ -24,6 +29,21 @@ public sealed class Magic : IDisposable
|
||||
/// <summary>Inline-detour manager (in-process only).</summary>
|
||||
public DetourManager DetourManager => Memory.DetourManager;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the memory region that contains <paramref name="address"/>.
|
||||
/// </summary>
|
||||
public MemoryRegion QueryRegion(IntPtr address) => Memory.QueryRegion(address);
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the committed and reserved regions of the target process address space.
|
||||
/// </summary>
|
||||
public IEnumerable<MemoryRegion> Regions => Memory.EnumerateRegions();
|
||||
|
||||
/// <summary>
|
||||
/// Factory for discovering and operating on the target process's threads.
|
||||
/// </summary>
|
||||
public ThreadFactory Threads => new ThreadFactory(Memory);
|
||||
|
||||
private Magic(MemoryBase memory)
|
||||
{
|
||||
Memory = memory;
|
||||
@@ -31,11 +51,38 @@ public sealed class Magic : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>Opens an external process for reading, writing, and execution.</summary>
|
||||
public static Magic Open(System.Diagnostics.Process process)
|
||||
public static Magic Open(Process process)
|
||||
{
|
||||
return new Magic(new ExternalReader(process));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a target process by its image name. Throws if zero or more than one match.
|
||||
/// </summary>
|
||||
public static Magic Open(string processName)
|
||||
{
|
||||
using Process process = ApplicationFinder.OpenProcess(processName);
|
||||
return Open(process);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the process that owns the top-level window with the specified title.
|
||||
/// </summary>
|
||||
public static Magic OpenByWindowTitle(string title)
|
||||
{
|
||||
using Process process = ApplicationFinder.OpenByWindowTitle(title);
|
||||
return Open(process);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the process that owns the specified window handle.
|
||||
/// </summary>
|
||||
public static Magic OpenByWindowHandle(IntPtr handle)
|
||||
{
|
||||
using Process process = ApplicationFinder.OpenByWindowHandle(handle);
|
||||
return Open(process);
|
||||
}
|
||||
|
||||
/// <summary>Creates an in-process session for the current process.</summary>
|
||||
public static Magic OpenInProcess()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using WhiteMagic.Native;
|
||||
using WhiteMagic.Windows;
|
||||
|
||||
namespace WhiteMagic.ProcessDiscovery;
|
||||
|
||||
/// <summary>
|
||||
/// Discovers running processes by name, window title, or window handle so they can be
|
||||
/// attached through a <see cref="Magic"/> session.
|
||||
/// </summary>
|
||||
public static class ApplicationFinder
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerates processes whose image name matches <paramref name="processName"/>
|
||||
/// (extension optional).
|
||||
/// </summary>
|
||||
public static IEnumerable<Process> Enumerate(string processName)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(processName);
|
||||
|
||||
return Process.GetProcessesByName(GetNameWithoutExtension(processName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the unique process whose image name matches <paramref name="processName"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Zero or multiple processes match.</exception>
|
||||
public static Process OpenProcess(string processName)
|
||||
{
|
||||
Process[] candidates = Enumerate(processName).ToArray();
|
||||
|
||||
if (candidates.Length == 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"No process named '{processName}' was found.");
|
||||
}
|
||||
|
||||
if (candidates.Length > 1)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Process name '{processName}' is ambiguous ({candidates.Length} matches): " +
|
||||
string.Join(", ", candidates.Select(p => $"{p.ProcessName}:{p.Id}")));
|
||||
}
|
||||
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates processes that own a top-level window whose title equals
|
||||
/// <paramref name="title"/>.
|
||||
/// </summary>
|
||||
public static IEnumerable<Process> FindByWindowTitle(string title)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(title);
|
||||
|
||||
var seen = new HashSet<int>();
|
||||
foreach (RemoteWindow window in WindowFactory.GetWindows())
|
||||
{
|
||||
if (!string.Equals(window.Text, title, StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
uint pid = window.ProcessId;
|
||||
if (pid == 0 || !seen.Add((int)pid))
|
||||
continue;
|
||||
|
||||
Process? process;
|
||||
try
|
||||
{
|
||||
process = global::System.Diagnostics.Process.GetProcessById((int)pid);
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return process;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the unique process that owns a top-level window titled <paramref name="title"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Zero or multiple windows match.</exception>
|
||||
public static Process OpenByWindowTitle(string title)
|
||||
{
|
||||
Process[] candidates = FindByWindowTitle(title).ToArray();
|
||||
|
||||
if (candidates.Length == 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"No top-level window titled '{title}' was found.");
|
||||
}
|
||||
|
||||
if (candidates.Length > 1)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Window title '{title}' is ambiguous ({candidates.Length} matches): " +
|
||||
string.Join(", ", candidates.Select(p => $"{p.ProcessName}:{p.Id}")));
|
||||
}
|
||||
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the process that owns the specified window handle.
|
||||
/// </summary>
|
||||
public static Process OpenByWindowHandle(IntPtr handle)
|
||||
{
|
||||
if (handle == IntPtr.Zero)
|
||||
throw new ArgumentException("Window handle cannot be zero.", nameof(handle));
|
||||
|
||||
uint tid = NativeMethods.GetWindowThreadProcessId(handle, out uint processId);
|
||||
if (tid == 0 || processId == 0)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException(
|
||||
$"GetWindowThreadProcessId failed for handle {handle:X}: error {error}.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return global::System.Diagnostics.Process.GetProcessById((int)processId);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Process {processId} owning window {handle:X} is no longer running.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetNameWithoutExtension(string name)
|
||||
{
|
||||
if (name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
|
||||
return name[..^4];
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user