- Example5: Fix format string bugs (alignment specifier placement, SafeMemoryHandle formatting) - Example4: Fix DetourManager.Detour() → Create() in all doc strings - Example3: Fix MemoryBase.CreateFunction() → InProcessInvoker.CreateFunction() in doc strings - Examples 1-5: Correct all runtime errors and API mismatches vs actual WhiteMagic API - README: Fix DetourManager.Detour() examples to use Create(); add missing code fence markers - Add WhiteMagic.Examples project with 5 comprehensive example files (40+ sub-examples) - Add docfx.json and toc.md for DocFX API reference generation - Add 5 conceptual guides: architecture, memory-access, execution-models, hooking, troubleshooting - Ensure zero errors, zero warnings across all projects (net8.0-windows) Doc strings now teach correct APIs; runtime format bugs eliminated; build succeeds.
402 lines
19 KiB
C#
402 lines
19 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading.Tasks;
|
|
using WhiteMagic;
|
|
using WhiteMagic.Assembly;
|
|
using WhiteMagic.Execution;
|
|
namespace WhiteMagic.Examples;
|
|
|
|
/// <summary>
|
|
/// Example 3: Execution Models
|
|
/// Demonstrates the three execution strategies: RemoteThreadExecutor, MainThreadPump, and InProcessInvoker
|
|
/// </summary>
|
|
public class ExecutionModels
|
|
{
|
|
/// <summary>
|
|
/// Target process for examples
|
|
/// </summary>
|
|
private static Process? TargetProcess()
|
|
{
|
|
var processes = Process.GetProcessesByName("notepad");
|
|
if (processes.Length > 0)
|
|
return processes[0];
|
|
|
|
Console.WriteLine("No Notepad process found. Please launch Notepad first.");
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 3.1: RemoteThreadExecutor (CreateRemoteThread)
|
|
/// Use for: Thread-agnostic payloads (WinAPI calls, DLL injection, self-contained code)
|
|
/// </summary>
|
|
public static void RemoteThreadExecutorExample()
|
|
{
|
|
Console.WriteLine("=== Example 3.1: RemoteThreadExecutor (CreateRemoteThread) ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
try
|
|
{
|
|
// Example: Call GetTickCount (thread-safe WinAPI function)
|
|
var getTickCount = magic["kernel32.dll"]["GetTickCount"];
|
|
if (getTickCount.Address != IntPtr.Zero)
|
|
{
|
|
Console.WriteLine($"Calling GetTickCount via RemoteThreadExecutor...");
|
|
uint ticks = getTickCount.Execute<uint>(CallConvention.Stdcall);
|
|
Console.WriteLine($"✓ Result: {ticks} ticks ({TimeSpan.FromMilliseconds(ticks):hh\\:mm\\:ss})");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("✗ GetTickCount not found in kernel32.dll");
|
|
}
|
|
|
|
// Example: Call GetCurrentProcessId
|
|
var getCurrentProcessId = magic["kernel32.dll"]["GetCurrentProcessId"];
|
|
if (getCurrentProcessId.Address != IntPtr.Zero)
|
|
{
|
|
Console.WriteLine($"Calling GetCurrentProcessId...");
|
|
uint processId = getCurrentProcessId.Execute<uint>(CallConvention.Stdcall);
|
|
Console.WriteLine($"✓ Result: Process ID = {processId}");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("✗ GetCurrentProcessId not found in kernel32.dll");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"✗ Execution failed: {ex.Message}");
|
|
}
|
|
|
|
Console.WriteLine("\n⚠️ NOTE: RemoteThreadExecutor is ONLY for thread-agnostic functions!");
|
|
Console.WriteLine(" Do NOT use for game state, scripting engines, or render operations.");
|
|
Console.WriteLine(" Use MainThreadPump for state-sensitive calls instead.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 3.2: MainThreadPump (Crash-Safe Execution)
|
|
/// Use for: State-sensitive calls (game state, scripting, UI, render operations)
|
|
/// </summary>
|
|
public static async Task MainThreadPumpExample()
|
|
{
|
|
Console.WriteLine("\n=== Example 3.2: MainThreadPump (Crash-Safe Execution) ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
try
|
|
{
|
|
// Find a per-frame function (e.g., D3D EndScene, or any function called every frame)
|
|
// For this example, we'll use a hypothetical frame function address
|
|
// In real use, you'd find the actual frame function via pattern scanning
|
|
IntPtr frameFunctionAddress = FindFrameFunction(magic);
|
|
|
|
if (frameFunctionAddress == IntPtr.Zero)
|
|
{
|
|
Console.WriteLine("✗ Could not find frame function (expected in this demo environment)");
|
|
Console.WriteLine(" In a real game, you'd use pattern scanning to find the frame function.");
|
|
return;
|
|
}
|
|
|
|
Console.WriteLine($"Found frame function at: 0x{frameFunctionAddress:X}");
|
|
|
|
// Create MainThreadPump
|
|
Console.WriteLine("Creating MainThreadPump...");
|
|
var pump = magic.CreateMainThreadPump(frameFunctionAddress);
|
|
|
|
// Example 1: Safe memory read from game state
|
|
Console.WriteLine("Enqueueing safe memory read...");
|
|
try
|
|
{
|
|
int value = await pump.ExecuteAsync(() =>
|
|
{
|
|
// Safe to read game state here (running on target's main thread)
|
|
return magic.Memory.Read<int>(magic.Memory.ImageBase + 0x1000);
|
|
});
|
|
|
|
Console.WriteLine($"✓ Safe read result: {value}");
|
|
}
|
|
catch (TimeoutException)
|
|
{
|
|
Console.WriteLine("✗ Pump operation timed out (work item wedged or target not running)");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"✗ Pump operation failed: {ex.Message}");
|
|
}
|
|
|
|
// Example 2: Safe memory write to game state
|
|
Console.WriteLine("Enqueueing safe memory write...");
|
|
try
|
|
{
|
|
await pump.ExecuteAsync(() =>
|
|
{
|
|
// Safe to write game state here
|
|
magic.Memory.Write(magic.Memory.ImageBase + 0x2000, 42);
|
|
return true;
|
|
});
|
|
Console.WriteLine("✓ Safe write completed");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"✗ Safe write failed: {ex.Message}");
|
|
}
|
|
|
|
// Example 3: Complex state manipulation
|
|
Console.WriteLine("Enqueueing complex state manipulation...");
|
|
try
|
|
{
|
|
string result = await pump.ExecuteAsync(() =>
|
|
{
|
|
// Safe to perform complex multi-step operations
|
|
IntPtr basePtr = magic.Memory.Read<IntPtr>(magic.Memory.ImageBase + 0x3000);
|
|
if (basePtr != IntPtr.Zero)
|
|
{
|
|
int health = magic.Memory.Read<int>(basePtr + 0x10);
|
|
int maxHealth = magic.Memory.Read<int>(basePtr + 0x14);
|
|
return $"Health: {health}/{maxHealth}";
|
|
}
|
|
return "Unknown";
|
|
});
|
|
Console.WriteLine($"✓ Complex operation result: {result}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"✗ Complex operation failed: {ex.Message}");
|
|
}
|
|
|
|
Console.WriteLine("\n✓ MainThreadPump examples completed");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"✗ MainThreadPump setup failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper: Find a per-frame function
|
|
/// In real use, you'd use pattern scanning to find D3D EndScene or similar
|
|
/// </summary>
|
|
private static IntPtr FindFrameFunction(Magic magic)
|
|
{
|
|
// For demo purposes, return zero (not found)
|
|
// In real use, you'd scan for patterns like:
|
|
// - D3D9 EndScene: device + 0x44 vtable entry
|
|
// - D3D11 Present callbacks
|
|
// - Game-specific Update/Render functions
|
|
|
|
// Example pattern scan (commented out for demo):
|
|
// var module = magic["d3d9.dll"];
|
|
// if (module != null)
|
|
// {
|
|
// IntPtr endScene = magic.Memory.FindPattern("?? ?? ?? ??", module.BaseAddress, module.ModuleMemorySize);
|
|
// return endScene;
|
|
// }
|
|
|
|
return IntPtr.Zero;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 3.3: InProcessInvoker (Direct Delegates)
|
|
/// Use for: In-process calls after DLL injection (max performance)
|
|
/// </summary>
|
|
public static void InProcessInvokerExample()
|
|
{
|
|
Console.WriteLine("\n=== Example 3.3: InProcessInvoker (Direct Delegates) ===");
|
|
|
|
Console.WriteLine("⚠️ NOTE: This example only works when injected in-process!");
|
|
Console.WriteLine(" For demo purposes, we'll show the syntax but it won't execute.");
|
|
|
|
// Example syntax (would work if injected):
|
|
Console.WriteLine("\nExample code (requires in-process injection):");
|
|
Console.WriteLine("```csharp");
|
|
Console.WriteLine("// Only works when injected in-process");
|
|
Console.WriteLine("using var magic = Magic.OpenInProcess();");
|
|
Console.WriteLine();
|
|
Console.WriteLine("// Define delegate signature");
|
|
Console.WriteLine("[UnmanagedFunctionPointer(CallingConvention.Cdecl)]");
|
|
Console.WriteLine("public delegate int AddNumbersDelegate(int a, int b);");
|
|
Console.WriteLine();
|
|
Console.WriteLine("// Create delegate from function address");
|
|
Console.WriteLine("var addNumbers = inProcess.CreateFunction<AddNumbersDelegate>(functionAddress);");
|
|
Console.WriteLine();
|
|
Console.WriteLine("// Call directly as a delegate (max performance, <1μs)");
|
|
Console.WriteLine("int result = addNumbers(10, 20);");
|
|
Console.WriteLine("Console.WriteLine($\"Result: {result}\");");
|
|
Console.WriteLine("```");
|
|
|
|
Console.WriteLine("\n✓ InProcessInvoker is the fastest but requires injection.");
|
|
Console.WriteLine(" Use RemoteThreadExecutor for initial injection, then switch to InProcessInvoker.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 3.4: Choosing the Right Execution Model
|
|
/// </summary>
|
|
public static void ChooseExecutionModel()
|
|
{
|
|
Console.WriteLine("\n=== Example 3.4: Choosing the Right Execution Model ===");
|
|
|
|
Console.WriteLine("Decision Flowchart:");
|
|
Console.WriteLine("─────────────────────────────────────────────────────────────────");
|
|
Console.WriteLine();
|
|
Console.WriteLine("Are you injected in-process?");
|
|
Console.WriteLine("├─ Yes → Use InProcessInvoker (direct delegates, <1μs)");
|
|
Console.WriteLine("└─ No → Does the call touch single-threaded state?");
|
|
Console.WriteLine(" ├─ Yes → Use MainThreadPump (crash-safe, ~1 frame latency)");
|
|
Console.WriteLine(" └─ No → Use RemoteThreadExecutor (CreateRemoteThread, ~1-2ms)");
|
|
Console.WriteLine();
|
|
Console.WriteLine("Examples by Use Case:");
|
|
Console.WriteLine("─────────────────────────────────────────────────────────────────");
|
|
Console.WriteLine();
|
|
Console.WriteLine("1. DLL Injection: RemoteThreadExecutor");
|
|
Console.WriteLine(" → LoadLibrary is thread-safe");
|
|
Console.WriteLine();
|
|
Console.WriteLine("2. Read Health Bar: MainThreadPump");
|
|
Console.WriteLine(" → Game state is main-thread-affine");
|
|
Console.WriteLine();
|
|
Console.WriteLine("3. Call GetTickCount: RemoteThreadExecutor");
|
|
Console.WriteLine(" → WinAPI, no thread affinity");
|
|
Console.WriteLine();
|
|
Console.WriteLine("4. Script Engine Call: MainThreadPump");
|
|
Console.WriteLine(" → Script VM is main-thread-only");
|
|
Console.WriteLine();
|
|
Console.WriteLine("5. Injected Profiling: InProcessInvoker");
|
|
Console.WriteLine(" → Already in-process, max performance");
|
|
Console.WriteLine();
|
|
Console.WriteLine("6. Window Mutation: RemoteThreadExecutor");
|
|
Console.WriteLine(" → WinAPI SetWindowPos is thread-safe");
|
|
Console.WriteLine();
|
|
Console.WriteLine("Performance Comparison:");
|
|
Console.WriteLine("─────────────────────────────────────────────────────────────────");
|
|
Console.WriteLine();
|
|
Console.WriteLine("┌─────────────────────┬──────────────┬──────────┬──────────────┐");
|
|
Console.WriteLine("│ Model │ Latency │ Safety │ Use Case │");
|
|
Console.WriteLine("├─────────────────────┼──────────────┼──────────┼──────────────┤");
|
|
Console.WriteLine("│ RemoteThreadExec │ ~1-2ms │ Thread- │ One-shot, │");
|
|
Console.WriteLine("│ │ │ agnostic │ DLL inject │");
|
|
Console.WriteLine("├─────────────────────┼──────────────┼──────────┼──────────────┤");
|
|
Console.WriteLine("│ MainThreadPump │ ~1 frame │ Crash- │ State- │");
|
|
Console.WriteLine("│ │ (16-33ms) │ safe │ sensitive │");
|
|
Console.WriteLine("├─────────────────────┼──────────────┼──────────┼──────────────┤");
|
|
Console.WriteLine("│ InProcessInvoker │ <1μs │ In- │ In-process │");
|
|
Console.WriteLine("│ │ │ process │ tools │");
|
|
Console.WriteLine("└─────────────────────┴──────────────┴──────────┴──────────────┘");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 3.5: Combining Execution Models
|
|
/// </summary>
|
|
public static void CombinedExecutionModels()
|
|
{
|
|
Console.WriteLine("\n=== Example 3.5: Combining Execution Models ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
Console.WriteLine("Strategy: Inject DLL, then switch to InProcessInvoker");
|
|
Console.WriteLine();
|
|
Console.WriteLine("Step 1: Use RemoteThreadExecutor to inject DLL");
|
|
Console.WriteLine("```csharp");
|
|
Console.WriteLine("var loadLibrary = magic[\"kernel32.dll\"][\"LoadLibraryA\"];");
|
|
Console.WriteLine("IntPtr dllHandle = loadLibrary.Execute<IntPtr>(CallConvention.Stdcall, dllPathPtr);");
|
|
Console.WriteLine("```");
|
|
Console.WriteLine();
|
|
Console.WriteLine("Step 2: Injected DLL initializes, now in-process");
|
|
Console.WriteLine("```csharp");
|
|
Console.WriteLine("// Inside injected DLL");
|
|
Console.WriteLine("using var inProcess = Magic.OpenInProcess();");
|
|
Console.WriteLine();
|
|
Console.WriteLine("// Use InProcessInvoker for max performance");
|
|
Console.WriteLine("var fn = inProcess.CreateFunction<MyDelegate>(address);");
|
|
Console.WriteLine("int result = fn(arg1, arg2); // <1μs latency");
|
|
Console.WriteLine("```");
|
|
Console.WriteLine();
|
|
Console.WriteLine("✓ DLL injection → Fast in-process calls");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 3.6: Error Handling in Execution Models
|
|
/// </summary>
|
|
public static void ExecutionModelErrorHandling()
|
|
{
|
|
Console.WriteLine("\n=== Example 3.6: Error Handling ===");
|
|
|
|
Console.WriteLine("RemoteThreadExecutor:");
|
|
Console.WriteLine("```csharp");
|
|
Console.WriteLine("try");
|
|
Console.WriteLine("{");
|
|
Console.WriteLine(" uint result = fn.Execute<uint>(CallConvention.Stdcall, args...);");
|
|
Console.WriteLine("}");
|
|
Console.WriteLine("catch (Win32Exception ex)");
|
|
Console.WriteLine("{");
|
|
Console.WriteLine(" // CreateRemoteThread failed (access denied, process exited, etc.)");
|
|
Console.WriteLine(" Console.WriteLine($\"Execution failed: {ex.Message}\");");
|
|
Console.WriteLine("}");
|
|
Console.WriteLine("```");
|
|
Console.WriteLine();
|
|
Console.WriteLine("MainThreadPump:");
|
|
Console.WriteLine("```csharp");
|
|
Console.WriteLine("try");
|
|
Console.WriteLine("{");
|
|
Console.WriteLine(" int result = await pump.ExecuteAsync(() => magic.Memory.Read<int>(addr));");
|
|
Console.WriteLine("}");
|
|
Console.WriteLine("catch (TimeoutException)");
|
|
Console.WriteLine("{");
|
|
Console.WriteLine(" // Work item wedged (stuck the frame) or target not running");
|
|
Console.WriteLine(" Console.WriteLine(\"Operation timed out\");");
|
|
Console.WriteLine("}");
|
|
Console.WriteLine("catch (Exception ex)");
|
|
Console.WriteLine("{");
|
|
Console.WriteLine(" // Other exception from work item");
|
|
Console.WriteLine(" Console.WriteLine($\"Operation failed: {ex.Message}\");");
|
|
Console.WriteLine("}");
|
|
Console.WriteLine("```");
|
|
Console.WriteLine();
|
|
Console.WriteLine("InProcessInvoker:");
|
|
Console.WriteLine("```csharp");
|
|
Console.WriteLine("try");
|
|
Console.WriteLine("{");
|
|
Console.WriteLine(" int result = delegate(arg1, arg2);");
|
|
Console.WriteLine("}");
|
|
Console.WriteLine("catch (AccessViolationException)");
|
|
Console.WriteLine("{");
|
|
Console.WriteLine(" // Invalid function address or bad call convention");
|
|
Console.WriteLine("}");
|
|
Console.WriteLine("```");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Run all execution model examples
|
|
/// </summary>
|
|
public static async Task RunAll()
|
|
{
|
|
Console.WriteLine("╔════════════════════════════════════════════════════════════╗");
|
|
Console.WriteLine("║ WhiteMagic Example 3: Execution Models ║");
|
|
Console.WriteLine("╚════════════════════════════════════════════════════════════╝");
|
|
|
|
RemoteThreadExecutorExample();
|
|
await MainThreadPumpExample();
|
|
InProcessInvokerExample();
|
|
ChooseExecutionModel();
|
|
CombinedExecutionModels();
|
|
ExecutionModelErrorHandling();
|
|
|
|
Console.WriteLine("\n✓ All execution model examples completed!");
|
|
Console.WriteLine();
|
|
Console.WriteLine("⚠️ CRITICAL REMINDER:");
|
|
Console.WriteLine(" - RemoteThreadExecutor: ONLY for thread-agnostic functions");
|
|
Console.WriteLine(" - MainThreadPump: For state-sensitive calls (crash-safe)");
|
|
Console.WriteLine(" - InProcessInvoker: Only after DLL injection");
|
|
Console.WriteLine();
|
|
Console.WriteLine(" Using the wrong model crashes the target application!");
|
|
}
|
|
}
|