Fix API documentation and examples; add comprehensive documentation suite

- 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.
This commit is contained in:
kbe
2026-07-22 22:26:07 +02:00
parent 040a51bf03
commit 6300bebe33
17 changed files with 4906 additions and 1 deletions
+333
View File
@@ -0,0 +1,333 @@
# Execution Models in WhiteMagic
WhiteMagic provides three distinct execution strategies, each designed for specific payload safety requirements. Choosing the right model is critical for avoiding crashes and ensuring reliable operation.
## The Problem: Thread-Affinity Crashes
When automating or debugging a target application, the most common failure mode is calling target functions on the wrong thread. Many applications (especially games, UI apps, and applications with scripting engines) have **single-threaded state**:
- Scripting VMs (Lua, Python, custom engines)
- Render contexts (Direct3D, OpenGL)
- Object models and state machines
- UI message pumps
If you call these functions from a thread you created via `CreateRemoteThread`, they race the target's main thread → memory corruption → crash.
**WhiteMagic's solution:** Split execution by payload safety, making the crash-safe path the default.
---
## Model 1: RemoteThreadExecutor (CreateRemoteThread)
### Mechanism
Creates a new thread in the target process via `CreateRemoteThread`, executes a call stub, and waits for the exit code.
### Use Cases ✅
**SAFE for thread-agnostic payloads:**
- Pure WinAPI calls (`GetTickCount`, `GetCurrentProcessId`)
- Self-contained computations
- `LoadLibrary` (DLL injection)
- Code that touches only memory you own
### Avoid ❌
**UNSAFE for single-threaded target state:**
- Scripting engine entry points
- Render operations (Direct3D calls)
- Game state queries/modifications
- UI interactions
- Object model traversal
### Usage Example
```csharp
using WhiteMagic;
using WhiteMagic.Assembly;
using var magic = Magic.Open(targetProcess);
// Example: Call GetTickCount (thread-safe WinAPI)
var getTickCount = magic["kernel32"]["GetTickCount"];
uint ticks = getTickCount.Execute<uint>(CallConvention.Stdcall);
// Example: Call a function that doesn't touch thread-local state
int result = magic.RemoteThread.Execute<int>(
functionAddress,
CallConvention.Cdecl,
arg1, arg2, arg3
);
```
### Performance
- **Latency:** ~1-2ms (thread creation + execution + join)
- **Throughput:** Limited by thread creation overhead
- **Best for:** One-shot calls, initialization, DLL injection
### Risks
- **Crash risk:** HIGH if touching single-threaded state
- **Detection:** Easily detected by anti-cheat (new thread creation)
- **Overhead:** Thread creation is not cheap
---
## Model 2: MainThreadPump (Crash-Safe) ⭐
### Mechanism
Installs a detour on a **per-frame function** (a function called every frame, like D3D's `EndScene`) and drains a thread-safe work queue there each frame.
### How It Works
1. **Detour installation:** Hook a per-frame function at `frameAddress`
2. **Queue enqueue:** Caller enqueues a delegate via `Enqueue<T>()`
3. **Frame execution:** Target's main thread runs the hook each frame
4. **Queue drain:** Hook checks queue, executes pending work items
5. **Result return:** `TaskCompletionSource` delivers result/exception
### Use Cases ✅
**SAFE for state-sensitive calls:**
- Game state modifications (health, position, inventory)
- Scripting engine calls
- UI interactions
- Render operations
- Object model traversal
- **Any function that assumes main-thread context**
### Usage Example
```csharp
using WhiteMagic;
using WhiteMagic.Execution;
using var magic = Magic.Open(targetProcess);
// Create a pump that hooks a per-frame function
// (e.g., D3D9 EndScene, or any per-frame handler)
var pump = magic.CreateMainThreadPump(frameAddress);
// Enqueue work that runs on the target's main thread
int health = await pump.Enqueue(() =>
{
// Safe to touch game state here
return magic.Memory.Read<int>(healthAddress);
});
// Modify game state safely
await pump.Enqueue(() =>
{
magic.Memory.Write(healthAddress, 999);
});
// Call a function that requires main-thread context
var result = await pump.Enqueue(() =>
{
var fn = magic["target"]["ProcessInput"];
return fn.Execute<int>(CallConvention.ThisCall, inputPtr);
});
```
### Finding a Frame Function
**Common per-frame functions:**
- Direct3D 9: `EndScene` (device + 0x44 vtable entry)
- Direct3D 11: Present callbacks
- OpenGL: SwapBuffers callbacks
- Custom: Many games have a `Update` or `Render` function per frame
**Helper for D3D9:**
```csharp
// Find D3D9 device and resolve EndScene
IntPtr d3dDevice = FindD3D9Device(magic);
IntPtr endScene = magic.Memory.Read<IntPtr>(d3dDevice + 0x44); // VTable
var pump = magic.CreateMainThreadPump(endScene);
```
### Performance
- **Latency:** ~1 frame (16-33ms at 30-60 FPS)
- **Throughput:** Limited by frame rate and work item duration
- **Best for:** Repeated state-sensitive calls, game mods, automation
### Safety Features
- **Crash-safe:** Runs on target's own thread
- **Exception propagation:** Exceptions in work items propagate to caller
- **Timeout handling:** Can detect wedged work items
- **Queue bounded:** Prevents unlimited queue growth
### Risks
- **Frame overhead:** Hook adds per-frame overhead (keep work items short)
- **Wedged work item:** A stuck work item stalls the frame (detectable via timeout)
- **Frame function needed:** Requires finding a per-frame function (application-specific)
---
## Model 3: InProcessInvoker (Direct Delegates)
### Mechanism
Once a managed DLL is injected into the target, creates native delegates via `Marshal.GetDelegateForFunctionPointer` and calls them directly.
### Use Cases ✅
**When injected in-process:**
- Direct function calls with zero thread crossing
- High-performance repeated calls
- Full .NET interop capabilities
### Usage Example
```csharp
using WhiteMagic;
using WhiteMagic.Execution;
// Only works when injected in-process
using var magic = Magic.OpenInProcess();
// Create a delegate to a native function
var getName = magic.Memory.CreateFunction<GetNameDelegate>(
getNameAddress
);
// Call directly as a delegate
string name = getName(12345);
// Delegate signature
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate string GetNameDelegate(int id);
```
### When to Use
- **After injection:** Your managed DLL is already in the target
- **Performance-critical:** Need sub-microsecond call latency
- **Complex interop:** Need to pass complex structures or callbacks
### Performance
- **Latency:** <1μs (direct function call)
- **Throughput:** Highest (no thread crossing)
- **Best for:** In-process tools, profilers, injected helpers
### Limitations
- **In-process only:** Requires managed DLL injection
- **No crash-safety benefit:** Still subject to thread-affinity issues
- **Requires loader:** Need a CLR host or injection bootstrapper
---
## Choosing the Right Model
### Decision Flowchart
```
Are you injected in-process?
├─ Yes → Use InProcessInvoker (direct delegates)
└─ No → Does the call touch single-threaded state?
├─ Yes → Use MainThreadPump (crash-safe)
└─ No → Use RemoteThreadExecutor (CreateRemoteThread)
```
### Practical Guidelines
| Scenario | Model | Reason |
|----------|-------|--------|
| DLL injection | RemoteThreadExecutor | `LoadLibrary` is thread-safe |
| Read health bar | MainThreadPump | Game state is main-thread-affine |
| Call GetTickCount | RemoteThreadExecutor | WinAPI, no thread affinity |
| Script engine call | MainThreadPump | Script VM is main-thread-only |
| Injected profiling | InProcessInvoker | Already in-process, max performance |
| Window mutation | RemoteThreadExecutor | WinAPI `SetWindowPos` is thread-safe |
### Common Mistakes
**Wrong:** Using `RemoteThreadExecutor` for game state reads
```csharp
// UNSAFE: Will crash in most games
int health = magic.RemoteThread.Execute<int>(
readHealthFn,
CallConvention.Cdecl
);
```
**Right:** Using `MainThreadPump` for game state
```csharp
// SAFE: Runs on game's main thread
int health = await pump.Enqueue(() =>
magic.Memory.Read<int>(healthAddress)
);
```
---
## Comparison Summary
| Feature | RemoteThreadExecutor | MainThreadPump | InProcessInvoker |
|---------|---------------------|----------------|------------------|
| **Safety** | Thread-agnostic only | Crash-safe | In-process context |
| **Latency** | ~1-2ms | ~1 frame (16-33ms) | <1μs |
| **Detection risk** | High (new thread) | Low (detour) | None (in-process) |
| **Best for** | One-shot calls, DLL injection | State-sensitive calls | In-process tools |
| **Setup cost** | Low | Medium (need frame fn) | High (need injection) |
| **Throughput** | Low | Medium | Highest |
---
## Advanced Topics
### Bypassing Anti-Cheat
**RemoteThreadExecutor** is easily detected (new thread creation). For stealth:
1. **Use MainThreadPump:** Detours are harder to detect than thread creation
2. **Thread hijacking:** For one-shot calls, hijack an existing thread (see `CodeInjector`)
### Combining Models
```csharp
// Use RemoteThreadExecutor to inject DLL
magic.RemoteThread.Execute<IntPtr>(
loadLibraryAddress,
CallConvention.Stdcall,
dllPathPtr
);
// Now injected, switch to InProcessInvoker
using var inProcess = Magic.OpenInProcess();
var fn = inProcess.Memory.CreateFunction<MyDelegate>(address);
```
### Error Handling
```csharp
try
{
int result = await pump.Enqueue(() =>
magic.Memory.Read<int>(address)
);
}
catch (AccessViolationException)
{
// Address not readable
}
catch (TimeoutException)
{
// Work item wedged (stuck the frame)
}
```
---
## Further Reading
- [Architecture](./architecture.md) — Overall system design
- [Function Hooking](./hooking.md) — DetourManager internals
- [Memory Access](./memory-access.md) — MemoryBase and MarshalCache