Blog

Getting started with Cheat Engine!

Are you stuck value-scanning? Try this instead!

Author avatar Leunsel 26.02.2026 7 min read

Pointer Script Basics in Cheat Engine

Ok, you want to create your first small Cheat Table, but you don't really know where to start? Or maybe the tutorials floating around the internet feel overly complicated, skip reasoning, or just give you a checklist. This post is meant to do the opposite: not "do X, then Y", but why X works, when it fails, and what mental model to apply. You should walk away understanding the concept, not believing you discovered a universal recipe.

Assumption

This post assumes you already know how to scan for simple values and edit them with Cheat Engine. The examples shown here are based on The Binding of Isaac: Rebirth (native code, not Mono/JIT).

You found some values… now what?

You scanned for coins, health, bombs, whatever you care about, and Cheat Engine found addresses that work. You freeze, you change them, it behaves exactly as expected. Then you restart the game and suddenly your addresses are wrong: not "a little off", but completely unrelated memory. This is the first key idea:

You did not find "the coin variable". You found "where the coin variable happens to live right now".

In most games, those are dynamic addresses: allocations created at runtime. Dynamic allocation means the same logical object (the player) can exist at a different memory location each run. So the problem is not "Cheat Engine is unreliable". The problem is that you stored a location, not an identity.

Raw values found in Cheat Engine

The mindset: stop chasing data, start tracking relationships

If you approach reverse engineering like "scan » freeze » done", you're treating memory like a spreadsheet: each value has a fixed cell. Real programs behave more like this: values live inside objects (structures/classes), objects are allocated and move between sessions, and code knows how to reach them (relationships). Pointer scripts work because they capture those relationships — not "address of coins", but "how the game reaches coins". That distinction is the entire foundation for stable tables.

Why not just pointer scan?

Technically you can pointer-scan. Pointer scan is not "wrong", it's just often the least structured solution for small/medium tables. Typical outcomes: long pointer chains that you don't understand, multiple candidates that work today but break tomorrow, and tables that become hard to maintain. Pointer scanning answers "can I reach this address?" but not "why does this address exist?" Pointer scripts anchor in something the program relies on: the instructions that access your value.

A more stable approach exists

Instead of chasing dynamic storage, anchor yourself in something static: the game's access logic (instructions).

Dynamic values vs. static instructions

The values you scan for are dynamic because they're data. Code is different. For native (non-JIT) games, executable code typically sits in stable module memory: the module base can shift due to ASLR, but the code layout inside the module tends to remain consistent unless the game updates. This gives you a reliable anchor: if you can find the instruction again, you can recover the data path again.

  • Data moves: allocations differ between runs
  • Code is stable: instructions usually remain at the same relative place

Find out what accesses your value

Right-click your memory record and use Find out what accesses this address (or "writes", depending on the case). In this example we want general access. Assume we get something like push [eax+0000135C]. That may look ugly, but it's extremely informative: it reveals the program's own model for this data.

Access results window

Breaking down the instruction

push [eax+0000135C]

Don't treat this as syntax to memorize. Treat it like a sentence that encodes a relationship. push moves a value onto the stack (often for a call), and [eax+0000135C] is a memory read where the address is computed from EAX. The important part is:

The value is read from the address: EAX + 0x135C

This implies two immediate facts: EAX is meaningful runtime context, and 0x135C is a displacement inside something larger (a field inside an object-like layout).

What is EAX (and what it is NOT)

EAX is a register. Registers are CPU storage locations used to execute instructions quickly. In x86 (32-bit), common general-purpose registers include EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP. Historical names exist (EAX "accumulator", ECX "counter", etc.), but modern compilers treat most registers as general-purpose temporaries. The key rule:

A register does not "mean" anything by name. It only means what the surrounding code uses it for.

The useful cheat-table mindset is: a register often holds a temporary reference to an active game object, and the instruction tells you which object and which field is accessed. So when you see [eax+135C], think: "EAX likely points to an object base; 0x135C selects a member."

  • EAX, frequently used for returns/arithmetic, commonly appears in code
  • EBX, general-purpose, sometimes used as base in older patterns
  • ECX, often used for loops/call args
  • EDX, paired with EAX in mul/div, also general-purpose
  • ESI/EDI, common in iteration/copy patterns
  • EBP/ESP, stack-frame / stack top (compiler + calling convention dependent)

What is the offset (0x135C)?

0x135C is an offset (displacement). It is a fixed byte distance from a base address, not a pointer chain. The CPU computes the effective address as:

EffectiveAddress = EAX + 0x135C

Offsets are how compiled code accesses fields. That is why they are often stable across sessions.

Understanding structures (the practical mental model)

When you see [eax+135C], the simplest useful assumption is: EAX points to the start of a structure (or class instance). A structure is a contiguous block of memory with fixed-layout members. Visualization:

BaseAddress (EAX)
+ 0000 » Health
+ 0004 » MaxHealth
+ 0008 » Coins
+ 000C » Bombs
...
+ 135C » Our value

If you prefer a language analogy, here's the same idea in C++ (conceptual mapping, not the real game struct):

Struct.cpp
struct Player { int health; // 0x0000 int maxHealth; // 0x0004 int coins; // 0x0008 int bombs; // 0x000C // ... }; // Runtime mental model: Player* p = (Player*)EAX; int value = p->target;
Correct takeaway

Pointer scripts are not "finding the right address". They capture a stable object reference during execution and then apply an offset like a field access.

Making it stable with an AoB Scan

We rely on two stability assumptions: the instruction remains findable (signature), and the register at that moment still holds the correct object base. AoB scanning does not search for your value, it searches for your anchor instruction by its byte pattern.

aobscanmodule(PlayerAccess, isaac-ng.exe, 50 8B 80 5C 13 00 00)
registersymbol(PlayerAccess)

This yields a stable code symbol even if runtime allocations change.

Capturing the structure base (the critical step)

If EAX is the base of the player structure, the one thing you want to preserve is the value of EAX at the moment the game accesses the field. Injection does not "make the value static". It stores runtime context so you can reuse it.

PlayerHook.CEA
[ENABLE] aobscanmodule(PlayerHook,isaac-ng.exe,FF B0 5C 13 00 00) // should be unique alloc(newmem,$1000) label(code) label(return) label(PlayerPtr) newmem: mov [PlayerPtr],eax code: push [eax+0000135C] jmp return PlayerPtr: dd 0 PlayerHook: jmp newmem nop return: registersymbol(PlayerHook) registersymbol(PlayerPtr) [DISABLE] PlayerHook: db FF B0 5C 13 00 00 unregistersymbol(PlayerHook) dealloc(newmem)

What this effectively creates: a stable way to locate code (AoB), capture runtime context (PlayerPtr), and compute a field (PlayerPtr + offset). Your Cheat Table entry becomes [PlayerPtr]+135C.

Why this is NOT a universal recipe

This method is robust when the access instruction reliably observes the authoritative object base. Games are not obligated to keep that model true. Common failure modes include temporary objects (EAX points to a copy), multiple contexts (same field accessed via different bases), aggressive register reuse (good base exists briefly), or computed values (derived, not stored as a field). The correct takeaway:

Every pointer script is a hypothesis: "this register is the base of the object I want, here."

A script that "sometimes works" is often worse than one that fails immediately, because it creates false confidence. Treat pointer scripting as model-building + validation, not a rote sequence.

Verification habits (reliable tables vs. lucky ones)

If you want tables that survive more than one session, verify assumptions deliberately:

  • AoB uniqueness: non-unique signatures hook the wrong logic later
  • Base stability: compare PlayerPtr across transitions (menus, rooms, new run)
  • Field behavior: offsets behave like fields (consistent correlation with game state)
  • Authority: ensure you edit the authoritative storage, not a cached copy

Hook » capture » validate » rely. If you skip validation, you're gambling.

Final thoughts

Pointer scripts are a disciplined way to translate runtime behavior into stable references: registers as temporary object references, offsets as structure fields, AoB as a stable anchor to logic. Once you internalize that, cheat tables stop being address-chasing and start becoming structured, maintainable models of how the game stores and accesses state.