Raiders2600
Reverse Engineering Raiders of the Lost Ark for the Atari 2600
Install / Use
/learn @joshuanwalker/Raiders2600README
Raiders of the Lost Ark (Atari 2600) - Reverse Engineered Source
Original Game (1982) by Atari, Inc.
Original Designer: Howard Scott Warshaw
Disassembly & Analysis: Halkun (That's me!)
Overview
This repository contains the fully reverse-engineered and commented source code for the Atari 2600 classic, Raiders of the Lost Ark.
Project Structure
The project has been reorganized for a clean development workflow:
src/: Contains the main assembly source (raiders.asm) and header files (tia_constants.h).bin/: Contains build tools (DASM) and emulator executable (Stella).out/: Destination for compiled binaries (.bin), symbol files (.sym), and listing files (.lst). (auto generated at compile time)make.bat: Windows batch script to compile the project.run.bat: Windows batch script to launch the compiled game.Makefile: Makefile to compile and launch the compiled in in Windows with Powershell or Linux with make.
How to Build & Run
Prerequisites
-
Windows OS
-
DASM:
dasm.exemust be in thebin/folder. -
Stella:
Stella.exeandSDL2.dllmust be in thebin/folder (optional, for running). -
Linux
-
dasm
dasmmust be installed on the system. -
stella:
stellamust be installed on the system.
Compiling
- Windows
Run the build script from the root directory:
make.bat
- Linux (or Windows PowerShell with make)
Build the rom by running make:
make
Running
- Windows
Launch the compiled ROM in Stella:
run.bat
- Linux (or Windows PowerShell with make)
Launch the compiled ROM in Stella:
make run
Technical Documentation
ROM Architecture
The game uses a 2-bank ROM (8KB total) with bank-switching via strobes at BANK0STROBE ($FFF8) and BANK1STROBE ($FFF9). Bank switching is done through a self-modifying code technique where opcodes are written into zero-page RAM variables and executed in-place.
- Bank 0 (
BANK0TOP=$D000): Contains game logic — collision handling, inventory management, room event handlers, scoring, movement, input processing, and sound. - Bank 1 (
BANK1TOP=$F000): Contains the display kernels, sprite data, playfield graphics, room handler dispatch, and music frequency tables.
Game Loop
Like all Atari 2600 games, the program is structured around the NTSC television signal — one complete pass through the loop produces one frame of video (~60 fps). The frame is divided into four phases: VSYNC, VBLANK, Kernel (visible picture), and Overscan. Game logic is split across VBLANK and Overscan to stay within the CPU time budgets of each phase, and the two ROM banks are switched in and out at specific points every frame.
Frame Overview
newFrame ──spin on INTIM──►
startNewFrame
├── VSYNC (3 scanlines)
│ Timers, weapon clamp, RESET check
│
├── VBLANK (~37 scanlines of CPU time)
│ ├── [Bank 0] Main game logic
│ │ Ark Room / title ── Snake AI ──
│ │ Event checks ── Input & movement ──
│ │ Inventory ── Item use ──
│ │ Sprite animation ── Mesa scroll
│ │
│ ├── [Bank 1] Room-specific handler
│ │ Per-room AI, physics, spawning
│ │
│ └── [Bank 0] Pre-kernel setup
│ Colors, NUSIZ, CTRLPF from tables
│ setThievesPosX: position all 5 TIA objects
│ Spin on INTIM until VBLANK expires
│
├── VISIBLE KERNEL (~192 scanlines)
│ ├── [Bank 1] drawScreen preamble (5 lines)
│ │ Clear collisions, set initial PF, enable TIA
│ │
│ ├── [Bank 1] Room kernel dispatch (160 lines)
│ │ staticSpriteKernel ──── rooms 0–5
│ │ scrollingPlayfieldKernel ── rooms 6–10
│ │ multiplexedSpriteKernel ── rooms 11–12
│ │ arkPedestalKernel ──── room 13
│ │
│ └── [Bank 1] drawInventoryKernel (~27 lines)
│ 6-item inventory strip, selection cursor
│
└── OVERSCAN (~30 scanlines of CPU time)
├── [Bank 1] Post-kernel logic
│ Sound/music ── Timepiece sprite ──
│ Event animation ── Death sequence ──
│ Inventory cycling ── Grapple state
│
└── [Bank 0] Collision handling
Weapon hits ── Indy vs objects ──
Room-specific pickups ── Idle handlers
──► newFrame (loop closes)
Phase 1: VSYNC (3 Scanlines)
Entry point: startNewFrame
The CPU asserts the VSYNC signal for exactly 3 scanlines, during which it performs lightweight housekeeping:
| Scanline | Work |
| -------- | ---- |
| 1 | Assert VSYNC. Clamp weapon position — if weaponPosY ≤ $50, center weaponPosX. Increment frameCount; every 64th frame (and #$3f) increments timeOfDay. If eventTimer is negative, decrement it (paralysis/cutscene countdown). |
| 2 | Check for game restart — if arkRoomStateFlag bit 7 is set (endgame state) AND the RESET switch is pressed, jump to startGame. |
| 3 | De-assert VSYNC. Arm the VBLANK timer: TIM64T = VBLANK_TIME (44). This gives 44 × 64 = 2,816 cycles ≈ 37 scanlines of CPU time for game logic. |
Phase 2: VBLANK (~37 Scanlines of CPU Time)
All game logic runs while the TIA outputs a blank screen. Work is spread across both ROM banks with two bank switches during this phase.
Bank 0 — Main Game Logic
This is the largest block of game code, executing in order every frame:
| Step | Label | Description |
| ---- | ----- | ----------- |
| 1 | checkGameOver | Start logic on first scan line: if indyStatus overflows to 0, call getFinalScore and transition to the Ark Room. |
| 2 | checkForArkRoom | Ark Room / title screen: if in the Ark Room, play Raiders March, check Yar bonus for HSW initials easter egg. Otherwise skip. |
| 3 | (Ark Room only) | Pedestal elevator: slowly lower Indy to his score height. Check fire button for restart. Set arkRoomStateFlag to enable RESET. |
| 4 | checkScreenEvent | Cutscene check: if screenEventState bit 6 is set, advance the Ark reveal sequence. |
| 5 | updateSnakeAI | Snake AI: every 4th frame, grow snake sprite, steer toward Indy using snakePosXOffsetTable, update ballPosX/ballPosY and kernelRenderState. |
| 6 | configSnake | Snake kernel setup: load kernelDataPtrLo/Hi and kernelDataIndex from snakeMotionTable0–snakeMotionTable3 for the wiggling ball sprite. |
| 7 | checkIndyStatus | If indyStatus bit 7 is set (death in progress), skip to dispatchRoomHandler — bypass normal input. |
| 8 | checkGameScriptTimer | If eventTimer is negative (Indy paralyzed/frozen), force standing sprite and skip input. |
| 9 | branchOnFrameParity | Frame parity split: even frames run full input processing. Odd frames skip to clearItemUseOnButtonRelease. |
| 10 | handleWeaponAim | Weapon aiming: joystick moves the weapon crosshair (missile 1) with boundary clamping. |
| 11 | handleIndyMove | Movement: read SWCHA → getMoveDir → move Indy → check room boundary override tables (checkRoomOverrideCondition) → trigger room transitions if a boundary is crossed. |
| 12 | handleInventorySelect | Left controller: fire button cycles through inventory slots. Handles item drop, bullet reload (+3), and shovel placement. |
| 13 | clearItemUseOnButtonRelease | Clears USING_GRENADE_OR_PARACHUTE flag on right fire button release. |
| 14 | handleItemUse | Right controller: fire button dispatches the selected item — grenade throw/cook timer, parachute deploy, grapple hook launch, shovel dig, Ankh warp, revolver fire, whip strike. This is the largest single block in VBLANK. |
| 15 | selectIndySprite | Sprite selection: choose Indy's current sprite pointer — parachute sprite, standing sprite, or walk-cycle animation (advances frame on a timer). |
| 16 | handleMesaScroll | Vertical scrolling: in Mesa Field or Valley of Poison, shift the camera offset (roomObjectVar) and adjust all object Y positions to scroll the world. |
Bank Switch → Bank 1: Room Handlers
At dispatchRoomHandler, the code writes selectRoomHandler as the target address and jumps through the jumpToBank1 trampoline.
selectRoomHandler dispatches via roomHandlerJmpTable — each room has its own handler that runs room-specific AI and physics:
| Room | Handler | Key Logic |
| ---- | ------- | --------- |
| Treasure Room | treasureRoomHandler | Item cycle timer, treasure availability, treasure spawning |
| Marketplace | (none — immediate return) | — |
| Entrance Room | entranceRoomHandler | Sets screenEventState = $40 |
| Black Market | blackMarketRoomHandler | Lunatic/blocker positioning, bribe check |
| Map Room | mapRoomHandler | Sun height from timeOfDay, Head of Ra beam, movement constraints |
| Mesa Side | mesaSideRoomHandler | Parachute/freefall physics, gravity, horizontal input |
| Temple Entrance | templeEntranceRoomHandler | Timepiece placement, room graphics from entranceRoomEventState |
| Spider Room | spiderRoomHandler | Spider AI (passive→aggressive), web positioning, animation |
| Shining Light | roomOfShiningLightHandler | Chase AI, dungeon secret exit check |
| Mesa Field | mesaFieldRoomHandler | Pins P0/M0/Ball to center Y ($7F) for scrolling |
| Valley of Poison | valleyOfPoisonRoomHandler | Thief chase/escape AI, tsetse swarm spawning |
| Thieves' Den | thievesDenRoomHandler | Moves 5 thieves with left/right boundary bounce |
| Well of Souls | wellOfSoulsRoomHandler | Sets mesa landing bonus, then shares thief-movement code |
All handlers exit via jmpSetupNewRoom, which bank-switches back to Bank 0.
Bank Switch → Bank 0: Pre-Kernel Setup
setupNewRoom prepares the TIA for display:
- If
screenInitFlag≠ 0, callupdateRoomEventState(one-shot room initialization), then clear the flag. - Set
NUSIZ0from the per-roomhmoveTableentry. - Set
CTRLPFfrom `roomP
