- 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.
580 lines
13 KiB
Markdown
580 lines
13 KiB
Markdown
# 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).*
|