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
+321
View File
@@ -0,0 +1,321 @@
# WhiteMagic Architecture
## Overview
WhiteMagic is built as a layered architecture that provides multiple levels of abstraction over Windows process-introspection APIs. This design allows consumers to choose the right level of control for their use case, from low-level memory operations to high-level ergonomic APIs.
## Layer Structure
```
┌──────────────────────────────────────────────────────────────┐
│ Magic (Facade) │
│ High-level entry point: Magic.Open(), Magic.OpenInProcess() │
└───────────────────────┬──────────────────────────────────────┘
┌───────────────┴───────────────┐
│ │
┌───────▼─────────┐ ┌────────▼────────┐
│ MemoryBase │ │ High-Level API │
│ (Abstract) │ │ │
├─────────────────┤ ├─────────────────┤
│ ExternalReader │ │ RemotePointer │
│ InProcessReader │ │ RemoteModule │
│ │ │ RemoteFunction │
│ ┌──────────────┐│ │ ManagedPeb/TEB │
│ │ Hooking ││ │ Window/Input │
│ │ DetourMgr ││ └─────────────────┘
│ │ PatchMgr ││
│ └──────────────┘│
└─────────┬───────┘
┌─────▼─────┬───────────┬───────────┐
│ │ │ │
┌───▼────┐ ┌────▼──┐ ┌──────▼──┐ ┌──────▼───┐
│Native │ │Discov.│ │Execution│ │Assembly │
│P/Invoke│ │Scan/PE│ │3-tier │ │IAssembler│
└────────┘ └───────┘ └─────────┘ └──────────┘
```
## Core Layer
### MemoryBase (Abstract)
The foundation of WhiteMagic is the `MemoryBase` abstract class, which defines the contract for all memory operations:
**Key responsibilities:**
- Abstract memory read/write operations
- Host for `MarshalCache<T>` optimization
- Owner of `PatchManager` and `DetourManager`
- Relative/absolute addressing support
**Design decision (D1):** Dual-mode readers share the same abstract interface, allowing high-level code (pattern scanning, patching, etc.) to work in both external and in-process modes without changes.
### ExternalReader
Implements `MemoryBase` for out-of-process operations:
**Mechanism:** Uses `ReadProcessMemory`/`WriteProcessMemory` via P/Invoke
**Handle:** `SafeMemoryHandle` to target process
**Use case:** Primary mode for automation hosts
### InProcessReader
Implements `MemoryBase` for injected code:
**Mechanism:** Uses `ReadProcessMemory`/`WriteProcessMemory` on self-handle
**Handle:** `SafeMemoryHandle` to current process
**Use case:** Once a managed DLL is injected, enables delegate calls and detours
**Design decision (D1 revision):** InProcessReader uses RPM-on-self instead of unsafe direct pointer deref because .NET cannot catch `AccessViolationException`, so a bad deref kills the host with no soft-failure path. The in-process speed win moves to the delegate-call and detour paths, not the reader.
## Discovery Layer
### PatternScanner
Scans target memory for byte patterns with wildcard support:
**Features:**
- IDA-style hex patterns (`48 8B ? ? ? ? ?`)
- Optional caching to avoid repeated scans
- Module-relative or absolute addressing
### PeHeaderParser
Parses PE headers to extract export information:
**Features:**
- Export table parsing
- Forwarder resolution (e.g., `kernel32!HeapAlloc``NTDLL.RtlAllocateHeap`)
- Ordinal export support
## Execution Layer (Three-Tier Model)
### RemoteThreadExecutor
**Mechanism:** `CreateRemoteThread` + hand-assembled convention stubs
**Use for:** Thread-agnostic payloads (pure WinAPI, self-contained code, DLL injection)
**Risk:** NOT crash-safe for single-threaded target state
### MainThreadPump
**Mechanism:** Detour on per-frame function + work queue
**Use for:** State-sensitive calls (default for target-state access)
**Safety:** Crash-safe — runs on target's own thread
**How it works:**
1. Installs a detour on a per-frame function (e.g., D3D `EndScene`)
2. Each frame, the hook drains a thread-safe queue
3. Work items run synchronously in target's context
4. Results/exceptions returned via `TaskCompletionSource`
### InProcessInvoker
**Mechanism:** `Marshal.GetDelegateForFunctionPointer`
**Use for:** In-process delegates after injection
**Benefit:** Zero thread-crossing overhead
## Hooking Layer
### DetourManager
**Scope:** In-process only
**Features:**
- Inline `E9 jmp` detours over function prologues
- `CallOriginal` support via trampoline
- `Apply`/`Remove` operations
- Auto-restore on `Dispose`
**Safety:** Prologue validation via `IAssembler.GetPrologueLength` (optional Iced backend)
### PatchManager
**Scope:** Both external and in-process
**Features:**
- Named byte patches
- `Apply`/`Remove`/`IsApplied`
- Auto-restore on `Dispose`
## Assembly Layer
### IAssembler Seam
Abstracts text → machine code generation:
**Implementations:**
- `StubAssembler` (default): Hand-emits calling-convention stubs, no dependency
- `IcedAssembler` (optional): Wraps Iced for arbitrary assembly
**Design decision (D3):** Keeps default configuration dependency-free; only callers needing arbitrary asm opt into Iced.
### Convention Stubs
`StubAssembler` emits x86/x64 calling-convention trampolines:
**Conventions supported:** cdecl, stdcall, thiscall, fastcall, x64 (Microsoft)
**Encoding:** Deterministic byte emission, no parsing required
## High-Level Layer
### RemotePointer
Indexer-based pointer arithmetic:
```csharp
var ptr = magic[baseAddress];
int value = ptr.Read<int>(offset);
ptr.Write(999, offset);
```
### RemoteModule/RemoteFunction
Module and export resolution:
```csharp
var module = magic["user32"];
var fn = module["MessageBoxA"];
int result = fn.Execute<int>(CallConvention.Stdcall, args...);
```
### ManagedPeb/ManagedTeb
Managed views of Process Environment Block and Thread Environment Block:
**Features:**
- Typed field access
- No manual structure marshalling
### Window/Input
Window mutation and input simulation:
**Features:**
- Move/resize/title/activate/flash windows
- Keyboard/mouse input without focus (where supported)
## Optimization Layer
### MarshalCache<T>
Per-type metadata caching:
**Cached data:**
- `Size` (managed blittable width)
- `MarshalSize` (unmanaged interop width)
- `TypeRequiresMarshal` (needs `Marshal.PtrToStructure`)
- `IsIntPtr` (for special handling)
**Performance:** Avoids per-call reflection and `Marshal.SizeOf` overhead
## Thread Safety
### ExternalReader
**Thread-safe:** Yes (RPM/WPM are thread-safe)
**Synchronization:** None required
### InProcessReader
**Thread-safe:** Yes (RPM-on-self is thread-safe)
**Synchronization:** None required
### MainThreadPump
**Thread-safe:** Yes (uses `ConcurrentQueue`)
**Synchronization:** Lock-free queue + `TaskCompletionSource`
### DetourManager/PatchManager
**Thread-safe:** No
**Synchronization:** Caller must synchronize
## Lifetime Management
All disposable resources follow RAII:
```csharp
using var magic = Magic.Open(process);
// All handles, patches, and detours auto-restore on disposal
```
**Resources cleaned up:**
- Process handles (`SafeMemoryHandle`)
- Applied patches (`PatchManager`)
- Active detours (`DetourManager`)
- Allocated memory (`AllocatedMemory`)
## Error Handling
**Strategy:** Explicit failures, silent success
- **Read operations:** Throw on failure (Win32Exception)
- **Write operations:** Return `false` on failure
- **String reads:** Return `string.Empty` on failure
- **Invalid handles:** Throw `InvalidOperationException`
**Rationale:** Writes to another process can legitimately fail (protection changed, process exited); silent retry is often the right strategy. Reads are typically expected to succeed, so exceptions surface the problem immediately.
## Extensibility Points
### IAssembler
Plug in custom assemblers:
```csharp
public interface IAssembler
{
byte[] Assemble(string assemblyText, ulong origin = 0);
}
```
### DetourManager.PrologueLengthResolver
Custom prologue validation:
```csharp
magic.DetourManager.PrologueLengthResolver = (address, minBytes) =>
// Custom validation logic
return requiredLength;
```
## Performance Characteristics
### Memory Access
| Operation | Cost | Notes |
|-----------|------|-------|
| `Read<T>` (blittable) | Low | `MemoryMarshal.Read`, no alloc |
| `Read<T>` (marshalled) | Medium | `Marshal.PtrToStructure` |
| `Read<T>` (array) | Medium-High | Per-element marshalling |
| `ReadBytes` | Low | Direct buffer copy |
| `ReadString` | Medium | Encoding + allocation |
### Execution
| Method | Latency | Safety |
|--------|---------|--------|
| `RemoteThreadExecutor` | ~1-2ms | Thread-agnostic only |
| `MainThreadPump` | ~1 frame (16-33ms) | Crash-safe |
| `InProcessInvoker` | <1μs | In-process only |
### Pattern Scanning
| Mode | Cost | Notes |
|------|------|-------|
| Uncached | High | Scans entire module |
| Cached | Low | O(1) after first scan |
## Design Principles
1. **Safety by default:** MainThreadPump prevents crashes from thread-affinity violations
2. **Bitness-agnostic:** Works on x86 and x64 without code changes
3. **No native dependencies:** Default configuration is pure managed
4. **Explicit operations:** Clear failure modes, no hidden retries
5. **Resource safety:** RAII-based cleanup prevents leaks
6. **Extensibility:** Seam points for assemblers, validators
## Further Reading
- [Execution Models](./execution-models.md) — Deep dive on the three-tier execution strategy
- [Memory Access](./memory-access.md) — MemoryBase readers and MarshalCache optimization
- [Function Hooking](./hooking.md) — DetourManager and PatchManager internals
- [Assembly Seam](./assembly-seam.md) — IAssembler abstraction and Iced integration
+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
+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
+481
View File
@@ -0,0 +1,481 @@
# Memory Access in WhiteMagic
WhiteMagic provides a dual memory-access model through an abstract `MemoryBase` class, supporting both external (out-of-process) and in-process readers with optimized typed I/O.
## MemoryBase Architecture
### Abstract Interface
`MemoryBase` defines the contract for all memory operations:
```csharp
public abstract class MemoryBase : IDisposable
{
// Abstract raw I/O
public abstract byte[] ReadBytes(IntPtr address, int count, bool isRelative = false);
public abstract int WriteBytes(IntPtr address, ReadOnlySpan<byte> bytes, bool isRelative = false);
// Typed I/O
public T Read<T>(IntPtr address, bool isRelative = false) where T : struct;
public bool Write<T>(IntPtr address, T value, bool isRelative = false) where T : struct;
// Array I/O
public T[] Read<T>(IntPtr address, int count, bool isRelative = false) where T : struct;
public bool Write<T>(IntPtr address, T[] values, bool isRelative = false) where T : struct;
// String I/O
public string ReadString(IntPtr address, Encoding encoding, int maxLength = 512);
public bool WriteString(IntPtr address, string value, Encoding encoding);
}
```
### Concrete Implementations
#### ExternalReader
**Mechanism:** `ReadProcessMemory`/`WriteProcessMemory` via P/Invoke
**Handle:** `SafeMemoryHandle` to target process
**Use case:** Primary mode for automation hosts
```csharp
using var magic = Magic.Open(targetProcess);
// Uses ExternalReader internally
```
#### InProcessReader
**Mechanism:** `ReadProcessMemory`/`WriteProcessMemory` on self-handle
**Handle:** `SafeMemoryHandle` to current process
**Use case:** Injected code, delegate calls, detours
**Design decision:** Uses RPM-on-self instead of unsafe direct pointer deref because .NET cannot catch `AccessViolationException`, so a bad deref kills the host with no soft-failure path. The in-process speed win moves to the delegate-call and detour paths, not the reader.
```csharp
using var magic = Magic.OpenInProcess();
// Uses InProcessReader internally
```
---
## Typed I/O with MarshalCache<T>
### Performance Optimization
`MarshalCache<T>` eliminates per-call reflection overhead by caching type metadata once:
```csharp
public static class MarshalCache<T>
{
// Cached at static-constructor time
public static readonly int Size; // Managed blittable width
public static readonly int MarshalSize; // Unmanaged interop width
public static readonly bool TypeRequiresMarshal; // Needs PtrToStructure
public static readonly bool IsIntPtr; // Special handling for IntPtr
}
```
### Blittable vs Marshalled Types
**Blittable types** (no marshaling needed):
- Primitives: `int`, `byte`, `float`, `double`, `bool` (1 byte managed)
- Enums (if underlying type is blittable)
- Structs containing only blittable fields
- **Performance:** `MemoryMarshal.Read<T>` — zero allocation
**Marshalled types** (require `Marshal.PtrToStructure`):
- `bool` (4 bytes in Win32 interop)
- `char` (2 bytes ANSI marshaling)
- Structs with `[MarshalAs]` attributes
- Structs with inline `ByValTStr`/`ByValArray`
- **Performance:** `Marshal.PtrToStructure` — allocates temporary copy
### Size vs MarshalSize
For most types, `Size == MarshalSize`. They differ when:
```csharp
// Example: bool field
struct MyStruct
{
public bool Flag; // 1 byte managed, 4 bytes Win32 BOOL
public int Value;
}
MarshalCache<MyStruct>.Size == 5; // Managed layout
MarshalCache<MyStruct>.MarshalSize == 8; // Win32 BOOL is 4 bytes
```
### Usage
```csharp
using var magic = Magic.Open(process);
// Blittable read (fast path)
int health = magic.Memory.Read<int>(address);
// Marshalled read (slow path)
var gameState = magic.Memory.Read<MyStruct>(address);
```
---
## Addressing Modes
### Absolute Addressing (Default)
```csharp
// Read at absolute address 0x12345678
int value = magic.Memory.Read<int>(0x12345678);
```
### Relative Addressing
Relative to module base (useful for ASRR):
```csharp
// Read at module_base + 0x1000
int value = magic.Memory.Read<int>(0x1000, isRelative: true);
// Equivalent to:
int value = magic.Memory.Read<int>(magic.Memory.ImageBase + 0x1000);
```
### Using RemotePointer
The `RemotePointer` indexer provides fluent relative addressing:
```csharp
var basePtr = magic[moduleBase];
int offset1 = basePtr.Read<int>(0x1000);
int offset2 = basePtr.Read<int>(0x2000);
// Chained offsets
var nestedPtr = magic[basePtr.Read<IntPtr>(0x1000)];
int value = nestedPtr.Read<int>(0x50);
```
---
## String I/O
### Reading Strings
```csharp
// Read null-terminated ANSI string
string ansi = magic.Memory.ReadString(
address,
Encoding.ASCII,
maxLength: 256
);
// Read null-terminated UTF-16 string
string unicode = magic.Memory.ReadString(
address,
Encoding.Unicode,
maxLength: 512
);
```
**Implementation:** Reads byte-by-byte until null terminator or `maxLength`.
### Writing Strings
```csharp
// Write null-terminated string
bool success = magic.Memory.WriteString(
address,
"Hello World",
Encoding.ASCII
);
```
**Implementation:** Writes bytes + null terminator.
---
## Array I/O
### Reading Arrays
```csharp
// Read 10 integers
int[] values = magic.Memory.Read<int>(address, 10);
// Read struct array
var enemies = magic.Memory.Read<EnemyStruct>(enemyListPtr, 50);
```
**Performance:**
- Blittable: `MemoryMarshal.Read<T>` in a loop (fast)
- Marshalled: `Marshal.PtrToStructure<T>` per element (slow)
### Writing Arrays
```csharp
// Write integer array
int[] values = { 1, 2, 3, 4, 5 };
magic.Memory.Write(address, values);
// Write struct array
EnemyStruct[] enemies = GetEnemies();
magic.Memory.Write(enemyListPtr, enemies);
```
---
## Raw Byte I/O
### Reading Bytes
```csharp
// Read 100 bytes
byte[] buffer = magic.Memory.ReadBytes(address, 100);
// Read with relative addressing
byte[] code = magic.Memory.ReadBytes(offset, count, isRelative: true);
```
### Writing Bytes
```csharp
// Write byte array
byte[] patchBytes = { 0x90, 0x90, 0x90 }; // NOP x3
int written = magic.Memory.WriteBytes(address, patchBytes);
// Write with relative addressing
int written = magic.Memory.WriteBytes(
offset,
new byte[] { 0x01, 0x02, 0x03 },
isRelative: true
);
```
---
## Error Handling
### Read Operations
**Strategy:** Explicit failures → throw exceptions
```csharp
try
{
int value = magic.Memory.Read<int>(address);
}
catch (Win32Exception ex)
{
// ReadProcessMemory failed (access violation, process exited, etc.)
Console.WriteLine($"Read failed: {ex.Message}");
}
catch (InvalidOperationException ex)
{
// Process handle is closed
Console.WriteLine($"Process not open: {ex.Message}");
}
```
**Returns:** `default(T)` if fewer bytes read than expected (e.g., partial read).
### Write Operations
**Strategy:** Silent failures → return `false`
```csharp
bool success = magic.Memory.Write(address, 999);
if (!success)
{
// Handle failure (retry, log, etc.)
}
```
**Reason:** Writes to another process can legitimately fail (protection changed, process exited); silent retry is often the right strategy.
### String Operations
**Read:** Returns `string.Empty` on failure.
```csharp
string text = magic.Memory.ReadString(address, Encoding.ASCII);
if (string.IsNullOrEmpty(text))
{
// Read failed or string is empty
}
```
**Write:** Returns `false` on failure.
---
## Thread Safety
### ExternalReader
**Thread-safe:** Yes
`ReadProcessMemory`/`WriteProcessMemory` are thread-safe at the OS level. No synchronization required.
### InProcessReader
**Thread-safe:** Yes
RPM-on-self is thread-safe. No synchronization required.
### High-Level Access
**Thread-safe:** Depends on usage
```csharp
// SAFE: Concurrent reads from multiple threads
int v1 = magic.Memory.Read<int>(addr1);
int v2 = magic.Memory.Read<int>(addr2);
// SAFE: Same address, concurrent reads (no race, just inconsistent value)
int v3 = magic.Memory.Read<int>(addr);
int v4 = magic.Memory.Read<int>(addr);
// UNSAFE: Concurrent writes (last write wins, no atomicity)
magic.Memory.Write(addr, 1); // Thread 1
magic.Memory.Write(addr, 2); // Thread 2 (may win)
```
For atomic read-modify-write, use `MainThreadPump` or implement locking.
---
## Performance Characteristics
| Operation | Cost | Notes |
|-----------|------|-------|
| `Read<T>` (blittable) | Low | `MemoryMarshal.Read`, no alloc |
| `Read<T>` (marshalled) | Medium | `Marshal.PtrToStructure` + alloc |
| `Read<T>` (array, blittable) | Medium | Per-element `MemoryMarshal.Read` |
| `Read<T>` (array, marshalled) | High | Per-element marshalling + alloc |
| `ReadBytes` | Low | Direct buffer copy |
| `ReadString` | Medium | Byte-by-byte + encoding + alloc |
| `Write<T>` (blittable) | Low | `MemoryMarshal.Write` + buffer alloc |
| `Write<T>` (marshalled) | Medium | `Marshal.StructureToPtr` + buffer alloc |
| `WriteBytes` | Low | Direct buffer copy |
| `WriteString` | Medium | Encoding + null terminator + buffer alloc |
### Optimization Tips
1. **Prefer blittable types:** Use `int` instead of `bool` where possible
2. **Batch reads:** Read arrays instead of individual elements
3. **Reuse buffers:** For repeated reads, reuse byte arrays
4. **Cache offsets:** Compute addresses once, reuse them
5. **Use MarshalCache:** Automatic via `Read<T>`/`Write<T>`
---
## Common Patterns
### Pattern: Reading a Nested Structure
```csharp
// Assume structure: GameManager -> PlayerList -> Player[i] -> Health
IntPtr gameManagerPtr = magic.Memory.ImageBase + 0x1000;
IntPtr playerListPtr = magic.Memory.Read<IntPtr>(gameManagerPtr + 0x20);
IntPtr playerPtr = magic.Memory.Read<IntPtr>(playerListPtr + (playerIndex * 8));
int health = magic.Memory.Read<int>(playerPtr + 0x4);
```
**Using RemotePointer:**
```csharp
int health = magic[gameManagerPtr]
.Read<IntPtr>(0x20) // PlayerList
.Let(ptr => magic[ptr]
.Read<IntPtr>(playerIndex * 8)) // Player
.Let(ptr => magic[ptr]
.Read<int>(0x4)); // Health
```
### Pattern: Scanning for a Value
```csharp
// Scan memory region for a specific value
IntPtr found = IntPtr.Zero;
byte[] region = magic.Memory.ReadBytes(baseAddr, size);
for (int i = 0; i < region.Length - 4; i++)
{
int value = BitConverter.ToInt32(region, i);
if (value == targetValue)
{
found = baseAddr + i;
break;
}
}
```
**Better:** Use `PatternScanner` (see [Discovery](./discovery.md)).
### Pattern: Safe Retry Loop
```csharp
// Retry write with exponential backoff
int attempts = 0;
bool success = false;
while (attempts < 5 && !success)
{
success = magic.Memory.Write(address, value);
if (!success)
{
attempts++;
Thread.Sleep(100 * (1 << attempts)); // 100ms, 200ms, 400ms, ...
}
}
```
---
## Troubleshooting
### "Read returns default value"
**Possible causes:**
1. Address is invalid
2. Process has exited
3. Memory protection doesn't allow read
4. Fewer bytes read than expected (partial read)
**Solutions:**
- Verify address with debugger
- Check `magic.Memory.Handle.IsInvalid`
- Use `CanRead` helper (if available)
- Validate `ReadBytes` length
### "Write returns false"
**Possible causes:**
1. Memory protection is read-only
2. Process has exited
3. Address is invalid
4. Anti-cheat blocking writes
**Solutions:**
- Verify address with debugger
- Check memory protection (`VirtualQueryEx`)
- Ensure process has `PROCESS_VM_WRITE` access
- Retry after `VirtualProtectEx` (if you have rights)
### "Performance is slow"
**Possible causes:**
1. Reading individual elements in a loop
2. Using marshalled types extensively
3. Small reads/writes (not batching)
**Solutions:**
- Read arrays instead of loops
- Use blittable types where possible
- Batch reads/writes
- Cache frequently accessed values
---
## Further Reading
- [Architecture](./architecture.md) — MemoryBase layer design
- [Discovery](./discovery.md) — Pattern scanning and PE parsing
- [Execution Models](./execution-models.md) — Safe execution in target process
+579
View File
@@ -0,0 +1,579 @@
# Troubleshooting Guide
This guide covers common issues, errors, and solutions when using WhiteMagic.
## Table of Contents
- [Process Access Issues](#process-access-issues)
- [Memory Operation Failures](#memory-operation-failures)
- [Execution Errors](#execution-errors)
- [Hooking Problems](#hooking-problems)
- [Pattern Scanning Issues](#pattern-scanning-issues)
- [Performance Problems](#performance-problems)
- [Crash and Stability Issues](#crash-and-stability-issues)
- [Build and Compilation Errors](#build-and-compilation-errors)
---
## Process Access Issues
### "Process is not open for read/write"
**Symptom:**
```
System.InvalidOperationException: Process is not open for read/write.
```
**Causes:**
1. Process has exited
2. Process handle is closed
3. Insufficient permissions
**Solutions:**
```csharp
// Check process state
if (magic.Memory.Handle.IsInvalid || magic.Memory.Handle.IsClosed)
{
Console.WriteLine("Process handle is invalid or closed");
}
// Re-open the process
using var magic = Magic.Open(process);
```
**Prevention:**
- Keep `Process` object alive
- Don't dispose `Magic` while using it
- Use `using` statement for automatic cleanup
### "OpenProcess failed: Access Denied"
**Symptom:**
```
Process.Open returns false, or Magic.Open throws Win32Exception
```
**Causes:**
1. Insufficient privileges (not running as administrator)
2. Target process is protected (anti-cheat, system process)
3. 32-bit/64-bit mismatch
**Solutions:**
```csharp
// Run as administrator
// Right-click → Run as Administrator
// Check process bitness matches host
if (Environment.Is64BitProcess != Is64BitProcess(targetProcess))
{
Console.WriteLine("Bitness mismatch between host and target");
}
```
**Prevention:**
- Run with admin privileges
- Check target process protection level
- Ensure bitness compatibility
---
## Memory Operation Failures
### "Read returns default value"
**Symptom:**
```csharp
int value = magic.Memory.Read<int>(address);
// value is 0 (or default), but expected different value
```
**Causes:**
1. Address is invalid
2. Memory protection prevents reading
3. Fewer bytes read than expected
4. Process has exited
**Solutions:**
```csharp
// Verify address with debugger
// Check if address is readable
byte[] test = magic.Memory.ReadBytes(address, 4);
if (test.Length < 4)
{
Console.WriteLine("Cannot read from address");
}
// Validate handle
if (magic.Memory.Handle.IsInvalid)
{
Console.WriteLine("Process handle is invalid");
}
```
### "Write returns false"
**Symptom:**
```csharp
bool success = magic.Memory.Write(address, value);
if (!success) { /* Write failed */ }
```
**Causes:**
1. Memory is read-only (e.g., `.text` section)
2. Address is invalid
3. Process has exited
4. Anti-cheat blocking writes
**Solutions:**
```csharp
// Retry with delay
for (int i = 0; i < 3; i++)
{
if (magic.Memory.Write(address, value))
break;
Thread.Sleep(50);
}
// Check if memory is protected
// Use VirtualProtectEx if you have rights (advanced)
```
**For code patches, use `PatchManager` instead:**
```csharp
var patch = magic.PatchManager.Create("Patch", address, bytes);
patch.Apply(); // Handles VirtualProtect automatically
```
---
## Execution Errors
### "CreateRemoteThread failed"
**Symptom:**
```
Win32Exception: CreateRemoteThread failed
```
**Causes:**
1. Target process is protected
2. Insufficient permissions
3. Target process has exited
**Solutions:**
```csharp
// Ensure process is still running
if (process.HasExited)
{
Console.WriteLine("Process has exited");
}
// Check permissions
// Run as administrator
```
### "Crash when calling target function via RemoteThreadExecutor"
**Symptom:** Target application crashes after `RemoteThreadExecutor.Execute<T>`
**Cause:** Calling single-threaded function from remote thread (thread-affinity violation)
**Solution:** Use `MainThreadPump` instead:
```csharp
// WRONG (crashes game):
int health = magic.RemoteThread.Execute<int>(
readHealthFn,
CallConvention.Cdecl
);
// RIGHT (crash-safe):
var pump = magic.CreateMainThreadPump(frameAddress);
int health = await pump.Enqueue(() =>
magic.Memory.Read<int>(healthAddress)
);
```
**Explanation:** Game state, scripting engines, and render contexts are often main-thread-only. Use `MainThreadPump` for these.
### "MainThreadPump work item wedged"
**Symptom:** `Enqueue<T>()` hangs or times out
**Cause:** Work item threw exception or entered infinite loop
**Solution:**
```csharp
try
{
int result = await pump.Enqueue(() =>
{
// Keep work items short!
return magic.Memory.Read<int>(address);
}, TimeSpan.FromSeconds(1)); // Add timeout
}
catch (TimeoutException)
{
Console.WriteLine("Work item wedged (stuck the frame)");
}
```
**Prevention:**
- Keep pump work items short (< 1ms)
- Avoid blocking calls in work items
- Use timeout on `Enqueue<T>`
---
## Hooking Problems
### "Detour failed: prologue too short"
**Symptom:**
```
InvalidOperationException: Prologue is too short for detour (minimum 5 bytes required)
```
**Cause:** Function prologue is shorter than minimum required for jmp instruction
**Solutions:**
```csharp
// Option 1: Hook a different function
var detour = magic.DetourManager.Detour(alternativeAddress, hook);
// Option 2: Patch deeper into function (after prologue)
// (Advanced, risky)
// Option 3: For WinAPI, use hot-patch area
if (IsHotPatchPadded(functionAddress))
{
// Can use 2-byte jmp at [address-2]
}
```
### "Patch failed: access violation"
**Symptom:**
```
AccessViolationException when applying patch
```
**Cause:** Memory is protected or invalid
**Solutions:**
```csharp
// Verify address is valid
byte[] test = magic.Memory.ReadBytes(address, 1);
// Use PatchManager (handles VirtualProtect automatically)
var patch = magic.PatchManager.Create("Patch", address, bytes);
patch.Apply();
```
### "Hook not called"
**Symptom:** Detour applied successfully, but hook delegate never executes
**Causes:**
1. Wrong function address
2. Target already called function before hook installed
3. Target is using a different implementation (e.g., forwarded export)
**Solutions:**
```csharp
// Verify address with debugger
// Install hook early (before target uses function)
// Check for forwarded exports
var fn = magic["module"]["function"];
Console.WriteLine($"Function address: {fn.Address}");
```
---
## Pattern Scanning Issues
### "FindPattern returns IntPtr.Zero"
**Symptom:** Pattern scan finds no matches
**Causes:**
1. Pattern is incorrect
2. Module not loaded
3. Memory layout changed (ASLR, update)
4. Pattern wildcards too broad
**Solutions:**
```csharp
// Verify pattern syntax
// Use IDA-style: "48 8B ? ? ? ? ?"
// ? = wildcard byte
// Narrow search range
// Scan only relevant module, not entire memory
// Check if module is loaded
var module = magic["moduleName"];
if (module.BaseAddress == IntPtr.Zero)
{
Console.WriteLine("Module not loaded");
}
// Test with known pattern
var test = magic.Memory.FindPattern("48 8B 05 ? ? ? ?", module.BaseAddress, 0x1000);
```
### "Pattern scanner is slow"
**Symptom:** `FindPattern` takes several seconds
**Cause:** Scanning large memory regions without cache
**Solution:**
```csharp
// Use pattern cache (if available in your version)
// Or cache results manually
private static readonly Dictionary<string, IntPtr> PatternCache = new();
IntPtr FindPatternCached(string pattern, IntPtr baseAddr, int size)
{
string key = $"{pattern}_{baseAddr}_{size}";
if (PatternCache.TryGetValue(key, out var cached))
return cached;
IntPtr result = magic.Memory.FindPattern(pattern, baseAddr, size);
PatternCache[key] = result;
return result;
}
```
---
## Performance Problems
### "Memory reads are slow"
**Symptom:** `Read<T>` takes several milliseconds
**Causes:**
1. Reading individual elements in a loop
2. Using marshalled types extensively
3. Target process is heavily loaded
**Solutions:**
```csharp
// WRONG: Slow loop
for (int i = 0; i < 1000; i++)
{
values[i] = magic.Memory.Read<int>(baseAddr + i * 4);
}
// RIGHT: Batch read
values = magic.Memory.Read<int>(baseAddr, 1000);
// RIGHT: Use blittable types
// Use int instead of bool where possible
```
### "High CPU usage"
**Symptom:** WhiteMagic causes high CPU in target process
**Cause:** Polling loops, tight read loops in pump
**Solutions:**
```csharp
// WRONG: Tight loop in pump
while (true)
{
int health = magic.Memory.Read<int>(healthAddr);
if (health == 0) break;
}
// RIGHT: Event-driven or throttled
// Use MainThreadPump with delays
// Or poll at reasonable interval (e.g., 60 Hz)
```
---
## Crash and Stability Issues
### "Target crashes when attached"
**Symptom:** Target application crashes shortly after `Magic.Open`
**Cause:** Thread-affinity violation (calling function from wrong thread)
**Solution:** Use `MainThreadPump` for state-sensitive calls:
```csharp
// Identify the crash-safe execution model
var pump = magic.CreateMainThreadPump(frameAddress);
// All state-sensitive calls go through pump
await pump.Enqueue(() => { /* safe code */ });
```
### "Random crashes during operation"
**Symptom:** Intermittent crashes, hard to reproduce
**Causes:**
1. Race conditions (concurrent memory access)
2. Anti-cheat interference
3. Memory protection changes mid-operation
**Solutions:**
```csharp
// Add synchronization
lock (syncLock)
{
magic.Memory.Write(address, value);
}
// Handle access violations gracefully
try
{
magic.Memory.Read<int>(address);
}
catch (AccessViolationException)
{
// Retry or handle gracefully
}
// Check for anti-cheat
// Some anti-cheat tools detect and block memory manipulation
```
---
## Build and Compilation Errors
### "XML documentation errors"
**Symptom:** Build fails with `CS1591` or `CS0419` errors
**Cause:** Missing XML comments or ambiguous cref references
**Solution:**
```xml
<!-- In WhiteMagic.csproj -->
<PropertyGroup>
<NoWarn>$(NoWarn);CS1591</NoWarn> <!-- Suppress missing warnings -->
</PropertyGroup>
```
Or add missing XML comments (see [Documentation Best Practices](../README.md#documentation)).
### "Type or namespace not found"
**Symptom:** `WhiteMagic` namespace not found after adding reference
**Cause:** Project not referencing `WhiteMagic.dll`
**Solution:**
```bash
dotnet add reference ../WhiteMagic/WhiteMagic.csproj
# OR
dotnet add package WhiteMagic
```
---
## Debugging Tips
### Enable Detailed Logging
```csharp
// Add diagnostic logging
using System.Diagnostics;
Debug.WriteLine($"Reading from 0x{address:X}");
int value = magic.Memory.Read<int>(address);
Debug.WriteLine($"Read result: {value}");
```
### Verify with External Tools
- **Cheat Engine:** Verify memory addresses and values
- **x64dbg/windbg:** Verify function addresses and disassembly
- **Process Hacker:** Check process handles and permissions
### Common Pitfalls
1.**Forgetting `isRelative` for module-relative addresses**
```csharp
// WRONG
int value = magic.Memory.Read<int>(0x1000);
// RIGHT (if 0x1000 is module-relative)
int value = magic.Memory.Read<int>(0x1000, isRelative: true);
```
2. ❌ **Using `RemoteThreadExecutor` for game state**
```csharp
// WRONG (crashes)
int health = magic.RemoteThread.Execute<int>(fn, CallConvention.Cdecl);
// RIGHT (crash-safe)
int health = await pump.Enqueue(() => magic.Memory.Read<int>(addr));
```
3. ❌ **Not disposing `Magic`**
```csharp
// WRONG (leaks handles)
var magic = Magic.Open(process);
// ... use magic
// Forgot to dispose!
// RIGHT
using (var magic = Magic.Open(process))
{
// ... use magic
} // Auto-disposes
```
---
## Getting Help
If you're still stuck:
1. **Check documentation:**
- [Architecture](./architecture.md)
- [Execution Models](./execution-models.md)
- [Memory Access](./memory-access.md)
- [Function Hooking](./hooking.md)
2. **Search issues:** Check existing GitHub issues
3. **Create minimal reproduction:**
```csharp
// Minimal code that reproduces the issue
using var magic = Magic.Open(process);
int value = magic.Memory.Read<int>(address);
// What happens vs. what you expect
```
4. **Include system info:**
- WhiteMagic version
- .NET version
- Target process (if applicable)
- Windows version
- x86 or x64
---
## Common Error Codes
| Win32 Error | Meaning | Solution |
|-------------|---------|----------|
| `ERROR_ACCESS_DENIED` (5) | Insufficient permissions | Run as administrator |
| `ERROR_INVALID_HANDLE` (6) | Handle is invalid/closed | Re-open process |
| `ERROR_NOT_ENOUGH_MEMORY` (8) | Insufficient memory | Reduce buffer size |
| `ERROR_NOACCESS` (998) | Invalid access | Check memory protection |
| `ERROR_PARTIAL_COPY` (299) | Partial read/write | Retry or check address |
---
*For more information, see the main [README](../README.md) and [architecture documentation](./architecture.md).*