How Pointer Paths Survive Restarts
Trace the player object back to a verified starting point and prove the pointer path across restarts.
Cheatsheet for Lesson 2.8
A one-page summary of “How Pointer Paths Survive Restarts” you can keep beside your notes. Plain Markdown.
Lesson 2.8 — How Pointer Paths Survive Restarts: cheatsheet
- Remember: A pointer path is a route through objects. Check every step, not only the final value, and note the conditions it depends on.
- Also useful: Read the path like treasure-map directions: go to the first spot, read the note there, follow it, then take a few steps along.
Key terms
eax: At entry,eaxis zero; after stepping over the call, it holds the side-record pointer.
Review
- Q: What is the strongest evidence that a pointer path is useful? A: The pinned build's starting address and dereference steps resolve the intended object across clean restarts
- Q: At a breakpoint, an instruction reads add dword ptr [eax+4], edx. Which description of the registers fits? A: eax points at a record, and edx is the amount added to its field at +4
Start from behavior
Section titled “Start from behavior”Use offline, 32-bit Wesnoth 1.14.9. Find the current player’s gold again and set a write breakpoint on its four bytes. Instead of recruiting, end a turn so income changes that value.

When the breakpoint fires, inspect the instruction that reached the gold field. In the 32-bit Wesnoth 1.14.9 build used by this book, income reaches:

009B4D00 add dword ptr [eax+4], edxRead that line like a sentence: “add the income in edx to the 32-bit number four bytes after the object in eax.” eax points at the current side record, eax + 4 is its gold field, and edx contains the income.
These addresses are exact only for Wesnoth 1.14.9 32-bit. If your instruction differs, use your build’s values instead of forcing these numbers onto it.
Trace the object pointer
Section titled “Trace the object pointer”Set a breakpoint on the call above the income write. At entry, eax is zero; after stepping over the call, it holds the side-record pointer. Step into that call on the next turn. The useful path begins with:
mov eax, dword ptr [ecx+60]; later, follow that pointer and add 0xA90; the gold field is another 4 bytes into the side recordAt 0x009AE7F7, read ecx. Restart Wesnoth, recreate the same local match, and break there again. In this book’s build, ecx remains 0x017EECB8. The complete path is:
[[0x017EECB8 + 0x60] + 0xA90] + 0x4Because 0x017EECB8 + 0x60 is 0x017EED18, later labs use the shorter form:
[[0x017EED18] + 0xA90] + 0x4Square brackets mean “read the pointer stored here.” A plus sign means “move this many bytes from that address.”
A pointer path is a route through an object graph
Section titled “A pointer path is a route through an object graph”Each dereference moves from one object to another. The observed root
0x017EED18 is an absolute address for the pinned course build, not an
RVA. Confirm that it still points into the expected module in your run. A
portable profile would store the root’s RVA and first discover the module’s
current base, as Lesson 2.7 explained. The heap objects reached afterward may
move between runs, which is why the path reads their current addresses rather
than saving yesterday’s final address.
A path is stable only under stated conditions. It may depend on:
- the exact executable build;
- a local match being active;
- the current player or side object existing;
- the same layout and pointer width;
- each intermediate page being readable at the moment of use.
Validate every edge, not only the final gold. A non-null pointer can still be stale or point to the wrong object. Check module ranges, page protections, alignment, and a few field invariants before trusting the final value.
Build the chain manually
Section titled “Build the chain manually”In Cheat Engine, add a pointer entry by hand using the module and the offsets you observed.

Be careful about order. A displayed list of offsets may be applied from the module outward or shown in reverse depending on the tool. For this exact build, enter base address 0x017EED18, first offset 0xA90, and final offset 0x4. The resolved value should equal the gold shown in the game.

Restart tests
Section titled “Restart tests”One successful run proves very little. Test:
- close Wesnoth completely;
- start it again;
- begin a new local match;
- confirm the pointer entry resolves;
- repeat at least three times.
Also test a state where no match is active. A good tool should report “player object unavailable,” not crash or read address zero.
Write the algorithm before the API
Section titled “Write the algorithm before the API”The code follows this plain algorithm:
read the 32-bit pointer stored at 0x017EED18if it is null, stop with a useful erroradd 0xA90 and read the next 32-bit pointerif it is null, stop with a useful erroradd the final gold-field offsetread the 32-bit gold valueWe can model failures precisely:
#[derive(Debug)]enum ResolveError { ModuleNotFound, NullPointer { step: usize }, AddressOverflow, ReadFailed { address: usize },}Here is the resolver without any Windows API noise. read_u32 can use ReadProcessMemory in an external tool or direct reads inside an injected DLL:
const PLAYER_ROOT: usize = 0x017E_ED18;const GAME_OFFSET: usize = 0xA90;const GOLD_OFFSET: usize = 0x4;
fn wesnoth_gold_address( mut read_u32: impl FnMut(usize) -> Result<u32, ResolveError>,) -> Result<usize, ResolveError> { let player = read_u32(PLAYER_ROOT)? as usize; if player == 0 { return Err(ResolveError::NullPointer { step: 0 }); }
let game_slot = player.checked_add(GAME_OFFSET) .ok_or(ResolveError::AddressOverflow)?; let side = read_u32(game_slot)? as usize; if side == 0 { return Err(ResolveError::NullPointer { step: 1 }); }
side.checked_add(GOLD_OFFSET) .ok_or(ResolveError::AddressOverflow)}The Result type makes failure data we must handle, not a surprise we hope never happens.
This interactive lab needs JavaScript. The lesson text around it still explains the same idea.
What counts as “stable”
Section titled “What counts as “stable””The path is stable only for the target version and states you tested. The absolute root in this example also depends on the module loading where it did in the verified course run; a relocated module requires base-plus-RVA resolution. Game updates can change layouts and offsets. Document:
- game version and architecture;
- module name;
- offsets in traversal order;
- the expected final type;
- how many restarts passed;
- known states where the chain is unavailable.
Chapter 3 turns this verified path into an external program. First, the final two lessons in this chapter ask a different question: what must be preserved when execution takes a temporary detour through new instructions?
Prove stability
What is the strongest evidence that a pointer path is useful?
This lab starts from a build-specific absolute address, then follows offsets and dereferences. Clean restarts test whether that path remains useful in the pinned build. A separately verified module base plus RVA would be a more portable starting design.
Finished Lesson 2.8?
Mark it done and the lesson list shows a beside it. A chapter whose lessons are all done is marked done too. Progress is saved in this browser only.
Plain Markdown, saved in this browser.
This book grew out of the original Game Hacking Academy by attilathedud and has been rewritten as a beginner-first course. Like the original repository, its source is published under the Apache License 2.0.