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

Preparing read-aloud controls…

Lesson 11.5 Listening edition

Snapshots as a Script Interface

Immutable, versioned observations and bounded action requests connect Lua to game state without exposing arbitrary pointers.

About 33 minutes

Section: Do not turn Lua into an untyped memory A P I, application programming interface.

After building a memory reader, it is tempting to expose this:

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

That design gives a typo the power of the process handle. It also couples scripts to exact offsets and makes every mod responsible for pointer validation.

Expose domain meaning instead:

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

Section: The host owns collection and validation.

The Windows layer should:

open the target with the least required rights;

verify game build and architecture;

resolve versioned roots and pointer paths;

copy fields with bounds and plausibility checks;

create ordinary owned records;

close or retain handles according to one clear owner.

Only step 5 crosses into Lua.

The boundary turns unstable bytes into stable meaning, then turns script intent back into checked data:

Lesson diagram

Addresses stop at the memory reader; Lua receives fields with names, units, and a snapshot version.

In brief: Expose named, validated values to Lua, and keep build-specific addresses inside the host observer.

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

There are no process addresses in this type. A script cannot keep a stale pointer because it never receives one.

Why avoid raw pointers here? Lua can represent native objects through userdata. Full userdata has Lua-managed storage and a host-defined metatable; light userdata is essentially an unowned pointer value. If a native entity dies while a script retains its raw address, the same address may later belong to a different entity. A copied snapshot avoids that lifetime problem for observation. When a request needs persistent identity, use a validated handle rather than a remembered address.

A handle can pair a numbered slot with a generation counter. Every time a slot is reused for a new entity, its generation changes. Suppose Lua remembers slot 3, generation 6. If that entity dies and slot 3 is reused at generation 7, the host rejects the stale pair rather than acting on the new occupant:

The slot number stays 3, the generation moves from 6 to 7.
Lesson diagram

Lesson 3.10 developed the same identity rule for a game collection. Here it protects the boundary between a script’s request and a changing game world.

Here is one entity as a hypothetical build lays it out in game memory, and the same entity after the observer has copied it into a snapshot and the host has turned that into a Lua table:

Labelled memory cells
Labelled memory cells

Section: A snapshot has a time and version.

Real snapshots should carry metadata:

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

Section: Put an abstraction barrier between bytes and game meaning.

An abstraction barrier is a rule about who may know a representation. The observer below the barrier knows that one build stores an entity position at a particular pointer path. Lua above the barrier knows only that an entity has an id, name, position, and alive state.

Alternative: The abstraction barrier is a translation desk: scripts ask about entities, while the observer translates those names into one build’s storage layout.

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

If a later build moves the position field, only the observer and its target profile should change. Every script should not learn a new offset.

Make the source of snapshots replaceable too:

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

A live Windows observer and a replay-file reader can both implement SnapshotSource. The same Lua state machine can then run against recorded data in a test and against the verified game in the lab. That is not just reuse: it lets you test automation without giving the test a process handle.

When Lua requests an action, include the sequence it observed:

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

The host can reject a request based on a snapshot that is too old. This is safer than pretending copied data is live.

Lesson diagram

The script did nothing wrong: entity 7 really was alive in snapshot 41. The game moved on while the script was deciding, and based_on lets the host see that the request describes a moment that has already passed.

Keep the snapshot; refuse the stale action

Section: Requests are data, not commands.

Bad interface:

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

Better interface:

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

The host matches an explicit enum:

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

Unknown kind values fail. No string becomes PowerShell, a console command, or arbitrary game code.

Section: Validate again at action time.

Between snapshot and request processing, an entity may disappear. Check:

the sequence is recent;

the entity still exists;

its current state permits the action;

all numbers are finite;

coordinates remain within known map bounds;

the per-script action budget is not exhausted.

This is a time-of-check/time-of-use issue. Validation during snapshot creation does not guarantee future state.

Narration: A unit can disappear after the snapshot was copied. Rechecking its identity when processing a request prevents acting on an outdated observation.

Section: Keep read and write capabilities separate.

Most analysis scripts need only snapshots and logs. Give them an observer A P I with no request function at all.

When an offline automation lab needs actions, load it with a different capability set:

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

Do not decide permissions from a script-provided name. The host chooses the A P I before executing the script.

Section: Test the boundary without a game.

The lua-labs host uses a simulated snapshot. Add tests where Lua requests:

a missing entity ID;

0/0 or infinity as a coordinate;

an unknown action;

too many actions;

an action from an expired sequence.

The host should reject each request with a useful error and remain alive.

This architecture also makes the script portable. The same Lua logic can consume a recorded replay, a simulated world, or a live observer because it depends on the snapshot contract rather than Windows memory details.