amiga-bootcamp/05_reversing/games/game_reversing.md

33 KiB
Raw Blame History

← Home · Reverse Engineering

Game Reverse Engineering — Disassembly, Modification, and Asset Extraction

Overview

Commercial Amiga games were fortresses built from hand-written 68000 assembly, custom trackloaders, and executable packers. The OS was irrelevant — most titles booted straight from disk, seized the hardware, and never called OpenLibrary in their lives. Reversing them is not like reversing an AmigaOS .library where you anchor on JSR -N(A6) and follow the LVO table. You are facing raw metal: arbitrary register usage, self-modifying code, data embedded mid-instruction, and protection schemes designed by people who understood the 68000 trace exception better than Motorola's own engineers.

This article covers the complete workflow for reverse engineering Amiga games — from the first triage of an NDOS disk image to identifying the memory location that holds your lives counter, extracting sprite data, and building a patch that compiles. The techniques here apply equally to demos, bootblock intros, and any binary that bypasses AmigaOS.

Note

This article assumes you are already comfortable with 68000 assembly and Amiga hardware registers. If not, start with Hand-Written Assembly RE and the OCS Custom Registers.


The Game Binary Landscape

Before touching a disassembler, determine what kind of binary you are facing. The approach differs radically.

graph TD
    START["Load disk image"] --> SIGNATURE{"Bootblock signature?"}
    SIGNATURE -->|"DOS\\0"| DOS["AmigaDOS disk<br/>files accessible"]
    SIGNATURE -->|"Other / NDOS"| NDOS["Non-DOS bootblock<br/>custom trackloader"]
    DOS --> FILETYPE{"Examine main executable"}
    NDOS --> PROTECTED{"Trackloader type?"}
    PROTECTED -->|"Standard MFM"| MFM["Raw track read<br/>sync $4489"]
    PROTECTED -->|"Custom format"| CUSTOM["Non-standard sync<br/>long tracks, weak bits"]
    FILETYPE -->|"HUNK executable"| HUNK["Standard RE workflow"]
    FILETYPE -->|"Packed / Crunched"| PACKED["Decrunch first<br/>→ exe_crunchers.md"]
    FILETYPE -->|"Raw binary blob"| RAW["Absolute load address<br/>no relocations"]

NDOS vs DOS-Based Games

Type Boot Sequence Disk Format RE Strategy
NDOS Bootblock disables OS, installs custom trackloader Non-standard or raw MFM Dump from emulator after boot; analyze raw memory image
DOS-based Standard AmigaDOS boot; executable launched from disk Standard OFS/FFS Analyze HUNK executable directly; may still use hardware banging
Hybrid AmigaDOS boot, but executable takes over hardware Standard filesystem Analyze HUNK, but expect Forbid() + direct register access

Most pre-1992 games are NDOS. Most post-1992 titles (especially AGA games and CD32 ports) use AmigaDOS but still bypass the OS for graphics and audio.

Packed vs Unpacked

Games were packed to fit on floppies. Common packers:

Packer Era Signature Decrunch Speed
PowerPacker 19891994 $42 + LEA/MOVE.L pattern Fast
Imploder 19881992 $49 (often); ATN!Imploder header Medium
ByteKiller 19881991 Short BRA.S over header, MOVEM.L Very fast
Shrinkler 1999+ Context-mixing setup; no fixed magic Slow (minutes on 7 MHz)

Important

Always attempt automated decrunching with xfdmaster.library before manual analysis. See Executable Unpacking for the full decruncher archaeology workflow.

Copy Protection Landscape

Scheme Mechanism How to Defeat
Rob Northen Copylock Trace exception decryption tied to disk timing Let it run in emulator; dump decrypted payload from RAM
Custom trackloader Non-standard MFM, long tracks Use RawDIC + Imager Slave; see Custom Loaders
Weak / fuzzy bits Mastered flux that reads randomly Preserve as IPF; ADF loses the weakness
Checksum loops Self-checksums with delayed failure NOP out checksum routine; trace to find patch point

Tooling

IRA — Interactive Reassembler

IRA is the native Amiga disassembler of choice for generating re-assemblable source code. Unlike IDA or Ghidra, which produce annotated databases, IRA outputs 68000 assembly source (.asm) plus a configuration file (.cnf) that you can refine iteratively.

Basic workflow:

# First pass: auto-detect code vs data
ira -A -KEEPZH -NEWSTYLE -COMPAT=bi -PREPROC MatchPatch

# Refine the .cnf file manually, then re-run with config:
ira -A -KEEPZH -NEWSTYLE -COMPAT=bi -CONFIG MatchPatch
Flag Purpose
-A Show hex bytes alongside disassembly (invaluable for hex editing later)
-KEEPZH Preserve zero hunks (empty hunks that may hold metadata)
-NEWSTYLE Modern label naming convention
-COMPAT=bi Big-endian compatibility mode
-PREPROC Attempt automatic code/data separation
-CONFIG Re-run using manual .cnf corrections

Why IRA over IDA/Ghidra for games?

  • Outputs compilable assembly source, not just annotations
  • The .cnf file lets you add SYMBOL (rename labels), LABEL (add new labels), COMMENT, and BANNER directives, then regenerate
  • Native awareness of Amiga executable quirks
  • Cross-platform: compiles for Windows, macOS, and Linux (and runs much faster than on real hardware)

Signs IRA misidentified code as data:

  • DC.L lines containing values like $4E75 (RTS) or $4E71 (NOP)
  • Data areas with labels that look like subroutine names

Signs IRA misidentified data as code:

  • Strings of EXT_ declarations at the start of a program
  • Code sections full of ORI #0 ($0000)
  • Sequences of hex values from $41 to $7A — these are likely unidentified ASCII text

Ghidra + ghidra-amiga

Ghidra with the ghidra-amiga extension provides a full HUNK loader, M68k decompiler, custom chip register mapping, and automatic LVO resolution. See Ghidra Setup for installation and configuration.

When Ghidra shines for games: C-coded late-era titles, cross-reference graphs, global renaming.

When Ghidra struggles: Hand-written assembly with no prologues, self-modifying code, JMP (PC, D0.W) jump tables, and mixed code/data sections.

IDA Pro

IDA Pro with the Amiga HUNK plugin is the traditional static analysis choice for Amiga binaries. It excels at interactive annotation, FLIRT signatures, and scripted automation. See IDA Setup for configuration details.

When IDA shines for games: Interactive tracing, custom IDA Python scripts for jump table resolution, and hardware register enum creation.

When IDA struggles: No native M68k decompiler (unlike Ghidra). Heavily optimized hand-written assembly requires manual function boundary definition.

Where to Get the Tools

Tool Where to Obtain Notes
IRA Aminet: dev/misc/ira.lha Also compiles from source for Windows/macOS/Linux
Ghidra https://ghidra-sre.org/ Free, from NSA; v10.x+ recommended
ghidra-amiga https://github.com/BartmanAbyss/ghidra-amiga Load as Ghidra extension; do not unzip
IDA Pro https://hex-rays.com/ida-pro/ Commercial; requires separate Amiga HUNK plugin
WinUAE https://www.winuae.net/ Windows Amiga emulator with built-in debugger
FS-UAE https://fs-uae.net/ Cross-platform (macOS/Linux/Windows); debugger via Shift+F12
xfdmaster.library Aminet: util/pack/xfdmaster.lha Native Amiga decruncher; use via xfdDecrunch CLI
hunkinfo Aminet: dev/misc/hunkinfo.lha Quick hunk structure dump
SPS / IPF tools https://softpres.org/ For preserving copy-protected disks as IPF
RawDIC Bundled with WHDLoad distribution Used with custom Imager Slaves for protected disks

Note

Many of these tools are also available pre-installed in curated Amiga emulation distributions like Amiga Forever or pre-configured WinUAE environments from EAB.

Emulator Debugging

Static analysis alone is often insufficient for games. You need dynamic verification.

WinUAE / FS-UAE debugger:

Key Action
Shift+F12 Enter debugger
g <address> Go to address
z Step one instruction
t Trace (step into)
W <address> <length> Write watchpoint
R <address> <length> Read watchpoint
m <address> Dump memory
d <address> Disassemble from address
s "filename" <start> <end> Save memory range to file

Critical technique — memory dump after decrunch:

  1. Boot the game in emulator
  2. Enter debugger (Shift+F12)
  3. Find the decruncher's final JMP to the original entry point
  4. Set a breakpoint on that JMP
  5. Let the game run — it decrunches in memory
  6. When breakpoint hits, dump the entire decrunched region with s

Pre-Flight: Research Before Disassembly

The most common mistake in game RE is failing to check if the work is already done.

  1. Search for existing analysis — EAB (English Amiga Board) threads, GitHub repos, speedrun communities, and TCRF (The Cutting Room Floor) often document exactly what you are looking for.
  2. Check for source code releases — Original authors sometimes release source decades later (e.g., Frontier: Elite II sources, various demo sources).
  3. Contact the author — Many Amiga developers are reachable and willing to share insights or even original source.
  4. Preserve the original media — If dealing with copy-protected disks, create IPF images (not ADF) using SPS/IPF tools. ADF loses weak-bit protection and custom track formats.

Phase 1: Triage and Loading

Step 1: Identify the Binary Type

# Check first bytes of disk image or executable
xxd game.adf | head -1
First Bytes Meaning
44 4F 53 00 (DOS\0) Standard AmigaDOS disk
Other executable NDOS bootblock or custom format
00 00 03 F3 HUNK executable (file, not disk)

Step 2: For NDOS Disks — Extract the Bootblock

The bootblock is the first 1024 bytes (2 sectors) of the disk. It is loaded to $7C00 by Kickstart and executed directly.

# Extract bootblock from ADF
dd if=game.adf of=bootblock.bin bs=1024 count=1

Analyze bootblock.bin at base address $7C00. Look for:

  • BRA or JMP to the main loader
  • DSKSYNC writes ($DFF07E) — indicates trackloader
  • INTENA / DMACON writes — indicates OS takeover

Step 3: For HUNK Executables — Dump Structure

hunkinfo game.exe

Note hunk types, sizes, and whether symbols are present. Some late-era games shipped with debug symbols accidentally left in — these are gold.

Step 4: Detect Packing

Scan the first CODE hunk for packer signatures. See Executable Unpacking for the full signature table.


Phase 2: Finding Anchors

A 500 KB game binary is overwhelming. You need anchors — known values or patterns that let you orient yourself.

Anchor 1: Text Strings

Games contain strings for menus, cheat codes, status messages, and file names.

# Extract strings from binary
strings game.exe > strings.txt

# Or with IRA:
ira -TEXT=1 -A -PREPROC game.exe

String types and what they reveal:

String Pattern Likely Meaning
"graphics.library" Game uses OS graphics (unusual)
"dos.library" Game uses OS file I/O
"FORM", "ILBM", "8SVX" Embedded IFF assets
"MATCHPATCH", "ZOOL" Game title or internal project name
File paths like "worlds/nif2txt.dat" Level data loading routines nearby
"CHEAT ENABLED" Cheat code handler

Warning

strings often returns garbage from misidentified data sections. Cross-reference with the disassembly to confirm the string is actually referenced by code.

Anchor 2: Known Numeric Values

Games contain specific numbers: starting lives, maximum health, item prices, level counts.

Decimal Hex (16-bit) Hex (32-bit) Likely Meaning
3 $0003 $00000003 Starting lives
10 $000A $0000000A Common statistic
100 $0064 $00000064 Percentage scale, health
1000 $03E8 $000003E8 Score multiplier, currency
320 $0140 $00000140 Screen width (lowres)
200 $00C8 $000000C8 Screen height (PAL lowres)

Search for these values in hex. If you find a MOVE.W #$0003, D0 near initialization code, you have likely found the lives setup.

Anchor 3: Hardware Register Accesses

Even games that take over the OS usually hit hardware registers. These are unambiguous anchors.

Register Address What It Reveals
JOY0DAT $DFF00A Joystick/mouse port 0 reads — player input handling
JOY1DAT $DFF00C Joystick/mouse port 1 reads
AUD0LCHAUD3LCH $DFF0A0$DFF0D0 Audio channel setup — sound effects, music
VHPOSR $DFF006 Vertical/horizontal position — RNG seeding, VBlank waits
VPOSR $DFF004 Vertical position (high bits) — frame timing
COP1LC $DFF080 Copper list pointer — display setup
BLTCON0 $DFF040 Blitter control — graphics rendering

Input handling identification:

; Classic joystick read pattern:
MOVE.W  $DFF00A, D0          ; Read JOY0DAT
AND.W   #$0101, D0           ; Mask direction bits
; ... decode into game state ...

Random number generator identification:

; Common RNG seed pattern — uses beam position for entropy:
MOVE.W  $DFF006, D0          ; VHPOSR = current raster position
; ... shuffle with ROXR ...

The ROXR (rotate right with extend) instruction is a dead giveaway for RNG routines. Once you find the RNG, every caller is potentially a game mechanic.

Anchor 4: AmigaOS Library Calls

Some games, especially later titles and CD32 ports, use AmigaOS for initialization or file I/O.

; OpenLibrary call pattern:
MOVEA.L 4.W, A6              ; SysBase
LEA     graphics_name(PC), A1
MOVEQ   #33, D0              ; minimum version
JSR     -552(A6)             ; OpenLibrary
LVO Library Function Game RE Relevance
-552 exec OpenLibrary Loading libraries
-30 dos Open File loading — level data, save games
-42 dos Read Reading data into memory — reveals file→memory mapping
-48 dos Write Save games and high scores — critical for state analysis
-36 dos Close File cleanup

Save game exploitation: Write calls in games are almost always save games or high scores. The data written holds persistent game state (inventory, stats, level progress). Finding the Write call reveals the in-memory structure of the save game, enabling editor construction.

Anchor 5: File Read Patterns

; File read pattern — reveals memory destinations:
JSR     -30(A6)              ; Open(file_name, MODE_OLDFILE)
MOVEA.L D0, D1               ; FileHandle
MOVEA.L #buffer, D2          ; Destination address
MOVE.L  #size, D3            ; Bytes to read
JSR     -42(A6)              ; Read(FileHandle, buffer, size)

The buffer address is the in-memory location of that file's data. Cross-reference this with string anchors (e.g., "worlds/nif2txt.dat") to map file contents to memory layout.


Phase 3: Mapping Game Mechanics

Once anchored, trace outward to reconstruct the game's logic.

Finding the Main Game Loop

Games need to synchronize to the display refresh (50 Hz PAL / 60 Hz NTSC). Look for:

; VBlank wait pattern — the heartbeat of the game:
wait_vblank:
    MOVE.W  $DFF006, D0      ; VHPOSR
    AND.W   #$FF00, D0       ; Mask vertical position
    CMP.W   #$0000, D0       ; Wait for line 0 (start of frame)
    BNE.S   wait_vblank

Or the more common VPOSR check:

wait_frame:
    MOVE.W  $DFF004, D0      ; VPOSR
    AND.W   #$01FF, D0       ; Mask vertical position bits
    CMP.W   #303, D0         ; Wait for bottom of PAL frame
    BNE.S   wait_frame

The code immediately following the VBlank wait is the main game loop.

Identifying Score and Lives

  1. Hex search: Search for the starting value (e.g., 3 lives = $0003).
  2. Cross-reference: Find all instructions that write this value. One is initialization; others are decrement (lose life) or increment (gain life).
  3. Verify with emulator: Patch the value at the memory location and run the game. If you start with 99 lives, you found it.

Identifying the RNG

; Typical Amiga game RNG (seeded from beam position):
rng_seed:
    MOVE.W  $DFF006, D0      ; VHPOSR
    EOR.W   D0, rng_state    ; Mix with current state
    ROXR.W  #1, rng_state    ; Shuffle
    MOVE.W  rng_state, D0    ; Return random value
    RTS

Key signatures:

  • Read from $DFF006 or $DFF004
  • ROXR or ROR instruction
  • Called from combat, item drops, enemy spawn, or any probabilistic mechanic

Audio as a Navigation Aid

Audio register writes reveal what the code is doing:

; Sound effect trigger:
MOVE.L  #bullet_sample, $DFF0A0   ; AUD0LCH/LCL = sample pointer
MOVE.W  #period, $DFF0A6          ; AUD0PER = playback period
MOVE.W  #volume, $DFF0A8          ; AUD0VOL = volume

If you extract the sample referenced by bullet_sample and hear a gunshot, you have found the shooting code. From there, trace back to find collision detection, enemy damage, and scoring.


Phase 4: Modification and Patching

Hex Editing Known Values

Once you have identified a value's location in the binary:

  1. Note the file offset and original bytes from the IRA -A output
  2. Open the binary in a hex editor
  3. Search for the unique byte sequence surrounding the value
  4. Patch and test
File Offset Original Patched Effect
$0001A4 66 0A 4E 71 4E 71 Replace BNE with two NOPs (defeat branch)
$003210 03 63 Change starting lives from 3 to 99

Warning

Patched bytes must preserve instruction alignment. A 16-bit MOVE.W patch that changes length will shift all subsequent code and break absolute addresses.

Building a Re-Assemblable Patch

For complex modifications, IRA's output is ideal:

  1. Disassemble with IRA to get game.asm and game.cnf
  2. Edit game.asm directly (e.g., change MOVEQ #3, D0 to MOVEQ #99, D0)
  3. Assemble with vasm or AsmOne
  4. Test on emulator

Trainer / Cheat Menu Construction

A trainer is a small patch that installs a hotkey handler to modify game state at runtime:

; Minimal trainer hook — intercepts keyboard and grants lives
    MOVE.W  $BFEC01, D0         ; Read keyboard data port (CIAA)
    NOT.B   D0
    ROR.B   #1, D0              ; Decode raw keycode
    CMP.B   #$45, D0            ; F1 key?
    BNE.S   .no_cheat
    MOVE.B  #99, lives_counter  ; Grant 99 lives
.no_cheat:

Install this in the VBlank interrupt or keyboard handler. See SetFunction Patching for runtime hook techniques.


Phase 5: Asset Extraction

Text and Strings

Use the IRA -TEXT=1 option or strings to find all text. For games with custom text encoding (e.g., compressed or shifted character sets), identify the font rendering routine and reverse the encoding table.

Graphics — IFF Extraction

Amiga games often store graphics in IFF format (FORM ILBM) or raw planar bitmaps.

IFF detection: Search for FORM ($464F524D) and ILBM ($494C424D) signatures in the binary or memory dump. The IFF header gives you width, height, depth, and palette.

Raw planar extraction:

  1. Find BPL1PTBPL5PT writes in the copper list or code
  2. The pointers reveal bitmap base addresses in memory
  3. Dump the memory range; decode as planar (interleaved or non-interleaved based on BPLMOD)

Audio — Sample and Module Extraction

Format Signature Extraction
8SVX FORM + 8SVX IFF audio chunk; playable directly
Protracker MOD M.K., FLT4, 4CHN Standard 31-sample + pattern data format
Raw PCM None — identified via AUDxLCH writes Mono 8-bit signed; import to Audacity as raw 8-bit signed, ~800028000 Hz

Decision Guide: Choosing Your Toolchain

Scenario Recommended Tool Why
Need re-assemblable source IRA Outputs .asm + .cnf; iterative refinement
Need C pseudocode / cross-references Ghidra + ghidra-amiga Decompiler, global renaming, xref graph
Heavy OS library usage (late games) Ghidra Automatic LVO resolution
Pure assembly, no OS calls IRA + emulator Ghidra decompiler gives up; IRA + dynamic trace works better
Packed / protected game Emulator debugger first Let protection run, dump decrypted memory, then load into static tool
Quick value patch (lives, score) Hex editor Fastest for one-byte changes
Bootblock analysis (1024 bytes) IRA or raw disassembly Small enough to read linearly

Historical Context

Why Games Bypassed the OS

Factor Impact
7 MHz CPU Every CPU cycle mattered. graphics.library added overhead (layer locking, clipping checks).
512 KB Chip RAM OS structures consumed precious DMA-accessible memory. Games needed every byte for sprites and sound.
Disk speed 880 KB floppy at ~50 KB/s effective. Custom trackloaders achieved 23× speed by reading raw tracks sequentially.
Copy protection AmigaDOS disks were trivially copyable with X-Copy. NDOS + custom formats + weak bits made mass duplication harder.
Demoscene culture Assembly was the standard. Using a C compiler for a game engine was seen as lazy until the mid-1990s.

The NDOS-to-DOS Transition

  • 19851990: Almost all commercial games are NDOS, hand-written assembly, custom trackloaders.
  • 19901993: Hybrid era. Games boot from AmigaDOS but take over hardware after loading. Some use dos.library for file I/O.
  • 19931996: AGA and CD32 era. Larger budgets, more C code, AmigaDOS-based loading. WHDLoad emerges to patch games for hard drive installation.

Modern Analogies

Amiga Game RE Concept Modern Equivalent Where It Holds / Breaks
NDOS bootblock takeover UEFI bootkit / custom bootloader Holds: bypasses OS entirely. Breaks: bootblock is 1024 bytes, UEFI is MBs.
Custom trackloader Direct NAND flash controller access Holds: raw media access for speed. Breaks: no MFM encoding on flash.
Executable packer UPX, VMProtect packing Holds: runtime decompression + jump to OEP. Breaks: modern packers use virtualization.
Rob Northen Copylock Denuvo anti-tamper Holds: trace/exception abuse, timing checks. Breaks: Copylock is 68000-specific; Denuvo uses x64 VM.
Hardware register banging Embedded MCU programming (STM32, Arduino) Holds: direct MMIO register access. Breaks: Amiga chips are video/audio-specific.
Memory patch (lives counter) Cheat Engine / GameGuardian Holds: scan for known value, patch at runtime. Breaks: modern games use encrypted/process-isolated memory.

Best Practices

  1. Always preserve original media as IPF before modifying — ADF loses copy protection and custom formats.
  2. Try automated decrunching firstxfdmaster.library can save hours.
  3. Document every patch with file offset, original bytes, and rationale — you will forget why you changed something.
  4. Use the -A flag in IRA — seeing raw hex bytes alongside disassembly is essential for building patch tables.
  5. Verify anchors dynamically — a suspected lives counter may actually be a loop iterator. Patch and test in emulator.
  6. Build a register map as you trace — hand-written assembly has no ABI. Document what each register means in each routine.
  7. Save memory dumps at key moments — after decrunch, after level load, after title screen. Compare dumps to find dynamic data structures.
  8. Trace audio register writes to locate game events — sound effects are the most reliable event markers in assembly.
  9. Cross-reference file reads with string anchors"level1.dat" + dos.library Read = level data structure.
  10. Work iteratively — name one function, trace its callers, name them too. Do not attempt to understand the entire binary in one pass.

Antipatterns

1. The Linear Reading Trap

Wrong: Opening the disassembly at offset 0 and reading top-to-bottom expecting to understand the game.

Why it fails: Hand-written assembly is non-linear. The entry point sets up interrupts and copper lists, then the real game logic lives in ISR chains and event handlers scattered across the binary.

Right: Start from anchors (strings, hardware registers, known values) and trace outward using cross-references.

2. The Compiler Assumption

Wrong: Expecting A6 to be a library base, D0/D1 to be scratch, and LINK/UNLK function boundaries.

Why it fails: Games are hand-written assembly. A6 might hold the hardware base pointer. D6 might be the frame counter. Functions may have no prologue.

Right: Treat every register as unknown until proven otherwise. Document the actual convention per routine.

3. The OS Dependency Delusion

Wrong: Searching extensively for JSR -N(A6) library calls to anchor analysis.

Why it fails: Most games make zero OS calls after initialization. The action is at $DFF000 and $BFE001, not in exec.library.

Right: Scan for hardware register constants ($DFF, $BFE) first. If none appear, then check for OS calls.

4. The Phantom String

Wrong: Assuming every readable ASCII sequence in the binary is a meaningful string.

Why it fails: Random data bytes can decode as printable ASCII. A string with no code cross-reference is likely not a string.

Right: Always verify that code actually references the string address (via LEA string(PC), A0 or similar).

5. The In-Place Patch Disaster

Wrong: Changing a MOVE.W #3, D0 to MOVE.W #99, D0 without checking instruction length.

Why it fails: MOVEQ #3, D0 is 2 bytes. MOVE.W #99, D0 is 4 bytes. The patch overflows into the next instruction, corrupting the code stream.

Right: Use equivalently-sized instructions. MOVEQ #99, D0 is invalid (MOVEQ range is -128 to +127, so #99 is fine and still 2 bytes). For larger values, MOVE.W is required but check alignment.


Pitfalls & Common Mistakes

1. Misidentifying Data as Code

Mixed code/data is the norm in game binaries. Copper lists, sprite data, and audio samples often reside in CODE hunks.

; This looks like instructions:
OR.B    #$80, D0
OR.B    #0, D0
OR.B    #$82, D0
OR.B    #$FF, D0

; But it is actually a copper list:
; DC.W $0180, $0000  = COLOR00 = $0000
; DC.W $0182, $0FFF  = COLOR01 = $0FFF

Fix: Search for COP1LC writes to find copper list addresses. Force-define those ranges as data arrays, not code.

2. Ignoring Relocation State

When you dump decrunched memory from an emulator, absolute addresses have already been patched by the decrunch stub. If you try to run that dump at a different load address, it crashes.

Fix: Note the load address used by the emulator. If re-assembling, either use the same base address or reconstruct PC-relative addressing.

3. Debugging After OS Death

Games call Forbid() and disable interrupts early. If your debugger relies on AmigaOS (like MonAm or HRTmon), it stops working the moment the game takes over.

Fix: Use emulator built-in debuggers (WinUAE/FS-UAE Shift+F12) or hardware cartridges (Action Replay) that trap via NMI, not OS services.

4. Overlooking Self-Modifying Code

Copy protection and optimization both use SMC. The disassembly shows one instruction; the runtime executes another.

; Static disassembly shows:
MOVE.W  D0, D0           ; This gets patched at runtime

; Init routine overwrites it:
MOVE.W  #$4E71, (patched+2, PC)   ; Patch in a NOP

Fix: Set write breakpoints on CODE hunk addresses in the emulator. If anything writes there during init, you have SMC.

5. Confusing VBlank Wait with Game Logic

The VBlank wait loop is easy to find but tells you nothing about what the game does each frame.

Fix: Trace forward from the VBlank wait exit. The next block is usually the frame update routine. Set a breakpoint there and step through one full frame.


Use Cases

Speedrun Research

Reverse engineering reveals frame-perfect mechanics: RNG seeds, hitbox dimensions, level transition triggers. Documenting the RNG_seed routine and its callers lets speedrunners manipulate luck.

Translation Projects

Finding the text rendering routine and font data enables text replacement. Games with embedded ASCII strings are trivial; games with custom encoding require reversing the blit-based font routine.

Save Game Editors

The dos.library Write call that saves game state reveals the exact memory structure of the persistent state. Mapping this structure enables external save game editors.

Modding and Enhancement

Patching the weapon damage table, adding a cheat menu, or replacing audio samples all require understanding the binary's data layout. IRA's re-assemblable output makes this sustainable.

Preservation and Documentation

Documenting the internal structure of unreleased or poorly documented games contributes to the historical record. TCRF and similar archives rely on this work.


FAQ

Q1: IRA vs Ghidra vs IDA Pro — which should I use?

Use IRA when you need re-assemblable source code or are working with pure hand-written assembly. Use Ghidra (free, open-source) when you need cross-references, decompilation, or the game was written in C. Use IDA Pro (commercial) when you need the best 68k processor module, advanced scripting, or are working with obfuscated or packed binaries that benefit from its debugger integration. Many RE projects use two or even all three: Ghidra or IDA for exploration, IRA for final patch generation.

See Ghidra Setup and IDA Pro Setup for configuration details.

Q2: How do I handle a game with no readable strings?

High entropy and no strings suggest encryption or compression. Let the game boot in an emulator, dump memory after decrunch/decryption, then analyze the dump. The decrypted payload will have strings.

Q3: Can I reverse a game back to C?

Not really. Generic decompilation of hand-written 68000 assembly produces unreadable pseudocode. The only successful "decompilations" are hand-crafted rewrites based on deep understanding of the assembly (e.g., GLFrontier).

Q4: How do I find the level data format?

Anchor on file read calls (dos.library Read) or look for large data tables referenced by the rendering code. Level data often follows audio/graphic assets in memory. Compare memory dumps between levels to find what changes.

Q5: What if the game uses a custom trackloader I can't read?

Use WinUAE's disk DMA breakpoint (W $DFF024 2) to catch every disk read. Trace backward from the breakpoint to find the trackloader code. Document the sync word, sector count, and MFM decode routine. See Custom Loaders.

Q6: How do I patch a game that checksums itself?

Find the checksum routine (usually a tight loop with ADD.L or EOR.L over a memory range). NOP it out, or recalculate the checksum to match your patch. The checksum routine is often called from multiple places — patch all callers.

Q7: Why does my patched game crash on real hardware but work in emulator?

Emulators are more forgiving of timing violations. Your patch may have altered cycle-exact code (e.g., a copper wait or blitter poll). Verify that you haven't changed instruction timing or introduced bus errors.


References