Skip to content

Modifying Game Code

brkzlr edited this page Jul 17, 2026 · 8 revisions

Scanning and changing values is usually the first thing you learn, but it has limits. Sometimes the value moves too much, pointer paths are ugly or freezing the value just isn't the right fix.

That's when you start looking at the code that reads or writes the value.

If you're not comfortable with normal scans, changing values and using the address table yet, don't start here.

The Basic Idea

A game eventually becomes machine code that the CPU runs. The disassembly view shows those machine code bytes as assembly instructions.

Instead of changing the ammo value directly, you can change the instruction that subtracts ammo. Instead of freezing health, you can change the instruction that writes damage. This is usually more stable than chasing one temporary address forever.

The usual workflow is:

  1. Find the value normally
  2. Use Find out what writes to this address or Find out what accesses this address
  3. Trigger the game action, like shooting or taking damage
  4. Jump to the instruction that touched the value
  5. Patch the instruction in the smallest way that does the job
  6. Test it
  7. Restore it if it breaks something

You don't need to master assembly before touching this stuff, but you do need to read a few common instructions. mov, add, sub, cmp, test, jmp and conditional jumps like je / jne show up constantly.

Useful references:

Where to Edit Code

Open Memory Viewer and look at the disassembly view.

Useful shortcuts:

  • F2: pause/break the process and refresh the current instruction location
  • F5: toggle a breakpoint on the selected instruction
  • F7: step one instruction
  • F8: step over a call

Right click an instruction and the two simplest code editing options are:

  • Replace instruction with NOPs
  • Edit instruction

For finding the instruction in the first place, start from a value in the address table and use the right click options that find what writes or accesses that address. That usually gets you to the correct code much faster than browsing random disassembly.

The disassembly view also draws nearby control-flow references as arrows. Jumps use solid lines and calls use dashed lines. Hover over an arrow to see its source and target, or click it to move between them.

Nearby direct references appear automatically, running Tools -> Dissect Code adds incoming references found during code dissection. This is useful for seeing which branches enter or leave the code you are about to patch.

Replacing with NOPs

NOP means no operation. On x86, the single-byte NOP instruction is 0x90 and it does nothing.

When you use Replace instruction with NOPs, PINCE reads the selected instruction length and replaces that many bytes with 0x90.

So if the original instruction is 3 bytes long, PINCE writes:

90 90 90

This is useful when the instruction itself is the thing you want to remove. For example:

  • The instruction subtracts ammo
  • The instruction writes fall damage
  • The instruction decreases durability
  • The instruction branches into a failure path

It's not always the right fix. If the instruction also does something important for other objects, NOPing it can break more than expected.

Example: Ammo Decrease

AssaultCube is a simple native Linux game people often use for testing this kind of thing.

One possible workflow:

  1. Find your current clip ammo with a normal scan
  2. Add it to the address table
  3. Right click it and choose Find out what writes to this address
  4. Shoot once
  5. Look for the instruction that fired when ammo decreased
  6. Double click the instruction address to jump to it in the disassembly view
  7. Close or stop the watchpoint window if you don't need it anymore
  8. Right click the instruction and choose Replace instruction with NOPs

On one version, the ammo decrease instruction looked like this:

add dword ptr [rax], -1

That means: take the address in rax and add -1 to the 4 bytes integer stored there.

If you replace that instruction with NOPs, shooting no longer subtracts ammo from that address.

Restoring Modified Instructions

PINCE stores the original bytes the first time you patch or NOP an address. To undo it, open View -> Restore Instructions in Memory Viewer.

That window shows:

  • The modified address
  • The original bytes
  • The disassembled form of those original bytes

Right click an entry and choose Restore this instruction. PINCE writes the saved original bytes back.

Small caveat: PINCE saves the original bytes only the first time that address is modified. If you patch the same address multiple times, restore goes back to the original bytes from before the first patch, not the previous patch.

Editing Instructions

Use Edit instruction when you want to replace an instruction instead of deleting it.

The dialog has two linked fields:

  • Editing the assembly assembles it with Keystone and updates the bytes
  • Editing the bytes disassembles them with Capstone and updates the assembly

You can enter multiple instructions separated by ;.

For example, changing:

add dword ptr [rax], -1

to:

add dword ptr [rax], -2

would make ammo decrease by 2 instead of 1.

Changing it to:

add dword ptr [rax], 2

would make ammo go up by 2 when the instruction runs.

Instruction Length Matters

Assembly instructions are not all the same size. One instruction might be 2 bytes, another might be 7 bytes.

If your replacement is longer than the original, it can overwrite the next instruction and break the program.

PINCE handles shorter replacements for you. If the original instruction was 3 bytes and your replacement is 2 bytes, PINCE pads the leftover byte with 0x90.

Example:

test eax, eax

is 2 bytes:

85 c0

If it replaces a 3-byte instruction, PINCE writes:

85 c0 90

Longer replacements are different. PINCE shows this warning:

New instruction is X bytes long but old instruction is only Y bytes long
This will cause an overflow, proceed?

If you press OK, the extra bytes overwrite whatever instructions come next. Sometimes advanced users do this on purpose, but if you're not sure, cancel.

Code Caves

When the replacement code is too large to fit, the normal trick is a code cave.

The idea is:

  1. Copy enough original bytes from the target location to cover a jump
  2. Allocate or find executable memory somewhere else
  3. Write your custom code there
  4. Optionally run the original bytes you overwrote
  5. Jump back after the overwritten area
  6. Replace the original location with a jump to your cave

This lets you run more code than would fit in the original instruction slot.

The annoying part is that copied instructions are not always safe to move. rip-relative instructions, call, jmp and conditional jumps can break when relocated unless you adjust them correctly.

For this reason, use Libpince Engine once you get past quick GUI edits. It has helpers like patch(), nop(), restore(), alloc() and assemble(), plus templates for:

  • AOB scan + NOP
  • AOB scan + patch
  • Code injection

The GUI is good for quick testing. Libpince Engine is better when you want a repeatable cheat entry with [ENABLE] and [DISABLE] sections.

Clone this wiki locally