Standing
Running 1
Running 2
Running 3
Jumping
Aiming Up
Aiming Diag
Prone

Inside Contra(NES, 1988)

Reverse engineering Konami's legendary run-and-gun — decompressing RLE tile data, extracting 232 sprites, and analyzing 6502 assembly from the original NES ROM.

128KB PRG ROM0KB CHR ROM (CHR RAM)Mapper 2 (UxROM)232 Sprites8 Levels

> Decompressed Tile Data

Unlike most NES games, Contra has no CHR ROM. All tile graphics are RLE-compressed inside the 128KB PRG ROM and decompressed to CHR RAM at runtime. Each sheet below shows the left pattern table (sprites, left) and right pattern table (backgrounds, right) after decompression.

Level 1 Jungle
Level 1 JungleJungle greens

Bridge, mountains, water tiles and player sprites for the iconic first stage

Base Interior
Base InteriorGray metallic

Indoor base level tiles -- mechanical walls, corridors, and defense systems

Shared Patterns
Shared PatternsGrayscale

Common pattern table data used across multiple levels -- includes boss components

Large Sprite/BG Block
Large Sprite/BG BlockRed/gray

Massive tile bank with sprites and background elements for multiple stages

Level 3 Waterfall
Level 3 WaterfallGreen tones

Waterfall stage graphics -- cascading water, rocky terrain

Base Interior 2
Base Interior 2Gray metallic

Additional indoor base tiles -- turrets, barriers, alien structures

Turret Sprites
Turret SpritesRed/white

Rotating gun turrets and red cannon enemy sprites

Ending Scene
Ending SceneBlue/red

Victory scene tiles -- the helicopter escape and island explosion

Player Death
Player DeathRed tones

Sprite tiles for the player death animation

Title Screen
Title ScreenRed/blue

Title screen pattern tiles -- the CONTRA logo letters

Weapon Box
Weapon BoxDefault

Flying weapon capsule and power-up box sprites

Level 1 BG Tiles
Level 1 BG TilesDefault

Background pattern data for Level 1

> All Game Sprites

Contra has 232 unique sprite compositions. Each sprite is assembled from multiple 8x8 NES tiles using a variable-length encoding format. Sprite data is stored in PRG bank 1, with each entry defining tile positions, OAM attributes (flip/palette/priority), and offsets.

Player Character (Bill Rizer)

Standing
Standing0x02
Running 1
Running 10x03
Running 2
Running 20x04
Running 3
Running 30x05
Jumping
Jumping0x06
Aiming Up
Aiming Up0x07
Aiming Diag
Aiming Diag0x08
Prone
Prone0x09
Swimming 1
Swimming 10x0a
Swimming 2
Swimming 20x0b
Shooting
Shooting0x0c
Run+Shoot
Run+Shoot0x0d
Jump+Shoot
Jump+Shoot0x0e
Prone+Shoot
Prone+Shoot0x0f

Enemy Soldiers

Soldier Stand
Soldier Stand0x30
Soldier Run
Soldier Run0x31
Soldier Shoot
Soldier Shoot0x32
Soldier Jump
Soldier Jump0x33
Soldier Prone
Soldier Prone0x34
Soldier Die
Soldier Die0x35
Sniper
Sniper0x36
Sniper Aim
Sniper Aim0x37
Grenade Guy
Grenade Guy0x38
Scuba Diver
Scuba Diver0x39
Scuba Shoot
Scuba Shoot0x3a

Bosses & Aliens

Boss Core
Boss Core0x82
Boss Arm
Boss Arm0x83
Boss Segment
Boss Segment0x84
Alien Form
Alien Form0x92
Alien Head
Alien Head0x99
Alien Mouth
Alien Mouth0x9a
Alien Body
Alien Body0x9b
Alien Arm L
Alien Arm L0x9c
Alien Arm R
Alien Arm R0x9d
Alien Claw
Alien Claw0x9e
Alien Heart
Alien Heart0x9f

Weapons, Items & Effects

Bullet
Bullet0x20
Spread Shot
Spread Shot0x21
Fireball
Fireball0x22
Laser
Laser0x23
Explosion 1
Explosion 10x24
Explosion 2
Explosion 20x25
Small Blast
Small Blast0x47
Big Blast
Big Blast0x48
Ring Blast
Ring Blast0x49
Falcon Icon
Falcon Icon0x72
1UP
1UP0x79

> How the NES Engine Works

NES Architecture vs. AGI

The NES is a fundamentally different architecture from Sierra's AGI engine. Instead of a virtual machine running bytecode scripts, the NES runs native 6502 machine code directly on hardware.

CPU: Ricoh 2A03 (6502)

  • 8-bit processor, ~1.79 MHz
  • 2KB internal RAM
  • 56 instructions (no multiply/divide)
  • 3 registers: A (accumulator), X, Y
  • 16-bit address bus (64KB space)
  • Bank switching for >32KB games

PPU: Picture Processing Unit

  • 256x240 pixel output (NTSC)
  • 2 pattern tables: 256 tiles each (8x8, 2bpp)
  • Left table ($0000): sprites
  • Right table ($1000): backgrounds
  • 4 palettes of 4 colors each (sprites + BG)
  • 64 hardware sprites via OAM

Konami's RLE Compression

Contra's tile data is compressed using Konami's custom RLE format. This is why the game uses CHR RAM instead of CHR ROM — the data must be decompressed before the PPU can use it. Nearly 50% of the entire ROM is compressed graphics.

Konami RLE Decompression Format
Byte values and their meaning:

0xFF           -- End of data stream
0x7F aa bb     -- Set PPU write address to $bbaa
< 0x7F (N)     -- RLE: write next byte N times
>= 0x80 (N)    -- Literal: copy next (N & 0x7F) bytes

Example compressed data:
  7F 00 00     -- Set PPU address to $0000
  05 3C        -- Write 0x3C five times
  83 A1 B2 C3  -- Write 3 literal bytes: A1, B2, C3
  FF           -- End

The CPU bank-switches to load the correct PRG bank,
then runs the decompressor which writes directly to
the PPU via memory-mapped registers ($2006/$2007).

Mapper 2 (UxROM) Bank Switching

The NES CPU can only address 32KB of PRG ROM at a time, but Contra has 128KB. Mapper 2 solves this by bank switching:

Memory Map
$0000-$07FF  2KB Internal RAM
$2000-$2007  PPU Registers
$4000-$4017  APU + I/O Registers
$6000-$7FFF  SRAM (unused in Contra)
$8000-$BFFF  Switchable 16KB PRG bank (0-6)
$C000-$FFFF  Fixed 16KB PRG bank (always bank 7)

Bank layout:
  Bank 0: Game logic, enemy AI
  Bank 1: Sprite data, drawing routines
  Bank 2: Compressed graphics + level data
  Bank 3: Super-tile definitions, palettes
  Bank 4: Compressed graphics (levels)
  Bank 5: Compressed graphics (levels)
  Bank 6: Compressed graphics (levels)
  Bank 7: Fixed -- NMI handler, decompressor,
           level loading, core engine (always loaded)

NES Tile Format (2bpp Planar)

Each NES tile is 8x8 pixels, 2 bits per pixel (4 colors), stored in 16 bytes as two bit planes:

NES Tile Encoding (16 bytes per tile)
Bytes 0-7:  Bit plane 0 (low bit of each pixel)
Bytes 8-15: Bit plane 1 (high bit of each pixel)

Each byte = one row, bit 7 = leftmost pixel

Example tile row (y=0):
  Plane 0, byte 0: 0b01111110 = .XXXXXX.
  Plane 1, byte 8: 0b01000010 = .X....X.

  Combined pixels: .2111121.
    00 = transparent
    01 = color 1
    10 = color 2
    11 = color 3

256 tiles x 16 bytes = 4KB per pattern table
2 pattern tables = 8KB total in CHR RAM

NES Color Palette (subset)

Black
Dk Blue
Blue
Purple
Dk Red
Red
Brown
Dk Green
Green
Teal
Forest
Sea
Cyan
Gray
White
Lt Blue

The NES has a fixed palette of 64 colors. Each game selects 4 palettes of 4 colors for backgrounds and 4 palettes of 4 colors for sprites (32 colors total on screen).

Sprite Composition System

NES hardware sprites are only 8x8 or 8x16 pixels. Larger characters like the player are composed from multiple hardware sprites. Contra uses a variable-length encoding:

Sprite Data Format (PRG Bank 1)
Each sprite entry:
  Byte 0: Entry count (number of 8x8 OAM entries)
  Special values:
    $FE = small sprite (only 3 bytes total)
    $80 = address pointer (jump to another sprite)

  For each OAM entry (4 bytes):
    Byte 0: Y offset from sprite origin
    Byte 1: Tile index (always even -- left pattern table)
    Byte 2: Attributes
      Bits 0-1: Palette number (0-3)
      Bit 5: Priority (0=front, 1=behind BG)
      Bit 6: Horizontal flip
      Bit 7: Vertical flip
    Byte 3: X offset from sprite origin

The player character uses ~6-8 hardware sprites
composed into a single 24x32 pixel character.

> Level Structure

1JungleSide-scrolling

The iconic opening stage -- run through dense jungle, cross bridges, fight turrets

2Base 1Behind-view

3D perspective corridor -- destroy electric barriers and wall-mounted guns

3WaterfallVertical

Climb the waterfall while enemies attack from above -- unique vertical scrolling

4Base 2Behind-view

Second indoor base with tougher enemies and more complex barrier patterns

5Snow FieldSide-scrolling

Snowy mountain terrain with grenaders and tanks

6Energy ZoneSide-scrolling

High-tech energy corridors with moving flame traps

7HangarSide-scrolling

The alien hangar -- fight through to the final boss

8Alien's LairSide-scrolling

The heart of the alien base -- destroy the alien heart to win

Level Data Format

Each level is defined by a header that specifies which compressed graphic data blocks to load, the super-tile definitions (16x16 pixel metatiles composed of 4 tiles), palette data, and scroll behavior. Side-scrolling levels use horizontal nametable mirroring, while “base” levels use a pseudo-3D behind-view renderer.

> 6502 Assembly Highlights

Unlike AGI's bytecode VM, Contra runs native 6502 machine code. Here are annotated excerpts from the fully disassembled ROM showing key game mechanics.

The Konami Code -- 30 Lives Cheat (Bank 7)
; The famous cheat code input sequence
; Up, Up, Down, Down, Left, Right, Left, Right, B, A
; Stored as controller bit patterns in a lookup table

konami_code_sequence:
  .byte $08  ; Up
  .byte $08  ; Up
  .byte $04  ; Down
  .byte $04  ; Down
  .byte $02  ; Left
  .byte $01  ; Right
  .byte $02  ; Left
  .byte $01  ; Right
  .byte $40  ; B
  .byte $80  ; A

; When all 10 inputs match in sequence on the title screen:
  lda #$1e          ; Load 30 (decimal)
  sta PLAYER_LIVES  ; Set lives to 30
  ; This is checked during the title screen input handler
  ; The game tracks your position in the sequence via a counter
NMI Handler -- Vertical Blank Interrupt (Bank 7)
; Called 60 times per second (NTSC) when PPU enters vblank
; This is the heartbeat of the game -- all rendering happens here

nmi_handler:
  pha              ; Save registers
  txa
  pha
  tya
  pha

  lda #$00
  sta OAMADDR      ; Set OAM address to $00
  lda #$02
  sta OAMDMA       ; DMA transfer: copy $0200-$02FF to OAM
                   ; This updates all 64 sprite positions

  jsr write_palette_colors_to_ppu
  jsr write_cpu_graphics_buffer_to_ppu

  inc FRAME_COUNTER  ; Increment global frame counter

  pla              ; Restore registers
  tay
  pla
  tax
  pla
  rti              ; Return from interrupt
Player Collision Detection (Bank 0)
; Check if player bullet hits an enemy
; Compares bounding box coordinates

check_bullet_enemy_collision:
  lda BULLET_X
  sec
  sbc ENEMY_X        ; distance = bullet_x - enemy_x
  bcc @no_hit        ; if negative (bullet left of enemy), branch
  cmp #$08           ; within 8 pixels horizontally?
  bcs @no_hit        ; too far right

  lda BULLET_Y
  sec
  sbc ENEMY_Y        ; vertical distance
  bcc @no_hit
  cmp #$10           ; within 16 pixels vertically?
  bcs @no_hit

  ; Hit detected!
  jsr kill_enemy     ; trigger enemy death animation
  jsr remove_bullet  ; remove the bullet
  rts

@no_hit:
  rts

> Technical Details

How This Was Built

The Contra NES ROM was downloaded from Archive.org and analyzed using custom Python tools. Konami's RLE compression format was reverse-engineered from the annotated disassembly by vermiceli, then implemented as a Python decompressor. Sprite compositions were extracted from the 233 sprite definitions in PRG bank 1.

ROM Analysis

  • iNES header parsing
  • PRG bank mapping (8 x 16KB)
  • Entropy analysis to find tile data
  • Konami RLE decompression
  • NES 2bpp planar tile decoding

ROM Layout

128KB + 16B Header
iNES Header    16B
PRG Bank 0   16KB  Code
PRG Bank 1   16KB  Sprites
PRG Bank 2   16KB  GFX+Data
PRG Bank 3   16KB  Tiles
PRG Bank 4   16KB  GFX
PRG Bank 5   16KB  GFX
PRG Bank 6   16KB  GFX
PRG Bank 7   16KB  Engine

Tools Used

  • nes_common.py — ROM parser
  • extract_tiles.py — Raw tile renderer
  • decompress_tiles.py — RLE decompressor
  • Reference: vermiceli/nes-contra-us disassembly

King's Quest vs. Contra -- Two Eras of Game Engineering

King's Quest (1984)Contra (1988)
PlatformIBM PC / PCjrNintendo NES
CPUIntel 8088 (4.77 MHz)Ricoh 2A03 / 6502 (1.79 MHz)
CodeBytecode VM (AGI)Native 6502 assembly
GraphicsVector drawing commandsTile-based PPU hardware
Resolution160x168 (doubled to 320)256x240
Colors16 (EGA fixed)25 on-screen (from 64)
SpritesSoftware-rendered (RLE bitmaps)Hardware OAM (64 x 8x8)
CompressionNone (AGI v2)Konami RLE (~50% of ROM)
TextXOR encrypted (“Avis Durgan”)No text engine (HUD only)
Storage360KB floppy128KB cartridge