[← Home](../../README.md) · [Reverse Engineering](../README.md) # 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](../static/asm68k_binaries.md) and the [OCS Custom Registers](../../01_hardware/ocs_a500/custom_registers.md). --- ## The Game Binary Landscape Before touching a disassembler, determine what kind of binary you are facing. The approach differs radically. ```mermaid graph TD START["Load disk image"] --> SIGNATURE{"Bootblock signature?"} SIGNATURE -->|"DOS\\0"| DOS["AmigaDOS disk
files accessible"] SIGNATURE -->|"Other / NDOS"| NDOS["Non-DOS bootblock
custom trackloader"] DOS --> FILETYPE{"Examine main executable"} NDOS --> PROTECTED{"Trackloader type?"} PROTECTED -->|"Standard MFM"| MFM["Raw track read
sync $4489"] PROTECTED -->|"Custom format"| CUSTOM["Non-standard sync
long tracks, weak bits"] FILETYPE -->|"HUNK executable"| HUNK["Standard RE workflow"] FILETYPE -->|"Packed / Crunched"| PACKED["Decrunch first
→ exe_crunchers.md"] FILETYPE -->|"Raw binary blob"| RAW["Absolute load address
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** | 1989–1994 | `$42` + `LEA`/`MOVE.L` pattern | Fast | | **Imploder** | 1988–1992 | `$49` (often); ATN!Imploder header | Medium | | **ByteKiller** | 1988–1991 | 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](../unpacking_and_decrunching.md) 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](../custom_loaders_and_drm.md) | | **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:** ```bash # 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](../ghidra_setup.md) 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](../ida_setup.md) 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
` | Go to address | | `z` | Step one instruction | | `t` | Trace (step into) | | `W
` | Write watchpoint | | `R
` | Read watchpoint | | `m
` | Dump memory | | `d
` | Disassemble from address | | `s "filename" ` | 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 ```bash # 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. ```bash # 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 ```bash 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](../unpacking_and_decrunching.md) 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. ```bash # 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 | | `AUD0LCH`–`AUD3LCH` | `$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:** ```asm ; 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:** ```asm ; 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. ```asm ; 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 ```asm ; 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: ```asm ; 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: ```asm 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 ```asm ; 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: ```asm ; 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 `NOP`s (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: ```asm ; 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](../dynamic/setfunction_patching.md) 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 `BPL1PT`–`BPL5PT` 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, ~8000–28000 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 2–3× 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 - **1985–1990**: Almost all commercial games are NDOS, hand-written assembly, custom trackloaders. - **1990–1993**: Hybrid era. Games boot from AmigaDOS but take over hardware after loading. Some use `dos.library` for file I/O. - **1993–1996**: 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 first** — `xfdmaster.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. ```asm ; 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. ```asm ; 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 — which should I use? Use **IRA** when you need re-assemblable source code or are working with pure hand-written assembly. Use **Ghidra** when you need cross-references, decompilation, or the game was written in C. Many RE projects use both: Ghidra for exploration, IRA for final patch generation. ### 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](../custom_loaders_and_drm.md). ### 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 - [Hand-Written Assembly RE](../static/asm68k_binaries.md) — Pure m68k binary methodology - [Executable Unpacking](../unpacking_and_decrunching.md) — Decruncher archaeology and memory extraction - [Custom Loaders & DRM](../custom_loaders_and_drm.md) — Trackloaders, copy protection, RawDIC - [Ghidra Setup](../ghidra_setup.md) — Ghidra + ghidra-amiga extension configuration - [Anti-Debugging](../anti_debugging.md) — Trace vector abuse, NMI defeat, checksum loops - [WHDLoad Architecture](../whdload_architecture.md) — Slave authoring and snooping - [Copper Programming](../../08_graphics/copper_programming.md) — Copper list format - [Blitter Programming](../../08_graphics/blitter_programming.md) — Blitter register sequences - [Paula Audio](../../01_hardware/ocs_a500/paula_audio.md) — Audio DMA registers - *Amiga Hardware Reference Manual* — Custom chip register reference - *M68000 Programmer's Reference Manual* — Instruction set and cycle timing - EAB: Small IRA Tutorial — https://eab.abime.net (search "IRA tutorial") - ghidra-amiga: https://github.com/BartmanAbyss/ghidra-amiga - Tetracorp Amiga RE Guide — https://tetracorp.github.io/guide/reverse-engineering-amiga.html