-
Notifications
You must be signed in to change notification settings - Fork 193
Pointers
Pointers are variables that store memory addresses.
Normal variables store values. For example, an int can store a player's HP amount, ammo amount, money amount, etc...
A pointer stores where something is instead. So instead of saying "the HP is 100", a pointer says "the HP is over there at this address".
This is useful because programs constantly pass addresses around internally. They don't always copy the whole player object or enemy object everywhere. They often keep a pointer to it.
You can find a more in-depth explanation about pointers here.
When you scan for a value in PINCE, you find the address where that value currently lives.
The annoying part is that the address is often temporary.
Most games don't keep the player's HP in one fixed address forever. They create objects while the game is running. A player object might be allocated when you load a save, an enemy object might be allocated when the enemy spawns, an inventory object might be allocated when the inventory is opened.
Because of that, the address can change after:
- Restarting the game
- Loading another save
- Changing map / level
- Dying and respawning
- Going back to main menu
So you might find HP at 0x7f1234567000, add it to the table, restart the game and now that address is useless.
That's where pointers come in. The value moved, but there may be a path in memory that still leads to it.
A pointer chain is a path made from a base address and offsets.
For example:
game.exe+0x123456 -> 0x18 -> 0x20 -> 0x8
This means something like:
- Start at
game.exe+0x123456 - Read the pointer stored there
- Add
0x18, then read the pointer there - Add
0x20, then read the pointer there - Add
0x8, this gives the final value address
The exact chain depends on the program. Sometimes it's only one offset. Sometimes it's a few levels deep.
You will often see bases like this:
game.exe+0x123456
This is better than a plain address like:
0x7f1234567000
The module itself can move when the game starts again, but PINCE can still find game.exe and add 0x123456 to the new module base.
In PINCE, a bare name like game.exe means the module's logical load base. It does not mean game.exe[0], which explicitly selects the start of the first raw mapped region. They often have the same address when the first mapping starts at file offset zero, but they can differ when the beginning of the image is not mapped, including some WINE layouts.
If two mapped files have the same basename, use the full mapped path for the logical module base instead of the ambiguous short name. See GDB Expressions for the full module and mapping-index syntax.
Plain heap or stack addresses are more fragile because they depend on runtime allocation order.
This doesn't mean every module based pointer is good. It just means it has a better chance to survive restart.
Pointers are not magic permanent addresses.
A chain can break if the game changes how it stores things, if the object doesn't exist yet or if the chain was only pointing to your value by coincidence.
For example, a chain might work while you're in-game but fail in the main menu because the player object doesn't exist there. That's normal.
Another common issue is display values. Some games keep a real HP value and a display copy. The display copy might point somewhere nice during one scan, but it is not the thing the game actually uses.
So before spending time on pointer scanning, make sure the address you found is the real one you care about.
You can add pointers manually if you already know the base and offsets.
When adding or editing an address table entry:
- Tick
Pointer - Put the base address in the pointer start address field
- Add offsets with
Add Offset - PINCE will show how the chain resolves
If you don't know the chain, use the pointer scanner.
The pointer scanner tries to find chains that lead to the current address of your value. Since one scan gives many false results, scan again after restart / reload and filter the maps.
The pointer scanner instructions are split off into the Pointer Scanner page.
Sometimes there isn't a useful pointer chain or it takes too much time to find one.
In that case you may need to use another method. For example, find the instruction that writes to the value and patch the code instead.
That is a different topic, but it's good to know that pointer scanning is only one way to solve changing addresses.