16 KiB
DolHook
A production-ready GameCube function-hooking library with ISO patcher. Inject custom code into GameCube games with full hardware-level control.
Features
- 🎮 Runtime Function Hooking - Detour game functions with automatic trampoline generation
- 💾 Memory Patching - Safe primitives with full cache synchronization
- 🔍 Pattern Scanning - Locate functions by signature with wildcard support
- 📺 Full VI/XFB Support - Complete video interface initialization with YUV framebuffer
- 🎨 Hardware Text Rendering - 8×8 bitmap font rendered directly to framebuffer
- 📦 ISO Patcher - Safely modify GameCube ISOs with automatic backup
- ⚡ Production Ready - Battle-tested on real hardware and Dolphin emulator
What is DolHook?
DolHook allows you to modify GameCube games at runtime by injecting a small payload (< 32 KB) into the game's executable. The payload runs before the game starts, installs your custom hooks, then passes control to the original game code.
Perfect for:
- Game modding and ROM hacking
- Reverse engineering and analysis
- Custom gameplay modifications
- Debug overlays and trainers
- Research and experimentation
Quick Start
# Install devkitPPC
wget https://github.com/devkitPro/pacman/releases/latest/download/devkitpro-pacman.amd64.deb
sudo dpkg -i devkitpro-pacman.amd64.deb
sudo dkp-pacman -S gamecube-dev
# Clone and build
git clone https://github.com/apfelteesaft/dolhook.git
cd dolhook
source tools/env.sh
make all
# Patch a game
./patchiso MyGame.iso --out MyGame.patched.iso
Boot the patched ISO and you'll see "Patched with DolHook" displayed on screen!
How It Works
Architecture Overview
┌─────────────────────────────────────────┐
│ GameCube Boot │
│ ↓ │
│ BIOS loads main.dol │
│ ↓ │
│ DOL entry → 0x80400000 (DolHook) │
│ ↓ │
│ entry.S saves registers │
│ ↓ │
│ dh_init(): │
│ • Detect NTSC/PAL video mode │
│ • Initialize VI hardware │
│ • Setup 640×480 YUV framebuffer │
│ • Render banner with shadow text │
│ • Flush CPU cache │
│ • Install your hooks │
│ ↓ │
│ Tail-jump to original entry 0x80003100 │
│ ↓ │
│ Game runs with hooks active ✓ │
└─────────────────────────────────────────┘
What Makes DolHook Special?
Complete Hardware Control: Unlike other hooking libraries, DolHook implements full VI (Video Interface) and XFB (External Frame Buffer) management. This means:
- 📺 True hardware initialization from scratch
- 🎨 Direct YUV framebuffer rendering (YUY2 format)
- 🔧 NTSC/PAL auto-detection and configuration
- ⚡ Proper cache coherency (dcbf/icbi/sync)
- 📊 Production-quality timing values
No Dependencies: DolHook doesn't rely on the game's SDK functions. If OSReport isn't available, it initializes the entire video system itself.
Installation
Prerequisites
- devkitPPC: PowerPC cross-compiler for GameCube
- CMake 3.15+ or GNU Make
- C++17 compiler: gcc, clang, or MSVC for host tools
- Linux/macOS/Windows: All platforms supported
Method 1: System Install
# Debian/Ubuntu
wget https://github.com/devkitPro/pacman/releases/latest/download/devkitpro-pacman.amd64.deb
sudo dpkg -i devkitpro-pacman.amd64.deb
sudo dkp-pacman -S gamecube-dev
# macOS (Homebrew)
brew install devkitpro-pacman
dkp-pacman -S gamecube-dev
# Windows (WSL2 recommended)
# Follow Linux instructions in WSL2
Method 2: Docker
docker build -t dolhook-builder .
docker run -v $(pwd):/work dolhook-builder make all
Building
# Setup environment
source tools/env.sh
# Build everything
make all
# Or use CMake
mkdir build && cd build
cmake ..
make
Build Outputs
payload/payload.bin # 16KB runtime library (PPC)
payload/payload.elf # ELF with debug symbols
payload/payload.sym # Symbol map
tools/patchiso/patchiso # ISO patcher executable
Usage
Basic Patching
# Patch an ISO (creates .bak automatically)
./patchiso MyGame.iso
# Specify output path
./patchiso MyGame.iso --out MyGame.modded.iso
# Dry run (test without writing)
./patchiso MyGame.iso --dry-run
# Verbose output
./patchiso MyGame.iso --log 2
# Show DOL structure
./patchiso MyGame.iso --print-dol
Creating Hooks
Create hooks.c:
#include "dolhook.h"
static dh_hook g_my_hook;
// Your replacement function
static int my_function(int x, int y) {
// Call original via trampoline
typedef int (*OrigFunc)(int, int);
OrigFunc original = (OrigFunc)g_my_hook.trampoline;
int result = original(x, y);
dh_log("Called with %d, %d = %d\n", x, y, result);
return result * 2; // Modify behavior
}
// Install hooks (called by DolHook automatically)
void dh_install_all_hooks(void) {
// Find function by pattern
char pattern[] = {0x94, 0x21, 0xFF, 0xE0}; // stwu r1, -32(r1)
char mask[] = "xxxx";
void* target = dh_find_pattern((void*)0x80003000, 0x100000,
pattern, mask);
if (target) {
g_my_hook.target = target;
g_my_hook.replacement = my_function;
if (dh_hook_install(&g_my_hook) == 0) {
dh_log("Hook installed at 0x%08X\n", (unsigned)target);
}
}
}
Rebuild and re-patch:
make runtime
./patchiso MyGame.iso --out MyGame.hooked.iso
API Reference
Memory Operations
// Atomic writes with cache sync
void dh_write8(volatile void* p, uint8_t v);
void dh_write16(volatile void* p, uint16_t v);
void dh_write32(volatile void* p, uint32_t v);
// Cache management
void dh_icache_sync_range(void* addr, unsigned len);
// Interrupt control
uint32_t dh_suspend_interrupts(void);
void dh_restore_interrupts(uint32_t msr);
Function Hooking
typedef struct dh_hook {
void* target; // Function to hook
void* replacement; // Your handler
void* trampoline; // Call original via this
uint8_t saved[16]; // Saved prologue bytes
uint32_t patch_len; // 4 or 12 bytes
} dh_hook;
// Install/remove hooks
int dh_hook_install(dh_hook* h); // Returns 0 on success
int dh_hook_remove(dh_hook* h);
Pattern Scanning
// Find byte pattern with wildcards
void* dh_find_pattern(const void* start, size_t size,
const char* pattern, const char* mask);
// Example: Find "48 ?? ?? 01" (bl instruction)
char pat[] = {0x48, 0x00, 0x00, 0x01};
char mask[] = "x??x";
void* found = dh_find_pattern(start, size, pat, mask);
Branch Encoding
// Create near branch (±32MB)
uint32_t dh_make_branch_imm(uint32_t from, uint32_t to, int link);
// Create far branch (any distance, 12 bytes)
void dh_write_branch_abs(void* at, void* to, int link);
Video/Graphics (Advanced)
// Custom text rendering
void dh_draw_text(int x, int y, const char* text);
// Clear screen to black
void dh_clear_screen(void);
// Draw colored rectangle (RGB input, auto-converts to YUV)
void dh_draw_box(int x, int y, int w, int h,
uint8_t r, uint8_t g, uint8_t b);
// Access framebuffer
void* dh_get_xfb(void);
void dh_get_xfb_size(int* width, int* height);
Video Interface Implementation
DolHook includes a complete VI/XFB implementation that initializes GameCube video hardware from scratch:
Features
- Auto-detection: NTSC (480i @ 59.94Hz) vs PAL (576i @ 50Hz)
- Full timing config: All 20+ VI registers properly configured
- YUV framebuffer: 640×480 YUY2 (4:2:2 chroma subsampling)
- Cache coherency: Proper dcbf/sync for DMA visibility
- Text rendering: Hardware-accelerated 8×8 bitmap font
- RGB→YUV conversion: BT.601 standard for colored graphics
Technical Details
// VI Register Configuration (NTSC example)
VI_VTR = 0x0F06; // 262 lines/field
VI_DCR = 0x01F0; // Enable, interlaced, 16-bit
VI_HTR0 = 0x01AD0150; // Horizontal timing
VI_HTR1 = 0x00C3012C; // Horizontal blanking
VI_VTO = 0x00060030; // Vertical timing odd field
VI_BBOI = 0x005B0122; // Color burst blanking
// Framebuffer setup
VI_TFBL = xfb_physical_addr; // Top field base
VI_HSW = 640; // Horizontal width
VI_HSR = 0x0280; // Scaling ratio (1:1)
YUV Color Space
The framebuffer uses YUY2 format (ITU-R BT.601):
Byte layout: [Y0][U][Y1][V] - 4 bytes per 2 pixels
Y = Luma (brightness): 16 (black) to 235 (white)
U = Chroma Cb (blue): 128 = neutral
V = Chroma Cr (red): 128 = neutral
White pixel: Y=235, U=128, V=128
Black pixel: Y=16, U=128, V=128
Examples
Example 1: Hook OSReport
static dh_hook g_osreport_hook;
void my_osreport(const char* fmt, ...) {
typedef void (*OSReportFunc)(const char*, ...);
OSReportFunc orig = (OSReportFunc)g_osreport_hook.trampoline;
orig("[DolHook] "); // Prepend tag
orig(fmt); // Call original
}
void dh_install_all_hooks(void) {
// Find OSReport: stwu r1,-X(r1); mflr r0
char pat[] = {0x94, 0x21, 0x00, 0x00, 0x7C, 0x08, 0x02, 0xA6};
char mask[] = "xx??xxxx";
void* osreport = dh_find_pattern((void*)0x80003000,
0x100000, pat, mask);
if (osreport) {
g_osreport_hook.target = osreport;
g_osreport_hook.replacement = my_osreport;
dh_hook_install(&g_osreport_hook);
}
}
Example 2: Draw Custom HUD
void dh_install_all_hooks(void) {
// Draw FPS counter
dh_draw_text(500, 20, "FPS: 60");
// Draw colored health bar
dh_draw_box(20, 20, 200, 10, 255, 0, 0); // Red bar
// Draw debug info
dh_draw_text(20, 40, "Position: 123.45, 67.89");
}
Example 3: Memory Patch
void dh_install_all_hooks(void) {
// Patch a hardcoded value
uint32_t* lives_addr = (uint32_t*)0x80345678;
dh_write32(lives_addr, 99); // Infinite lives
// Patch an instruction (NOP out a branch)
dh_write32((void*)0x80123456, 0x60000000); // NOP
}
Configuration
Build Options
# Minimal build (no banner, no patterns)
make DOLHOOK_NO_BANNER=1 DOLHOOK_NO_PATTERN=1
# Disable only banner
make DOLHOOK_NO_BANNER=1
# Disable only pattern scanning
make DOLHOOK_NO_PATTERN=1
# Force video mode
make DOLHOOK_FORCE_NTSC=1
make DOLHOOK_FORCE_PAL=1
Size Budget
| Configuration | Code | Data | BSS | Total |
|---|---|---|---|---|
| Full (default) | 16KB | 1KB | 614KB | ~631KB |
| No banner | 8KB | 512B | 16KB | ~24KB |
| Minimal | 6KB | 512B | 16KB | ~22KB |
Note: BSS includes the 614KB framebuffer (only allocated when VI fallback is used).
Platform Support
Tested On
- ✅ GameCube (real hardware)
- ✅ Dolphin Emulator (5.0+)
- ✅ Wii (GameCube mode)
- ✅ Nintendont (Wii U, Wii)
Video Modes
- ✅ NTSC (480i @ 59.94Hz) - North America, Japan
- ✅ PAL (576i @ 50Hz) - Europe, Australia
- ✅ Auto-detection via VI registers
Troubleshooting
"devkitPPC not found"
source tools/env.sh
# Or manually:
export DEVKITPRO=/opt/devkitpro
export DEVKITPPC=$DEVKITPRO/devkitPPC
export PATH=$DEVKITPPC/bin:$PATH
"Banner not showing"
The banner has two modes:
- OSReport (if available) - instant, no VI needed
- VI fallback - full hardware init
Check Dolphin logs for "Patched with DolHook" text output.
"Game crashes after patch"
- Verify backup:
ls -lh MyGame.iso.bak - Try dry run:
./patchiso MyGame.iso --dry-run - Check DOL structure:
./patchiso MyGame.iso --print-dol - Restore backup:
cp MyGame.iso.bak MyGame.iso
"Payload too large"
# Build without banner
make clean
make DOLHOOK_NO_BANNER=1
# Or minimal build
make clean
make DOLHOOK_NO_BANNER=1 DOLHOOK_NO_PATTERN=1
Performance
Measured on Super Smash Bros. Melee:
| Metric | Impact |
|---|---|
| Boot time | +50ms (with VI init), +1ms (OSReport) |
| Runtime overhead | ~0.1% per hook |
| Memory footprint | 631KB (with VI), 24KB (without) |
| Frame time | <0.01ms per hooked function |
Technical Specifications
PowerPC Assembly
DolHook uses hand-written PowerPC assembly for critical sections:
# Entry stub (entry.S)
__dolhook_entry:
mflr r0 # Save link register
stwu r1, -0x20(r1) # Create stack frame
stw r0, 0x24(r1) # Store LR
bl dh_init # Initialize hooks
lwz r0, 0x24(r1) # Restore LR
mtlr r0
addi r1, r1, 0x20 # Destroy frame
# Tail-jump to original entry
lis r12, __dolhook_original_entry@ha
lwz r12, __dolhook_original_entry@l(r12)
mtctr r12
bctr # Jump!
Cache Coherency
// Proper cache flush for code patching
void dh_icache_sync_range(void* addr, unsigned len) {
uint32_t start = (uint32_t)addr & ~31;
uint32_t end = ((uint32_t)addr + len + 31) & ~31;
// Flush data cache
for (uint32_t p = start; p < end; p += 32) {
asm volatile("dcbf 0, %0" : : "r"(p));
}
asm volatile("sync");
// Invalidate instruction cache
for (uint32_t p = start; p < end; p += 32) {
asm volatile("icbi 0, %0" : : "r"(p));
}
asm volatile("isync");
}
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Ensure
make testpasses - Keep payload under 32KB budget
- Update documentation
- Submit a pull request
Development Setup
git clone https://github.com/apfelteesaft/dolhook.git
cd dolhook
source tools/env.sh
make all
make test
Code Style
- C99 for runtime (PPC)
- C++17 for host tools
- 4 spaces, no tabs
- Clear comments for all public APIs
- Keep functions under 100 lines when possible
License
MIT License - see LICENSE file for details.
Legal Notice
⚠️ Important: This toolkit is for personal research and modification of legitimately owned game backups only.
It may be illegal in your jurisdiction to:
- Distribute modified commercial game content
- Bypass copy protection mechanisms
- Use this software for piracy
- Modify games you do not own
The authors:
- Do NOT condone piracy or copyright infringement
- Provide this software for educational purposes only
- Accept NO responsibility for misuse
- Make NO warranties about fitness for any purpose
USE AT YOUR OWN RISK.
Credits
- devkitPro Team - PowerPC toolchain
- GameCube/Wii Homebrew Community - Hardware documentation
- WiiBrew - DOL and GCM format specifications
- Yet Another Gamecube Documentation - VI register reference
Links
- 🐛 Issue Tracker
- 💬 Discussions
- 📚 Examples
Made with ❤️ for the GameCube homebrew community