Pointers

In a running program, most values you care about are reached through a pointer chain β€” a static base address plus a series of offsets that ultimately land on the value. Cheat Engine’s β€œpointer scan” feature is built around this idea, and PyMemoryEditor offers the same workflow:

  • resolve_pointer_chain β€” walk a chain you already know.

  • RemotePointer β€” a live, re-resolving handle that re-walks the chain on every read/write.

  • scan_pointer_paths β€” the reverse operation: find chains that resolve to a given address. See Pointer scan.

Why pointer chains?

A scanned address (0x1FA3C140) typically changes every run: the OS loads modules at randomized base addresses (ASLR), and the heap allocates objects in different places each time you launch the program.

A multi-level pointer, in contrast, expresses a value as module + offset β†’ [+x] β†’ [+y] β†’ …. The static base (the module + static offset) doesn’t change between runs once ASLR is accounted for, so the same recipe works every time.

Walking a chain

resolve_pointer_chain performs the walk and returns the final address where the value lives:

# Cheat-table entry:  "game.exe" + 0x10F4F4 -> [+0x0] -> [+0x158]
module = next(m for m in process.get_modules() if m.name == "game.exe")
base = module.base_address + 0x10F4F4

hp_address = process.resolve_pointer_chain(base, [0x0, 0x158])
hp = process.read_int(hp_address)

Method signature

resolve_pointer_chain(base_address, offsets, *, ptr_size=None)

Walk a multi-level pointer chain.

Reads ptr_size bytes at base_address to obtain the first pointer, then for each offset in offsets[:-1] adds the offset and dereferences again. The last offset is added without dereferencing β€” the returned integer is the final address where the value of interest lives.

Parameters:
  • base_address (int) – starting address β€” typically module_base + static_offset.

  • offsets (Sequence[int]) – sequence of offsets to walk. Pass [] to dereference base_address once and return that pointer.

  • ptr_size (int) – pointer width β€” 8 for 64-bit targets, 4 for 32-bit. Leave None (the default) to use the target’s pointer_size, detected automatically.

Returns:

the final address (an int).

32-bit vs 64-bit

By default (ptr_size=None) the pointer width is detected from the target β€” 4 bytes for a 32-bit process, 8 for 64-bit. Pass ptr_size explicitly only to override that; setting it to the wrong width reads pointers of the wrong size and yields garbage addresses.

Live pointers: RemotePointer

resolve_pointer_chain finds an address once. A RemotePointer wraps the same recipe in a reusable handle β€” every time you read .value, the chain is re-walked, so the handle keeps working even as the target moves things around the heap.

# A handle to the player's HP, behind a two-level pointer.
hp_ptr = process.get_pointer(
    base + 0x10F4F4,
    [0x0, 0x158],
    pytype=int,
    bufflength=4,
)

print(hp_ptr.value)   # read it
hp_ptr.value = 9999   # write it

process.get_pointer(...) is a convenience wrapper around the RemotePointer constructor β€” it accepts the same arguments (base_address, offsets, pytype, bufflength, ptr_size).

Direct vs chained handles

The offsets argument controls what RemotePointer does on every access:

offsetsBehavior
None (default)Direct handle: address = base_address, no dereferencing. Use this to wrap an address you already have (e.g. from search_by_value).
[] (empty list)Dereferences base_address once and reads the value at that pointer.
[o1, o2, ...]Walks the chain on every access β€” resolve_pointer_chain semantics.

Pointer arithmetic

RemotePointer supports C-style arithmetic. Adding an integer returns a new handle, without touching memory:

# Mana is stored right after HP, so just step 4 bytes forward.
mp_ptr = hp_ptr + 4
print(mp_ptr.value)

You can also subtract two pointers to get a byte distance:

distance = mp_ptr - hp_ptr   # 4

RemotePointer API

class RemotePointer(process, base_address, offsets=None, *, pytype=int, bufflength=None, ptr_size=None)

A re-resolving, read/write handle to a typed value in a target process.

property process

The AbstractProcess this pointer reads from / writes to.

property base_address

The starting address the pointer was built with.

property offsets

The pointer-chain offsets, or None for a direct handle.

property address

The address the value currently lives at β€” recomputed on every access for a pointer chain.

property value

Read or write the value at address using the bound type.

read(pytype=None, bufflength=None)

Read the value at address, optionally overriding the bound type for one-off reads.

write(value, pytype=None, bufflength=None)

Write value to address, optionally overriding the bound type.

__add__(delta)

ptr + n β†’ a new pointer n bytes ahead.

__sub__(other)

ptr - n β†’ a new pointer n bytes behind. ptr - other (where other is a RemotePointer) β†’ the byte distance between the two resolved addresses.

__int__()

The resolved address β€” handy for arithmetic and logging.

See also