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
+387
View File
@@ -0,0 +1,387 @@
# Function Hooking in WhiteMagic
WhiteMagic provides two types of runtime code modification: **inline detours** (function hooking) and **byte patches**. Both are reversible and automatically restored on disposal.
## Overview
| Feature | DetourManager | PatchManager |
|---------|---------------|--------------|
| **Scope** | In-process only | External + In-process |
| **Mechanism** | Inline `jmp` over prologue | Named byte patch |
| **Reversible** | ✅ Yes | ✅ Yes |
| **Auto-restore** | ✅ Yes | ✅ Yes |
| **Original call** | ✅ Via trampoline | ❌ No |
## DetourManager (Inline Function Hooking)
### What is a Detour?
An inline detour overwrites the first bytes of a function's prologue with a jump instruction (`jmp` or `push/ret`) that redirects execution to your hook delegate. The original bytes are saved in a **trampoline** that can call the original function.
### How It Works
```
Original function:
│ push ebp
│ mov ebp, esp
│ sub esp, 0x10
│ ... rest of function
After detour:
│ jmp [hook_address] ← Overwrites prologue
Trampoline:
│ push ebp ← Saved original bytes
│ mov ebp, esp
│ jmp [original+5] ← Jumps back to original function
```
### Use Cases
- **API interception:** Hook WinAPI functions (e.g., `CreateFileW` to monitor file access)
- **Function replacement:** Replace a game function with your own logic
- **Behavior modification:** Change parameters or return values
- **Profiling/instrumentation:** Count calls, measure execution time
### Usage Example
```csharp
using WhiteMagic;
using WhiteMagic.Hooking;
// In-process only
using var magic = Magic.OpenInProcess();
// Define your hook delegate
[DllImport("kernel32.dll")]
public delegate void SleepDelegate(uint dwMilliseconds);
public static void MySleep(uint ms)
{
Console.WriteLine($"Sleep called with {ms}ms");
// Optionally call original
// originalSleep(ms);
}
// Apply the detour
IntPtr sleepAddr = magic["kernel32"]["Sleep"].Address;
var detour = magic.DetourManager.Detour(
sleepAddr,
(SleepDelegate)MySleep
);
detour.Apply();
// ... use the hook
// Remove and restore original
detour.Remove();
```
### CallOriginal (Trampoline)
To call the original function from your hook:
```csharp
static MyDetourDelegate Original = null!;
static void MyHook(int arg1, float arg2)
{
// Do something before
Console.WriteLine($"Before: {arg1}, {arg2}");
// Call original
int result = Original(arg1, arg2);
// Do something after
Console.WriteLine($"After: {result}");
return result;
}
// Create detour with original
var detour = magic.DetourManager.Detour(
targetAddress,
(MyDetourDelegate)MyHook,
out var original
);
Original = original;
detour.Apply();
```
### Prologue Safety
Before splicing a detour, WhiteMagic validates the prologue:
**Default (StubAssembler):**
- Covers common x86/x64 prologue shapes:
- `push ebp; mov ebp, esp`
- `mov edi, edi` (hot-patch padding)
- `sub rsp, XX` (x64 stack allocation)
- Single-byte instructions (`nop`, `int3`)
**Optional (IcedAssembler):**
- Full disassembly validation via Iced
- Handles arbitrary prologues
- Ensures splice lands on instruction boundaries
### Safety Considerations
**Risks:**
- **Mid-instruction splice:** Crashes if prologue validation fails
- **Race conditions:** Patching while code is running can crash
- **Anti-cheat:** Detours are easily detected
**Mitigations:**
- Validate prologue before patching
- Pause threads during application (use `DetourManager.Apply(options)`)
- Use `MainThreadPump` for crash-safe execution
- Auto-restore on `Dispose`
### Thread Safety
**DetourManager is NOT thread-safe.**
```csharp
// WRONG: Concurrent modifications
Task.Run(() => detour1.Apply());
Task.Run(() => detour2.Apply());
// RIGHT: Synchronize
lock (magic.DetourManager)
{
detour1.Apply();
detour2.Apply();
}
```
### Performance Impact
- **Hook overhead:** ~5-10 CPU cycles (single jmp)
- **Trampoline call:** ~20-30 cycles (jmp + trampoline execution)
- **Best practice:** Keep hook delegates short
---
## PatchManager (Named Byte Patches)
### What is a Patch?
A patch is a named byte buffer that overwrites a region of memory. Unlike detours, patches can be any bytes and don't include a trampoline mechanism.
### Use Cases
- **Patching constants:** Change a hardcoded value (e.g., max HP cap)
- **NOP-ing code:** Remove instructions (e.g., bypass a check)
- **Restoring bytes:** Undo anti-cheat modifications
- **Hot-patching:** Patch the hot-patch region (common in WinAPI)
### Usage Example
```csharp
using WhiteMagic;
using WhiteMagic.Hooking;
using var magic = Magic.Open(targetProcess);
// Create a patch to NOP 5 bytes
var patch = magic.PatchManager.Create(
"MaxHealthCapPatch",
address,
new byte[] { 0x90, 0x90, 0x90, 0x90, 0x90 } // NOP x5
);
patch.Apply();
// Check status
if (patch.IsApplied)
{
Console.WriteLine("Patch applied");
}
// Remove and restore
patch.Remove();
```
### VirtualProtect Dance
When applying a patch, `PatchManager` automatically:
1. Calls `VirtualProtectEx` to make region writable
2. Writes the patch bytes
3. Calls `VirtualProtectEx` to restore original protection
### Relative Addressing
Patches support relative addressing:
```csharp
// Apply relative to module base
var patch = magic.PatchManager.Create(
"DataPatch",
address, // Interpreted as relative if isRelative=true
patchBytes,
isRelative: true
);
```
### Thread Safety
**PatchManager is NOT thread-safe.** Synchronize concurrent modifications.
---
## Lifecycle Management
Both managers auto-restore on disposal:
```csharp
// All applied detours and patches auto-restore
using (var magic = Magic.OpenInProcess())
{
var detour = magic.DetourManager.Detour(addr, hook);
detour.Apply();
var patch = magic.PatchManager.Create("Patch", addr, bytes);
patch.Apply();
// End of using block: detour.Remove() and patch.Remove() called automatically
}
```
### Explicit Disposal
```csharp
var detour = magic.DetourManager.Detour(addr, hook);
detour.Apply();
// Later
detour.Remove(); // Restores original bytes
```
---
## Advanced: Memory Protection
### Handling Read-Only Memory
If the target region is protected (e.g., `.text` section), both managers use:
```csharp
VirtualProtectEx(handle, address, size, PAGE_EXECUTE_READWRITE, out oldProt);
// Write bytes
VirtualProtectEx(handle, address, size, oldProt, out _);
```
### Risks
- **Anti-cheat:** May detect protection changes
- **Race conditions:** Other threads might execute during patch window
- **Crashes:** If code executes during the brief writable window
---
## Advanced: Conditional Patching
```csharp
public class ConditionalPatch
{
private readonly Patch _patch;
private bool _enabled;
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
if (value)
_patch.Apply();
else
_patch.Remove();
_enabled = value;
}
}
}
```
---
## Advanced: Chain Hooking
Multiple hooks on the same function:
```csharp
// First hook
var detour1 = magic.DetourManager.Detour(addr, Hook1);
detour1.Apply(out var trampoline1);
// Second hook (trampoline from first)
var detour2 = magic.DetourManager.Detour(
trampoline1.TrampolineAddress,
Hook2
);
detour2.Apply();
// Execution flow: Original → Hook2 → Hook1 → Trampoline1 → Original+5
```
---
## Comparison to Other Libraries
| Library | Detours | Patches | Auto-restore | In-process |
|---------|---------|---------|---------------|------------|
| **WhiteMagic** | ✅ | ✅ | ✅ | ✅ Required |
| BlackMagic | ❌ | ❌ | ❌ | N/A |
| MemorySharp | ❌ (planned) | ❌ (planned) | N/A | N/A |
| GreyMagic | ✅ | ✅ | ✅ | ✅ Required |
---
## Troubleshooting
### "Detour failed: prologue too short"
**Cause:** Function prologue is shorter than minimum required (5 bytes for x86 `jmp`, 14 bytes for x64 `push/ret`).
**Solution:**
- Use a different function
- Patch deeper into the function (after prologue)
- For x86, use hot-patch area (`mov edi, edi` padding)
### "Patch failed: access violation"
**Cause:** Memory region is protected or invalid.
**Solution:**
- Verify address is valid (use `Magic.Memory.CanRead`)
- Ensure process has `PROCESS_VM_OPERATION` access
- Check if anti-cheat is blocking writes
### "Crash after detour"
**Cause:** Prologue validation failed or mid-instruction splice.
**Solution:**
- Enable Iced backend for full validation
- Pause threads during application
- Check if target is using code obfuscation
### "Hook not called"
**Cause:** Wrong function address or hook installed after target already called it.
**Solution:**
- Verify address with debugger
- Install hook early (before target uses function)
- Check if target is using a different implementation (e.g., forwarded export)
---
## Further Reading
- [Architecture](./architecture.md) — Hooking layer design
- [Execution Models](./execution-models.md) — Safe hook execution
- [Memory Access](./memory-access.md) — Reading/writing memory