Game Reverse Engineering for Beginners: Code, Tools and a Practice Lab
Learn game reverse-engineering methods with an original scoring program, worked code, Ghidra guides and public examples from other developers.
Reverse engineering means working from observable behavior and compiled structure to a tested explanation. For game development, that can help you understand scoring, save formats or a state transition. This guide uses an original tiny scoring program throughout. You need basic functions and a terminal; Linux x86_64 is the tested command path.
Examples from other developers
These are public training and analysis projects. Their code and walkthroughs stay at the authors' original sites.
| Public project | What they made | How the method works |
|---|---|---|
| Wrongbaud's Hackaday-U exercises | Four sessions of purpose-built x86_64 ELF challenges, slides and example materials. | Identify a program, inspect functions and control flow in Ghidra, annotate a hypothesis, then test it. Author's session explanation ↗ |
| Capstone team's Python sample | A short script that turns a supplied byte sequence into addresses, instruction names and operands. | Choose the correct CPU/mode, pass the bytes to Cs.disasm, inspect the returned instructions. This is disassembly; game rules still need reconstruction. |
| Compiler Explorer | A source/assembly comparison tool with small arithmetic examples and optimization controls. | Change one expression or compiler flag, then compare generated instructions. Use your original function to learn why multiply/add logic may appear as shifts or address arithmetic. |
For a complete beginner path, the official Ghidra student guide covers project creation, importing, analysis and navigation. Linked author projects are source-reviewed learning references; we have not run their exercises or certified their installation here.
How the method applies to games
| Game question | What to inspect | How to check your explanation |
|---|---|---|
| Scoring and combo rules | Input values, threshold branches, arithmetic and return value. | Change one token, chain or miss; test the cap and zero floor. The worked lab below covers this. |
| Movement and collision | Your own update loop, time step, input vector and collision boundary. | Measure distance over the same simulated interval, diagonals and four wall contacts; compare the existing controller first. |
| Game-state transitions | State values, callers and conditions for start, pause, win, lose and restart. | Draw the transition table and run the same sequence twice; distinguish display text from the underlying state. |
| Save and level formats | Your permitted versioned fixtures, field types, lengths and byte order. | Change one field, compare bytes, then validate a fresh roundtrip and malformed lengths in a copy. |
These are investigation paths for your permitted game project. The executed lab here covers scoring and native binary inspection; it does not certify movement, saves or a full engine importer.
Original worked example: investigate a score
Download complete original practice codescore_lab.c · recovered.py · check.py · observations.json · README.md · LICENSE.txt
Extract the whole reverse-lab folder. Keep its MIT notice. This is an author-made exercise; the reference source is included, so you can check your answer. If you want a challenge, record your observations before opening score_lab.c.
python3 check.pyRequires Python3, a C compiler and GNU binutils. The helper compiles only the supplied original source in temporary folders, inspects actual native artifacts and removes the temporary binaries. Tested GCC13.3.0 and binutils2.42 on Linux x86_64. You should see "result": "passed",444 behavior comparisons and24 invalid-input checks.
| Tokens, chain, misses | Actual output | What to investigate |
|---|---|---|
| 0, 0, 0 | 0 | How much is one token worth? |
| 1, 0, 0 | 7 | How much is one token worth? |
| 2, 0, 0 | 14 | How much is one token worth? |
| 3, 0, 0 | 21 | How much is one token worth? |
| 2, 2, 0 | 14 | Where does the chain bonus start? |
| 2, 3, 0 | 64 | Where does the chain bonus start? |
| 2, 4, 0 | 64 | Where does the chain bonus start? |
| 2, 3, 4 | 56 | How much does each miss subtract? |
| 99, 3, 0 | 743 | Does the token count cap? |
| 100, 3, 0 | 743 | Does the token count cap? |
How to do it, step by step
- STEP 1
Define one question and preserve the baseline
Start with files you own or are permitted to analyze. Reuse source, debug symbols, engine tools and existing BINX systems first. Here the question is simply: how do three inputs become a score? Work in a copy and record tool versions and a file hash. Do not start by rewriting your working game.
cc -std=c11 -Wall -Wextra -g -O0 score_lab.c -o score_lab readelf -h score_labCheck: the built lab is ELF64/x86_64. Commands and instruction syntax below are for this target; PE, ARM and other platforms need their own tools.
- STEP 2
Change one input, predict, observe
./score_lab 2 0 0 ./score_lab 2 3 0 ./score_lab 2 3 4Check: scores14,64,56. Two tokens give14; chain3 appears to add50; four misses remove8. These are hypotheses from observations. Test chain2/3/4, token99/100 and a large miss count before accepting the rule.
- STEP 3
Find anchors, then inspect instructions
strings score_lab nm score_lab objdump -d -M intel --disassemble=award_points score_labCheck: the output string is an anchor, the symbol identifies our function and objdump prints its real instructions. Follow argument values, comparison branches, arithmetic and the return. A string by itself does not prove the scoring location. With optimization,7× may use shifts/subtraction and a conditional may become a conditional move.
- STEP 4
Compare a stripped copy and a decompiler hypothesis
cp score_lab score_stripped strip --strip-all score_stripped nm score_stripped objdump -d -M intel score_strippedCheck: our
award_pointslabel is removed, but code, output string and observed behavior remain. Keep the original intact. In Ghidra create a Non-Shared project, import your built lab, confirm ELF/x86_64, open it and accept analysis. For the named build locateaward_pointsand compare Listing/Decompiler. On the stripped copy follow string references and callers, then trace the arguments and branches. Annotate guesses and rename only after evidence. Interactive Ghidra steps are source-reviewed instructions; they have not been executed in this release.Decompiler output is estimated pseudo-C. Comments, original variable names, exact source structure and design intent may be lost. Do not describe a pseudo-C display as recovered original source.
- STEP 5
Write your own model and compare
Our reconstructed rule caps tokens99, awards7each, adds50 for chain>=3, subtracts2per miss and floors the result0. Compare it with many valid inputs and the reference source. The helper checks111 inputs across O0/O2 and stripped/unstripped native binaries, then rejects malformed/out-of-domain CLI inputs.
Check: all444 comparisons and24 rejection checks pass. This tests this bounded lab, not every game, compiler or possible input. For a permitted game project, preserve its existing systems and apply the same method to one small mechanic or format.
The code and what it proves
Open the original C reference after your investigation
/* Original Forge practice program. See LICENSE.txt. */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
int award_points(int tokens, int chain, int misses) {
if (tokens > 99) tokens = 99;
int points = tokens * 7;
if (chain >= 3) points += 50;
points -= misses * 2;
return points < 0 ? 0 : points;
}
static int argument(const char *text, int *value) {
char *end;
errno = 0;
long n = strtol(text, &end, 10);
if (errno || end == text || *end || n < 0 || n > 999) return 0;
*value = (int)n;
return 1;
}
int main(int argc, char **argv) {
int tokens, chain, misses;
if (argc != 4 || !argument(argv[1], &tokens) ||
!argument(argv[2], &chain) || !argument(argv[3], &misses)) {
fprintf(stderr, "Use: score_lab TOKENS CHAIN MISSES (integers 0..999)\n");
return 2;
}
printf("Forge practice score: %d\n", award_points(tokens, chain, misses));
return 0;
}
Original reconstructed Python function
"""Original hypothesis reconstructed for this practice program only."""
def award_points(tokens, chain, misses):
if any(type(n) is not int or not 0 <= n <= 999 for n in (tokens, chain, misses)):
raise ValueError('Practice input domain: three integers 0..999')
return max(0, min(tokens, 99) * 7 + (50 if chain >= 3 else 0) - misses * 2)
Inspect the complete native comparison script · Read actual observations and scope · Keep the original permission notice
Behavior can match without reproducing the original source. Keep observed inputs, predicted outputs, compiler/options and open questions together. When you add a new hypothesis, try a case likely to disprove it. For a file-format exercise, build your own versioned fixture and validate bounds/types before interpreting bytes.
Take the method and complete code to your AI
Review before sharing. You can select the text manually.
Review the full original source and learning brief
Actual native compilation/inspection/strip/execution passes. Interactive Ghidra/decompiler, browser/clipboard/device/assistive and other toolchain/target checks remain OPEN. This guide contains no real-game example, extracted game assets or third-party binary. Original lab rights do not extend to linked tools, course materials or other inputs.
Learn at the original sources
Continue with your original map workflow · Understand your own game modules · All Forge guides