Skip to lesson
Game Hacking Academy
Preparing lesson text…
Exit reader mode
Voice and speed

Preparing read-aloud controls…

Lesson 2.7 Listening edition

Why Addresses Move

Understand dynamic allocation, module bases, ASLR, and why a pointer path survives when one raw address does not.

About 18 minutes

Section: The disappearing-address problem.

You find gold at this value, restart the game, and the address is different. Nothing is broken. The address was never a permanent identity.

Two common reasons are:

dynamic allocation — the game asks for memory while it runs;

A S L R, address space layout randomization,, short for Address Space Layout Randomization — Windows loads modules at different base addresses between runs.

Virtual addresses belong to one process. The same numeric address in two different processes can name completely different physical memory, and a freed address can later be reused for another allocation in the same process.

Every address involves three separate things, location, identity, and lifetime:

a location is the virtual address used for this access;

an identity is the game object or field that lives there;

a lifetime is the interval during which that mapping remains valid.

An address gives only the first item. The code that uses it, the objects that point to it, and repeated access patterns show the identity. Allocation and game events determine the lifetime. A reusable tool must rediscover or validate the location instead of treating yesterday’s address as permanent identity.

In brief: An address is only a location for this run. Identity and lifetime come from the code and objects around it.

Section: Relative locations.

A S L R can load a module at a different base address on another run. Within one exact executable build, a global’s relative virtual address (R V A) in the image is fixed even when the module base changes. The loader maps sections at their defined relative locations and applies relocations where needed. Thus a tool can rediscover the base and add the verified R V A; it must not assume A S L R picks a new base on every launch or that a rebuilt game keeps the same R V A:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

If wesnoth.exe loads at this value and a global pointer is at offset this value, the current address is:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

usize is the integer type normally used for an address-sized number:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

checked_add returns None if the addition overflows instead of silently wrapping.

Alternative: Think of a tape measure: the starting mark moves each run, but a feature stays the same distance from it. Base plus offset gives today’s place.

Section: Pointer chains.

A stable module location may hold a pointer to an object, which holds a pointer to another object, which contains gold:

Lesson diagram

Each arrow that says “read pointer” means:

calculate an address;

read a pointer-sized value from it;

treat the result as the next address.

The last step may be a field address rather than another pointer.

Pointer size follows the target architecture. A 32-bit process stores a pointer in four bytes; a 64-bit process normally uses eight. Reading eight bytes from a 32-bit pointer field consumes neighboring data, while reading four from a 64-bit pointer truncates the address. The reader must use the target’s bitness, not the host’s.

At every link, distinguish three different numbers:

the address where a pointer is stored;

the pointer value read from that address;

the field offset added to that pointer value.

Writing the chain as numbered equations makes accidental double-dereferences visible.

Walked with real numbers, the chain in the diagram above looks like this:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Notice the rhythm: add, read, add, read — and then at the last step, add and stop. That final + hexadecimal 3 0 produces the address of gold, not a pointer to somewhere else. Reading a pointer there instead would take the number 250, treat it as an address, and try to read memory at this value.

That is the “one dereference too many” mistake, and it has a recognizable signature: the error names a very small address. Ordinary game values — health, gold, ammunition — are small numbers, so when one is mistakenly used as an address it points at the very bottom of the address space, where nothing is mapped. An error mentioning an address below a few thousand almost always means you dereferenced a value instead of a pointer.

Predict first

Restart the game. Windows loads wesnoth.exe at this value this time, and the game allocates its manager and player objects at new heap addresses.

You walk the same path: root offset this value, then + hexadecimal 1 8, then + hexadecimal 3 0. Where do you end up?

Answer: At this run’s gold field. The root slot moved with the module to this value + this value equals this value, and the game stored this run’s manager address in it. Each read picks up a pointer the game wrote in this run, so the walk follows the objects to their new places.

One pointer path, three runs of the game.

Section: Pointer scan results need checking.

Cheat Engine can search for pointer paths automatically:

Cheat Engine’s pointer scan feature

A result is not automatically correct. Validate it across several clean restarts. Prefer paths that:

begin inside a known module;

use a short chain;

work on the exact documented game version;

end at the expected value;

fail safely when a pointer is null or unreadable.

A pointer scan finds numeric relationships that existed in its snapshots. It does not prove ownership. Large offsets, very long chains, and links through short-lived allocations are more likely to be coincidences. Normal gameplay tests—opening menus, changing maps, loading a save—exercise those ownership assumptions.

Narration: A path that breaks after you load a new map probably ran through a short-lived object. Testing across menus and loads exposes that before you rely on it.

Section: Give addresses a checked type.

Do not scatter raw address math around a program. Give it a type:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

This wrapper does not prove that an address is readable. It does stop us from accidentally mixing an address with an ordinary game value, and it gives one place for overflow checks.

The operating-system call that reads another process still belongs behind a carefully reviewed unsafe boundary. The rest of the pointer-chain algorithm can remain safe, ordinary code.

Section: Walk a chain without losing the meaning.

Use a function whose name says whether the final offset is dereferenced:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

The final field address is returned without reading another pointer. Keeping that distinction in the function signature and variable names prevents the common “one dereference too many” mistake.

The change from loose arithmetic to a deliberate chain is easier to see as a diff:

The original helper adds the root offset, two link offsets and the field offset as if they were all distances inside one allocation. Here those arguments describe a two-link pointer path: each intermediate slot stores the address of another object.

Before: add every offset without following pointers

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Separate address arithmetic from dereferencing. At each link, calculate the slot, read the pointer stored there, and reject null; only the final field is read as gold. These offsets are inputs recovered for your own exact lab build, not universal game addresses.

Improved code

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Show the diff

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Takeaway: adding an offset finds a slot; reading the pointer in that slot finds the next object.

Offsets describe different jobs. root_offset locates the first pointer, link offsets locate the next pointer, and field_offset locates data inside the final object. Naming those stages prevents addition from being mistaken for dereferencing. It also places an error at the exact link that failed instead of returning one mysterious “bad address” message.