In the NES, a frame consists of the background and the sprites. Both the background and the sprites are composed of 8x8 pixel tiles from the pattern table. The background is 32 tiles wide and 30 tiles tall, for a total of 256x240 pixels. This data structure is called the nametable. Each byte in the nametable references an index into the pattern table.
The pattern table is the data structure that describes what the 8x8 tiles in the nametable and sprites look like. Each pattern table entry is 16 bytes. This is the fundamental drawing block of the NES.
There are 4 nametables in total, but only 2 are written to and the remaining two are copies of the initial 2 nametables (mirroring). Additionally, each nametable has 64-bytes at the end to specify which palette is assigned each part of the nametable. This is called the attribute table. Each entry in the attribute table specifies the palette for a 4x4 set of tiles in the nametable.
Finally, there are sprites, which are drawn on top of the nametable and can be easily moved around the screen.
Addresses specified in this article include both the address that is used by the NES CPU to locate data, as well as the offset address in the PRG ROM where the referenced address is. Note that a ROM file has a #$10 byte iNES header that doesn't exist in actual PRG ROM. So when using the ROM address to look for the location in a NES ROM file, you will need to add an addition #$10 bytes to the ROM address.
- Attribute Table - one of 2 64-byte tables (one for each nametable) that specifies which palettes to use for each part of the nametable
- Background - A combination of the nametable and the attribute table
- Graphic data - sections of compressed graphic data, typically pattern data, but can be nametable data, or even attribute data as well
- Nametable - matrix of bytes referencing items in the pattern table. Can generally be thought of as the background.
- Palette - a set of 4 colors that can be used for coloring a tile
- Pattern table - a 16 byte object defining the look of an 8px by 8px tile
- Screen - a set of super-tiles for a level that is 256 pixels wide
- For horizontal levels, the size of a screen is 8x7 super-tiles, and for a vertical level it is 8x8 super-tiles
- Sprite - a graphical element composed of pattern table tiles. Some
documentation refers to these as 'objects'.
- Sprites in Contra are 8x16, meaning 2 pattern table tiles make up each component of a sprite.
- Super-tile - a group of 4x4 set of tiles that are used to construct a level's nametable. This term isn't standard and was borrowed from part 2 of Allan Blomquist's 7-part series on Contra called Retro Game Internals. Some people may refer to this as a meta tile.
- Tile - an element of the nametable, represents an 8x8 pixel from the pattern table
Usually, for attribute, palette, and sprite data, the data is first loaded from the PRG ROM and then moved into CPU memory. Then, every frame that data is read from CPU memory into the PPU to display. Pattern table data is read directly from PRG ROM into the PPU.
The graphic data is in various places throughout the PRG ROM. For example (with the exception of the intro sequence and ending sequence), the palette colors are in bank 7, while the attribute table data is mostly in bank 3. For the game pattern table data, this is stored in compressed blocks in banks 2, 4, 5, and 6. I refer to these blocks of data as 'graphic data', because while it mostly stores pattern table data, in some cases (intro, outro, and clearing screen) it does store nametable, and attribute data.
The graphic data in the game ROM are compressed using a encoding system known as Run-length encoding (RLE). This algorithm is covered in another section in this document. As the graphic data are loaded into PPU memory from ROM memory, they are decompressed.
A level contains multiple "graphic data". These make up all the graphics (minus sprites) necessary for a level.
As part of loading graphics a level, the load_level_graphic_data
label is
executed, this looks up the appropriate graphic data from the
level_graphic_data_tbl
and then immediately proceeds to decompress and write
the data to the PPU with label write_graphic_data_to_ppu
.
As an example, the 7 graphic data used for level 1 level_1_graphic_data
(ROM: $1c8fd, MEM: $c8fd) are as follows: #$03, #$13, #$19, #$1a, #$14, #$16,
#$05, #$ff. Each byte is an offset entry into the graphic_data_ptr_tbl
table.
load_level_graphic_data
will load each of those graphic data offsets into the
A register and then call write_graphic_data_to_ppu
, which decompresses and
writes the graphics into the PPU. This is repeated by load_level_graphic_data
until the offset specified is #$ff, which signifies there are no more graphics
data for the level to load.
While in theory you could specify the nametable with this same scheme, the
level's nametables are not specified in these graphics data in general and
instead graphics data is used only for pattern and attribute data. The only
time the graphics data contains nametable data is for either blanking the
nametable (graphic_data_00
(ROM: $1cb36, MEM: Bank 7 $cb36)), or setting up
the nametable for the intro and ending scenes (graphic_data_02
and
graphic_data_18
respectively). Level nametable information is populated by
"Super-Tiles". Read that section for more information.
In general, the left pattern table, PPU addresses [$0000-$1000), is used for storing pattern tiles associated with sprites. The right pattern table, PPU addresses [$1000-$2000), is used for storing pattern tiles associated with the nametable. This is useful to understand, because sprites are displayed with 2 pattern table tiles, making up a 8x16 sprite. So, when viewing the PPU during a level with any debugging tools, it's appropriate to view the left pattern table as 8x16 sprites if that option is available.
Below is a table of all graphic data locations in the ROM (excluding alternate graphics data).
The PPU Address ranges are specified in interval notation.
- The square brackets [] represent 'closed interval' and the address is included in the range
- The parentheses () represent 'open interval' and are not included in the range
Label Name | Bank | Label In-Memory Address | PRG ROM Address | Graphics Data | PPU Addresses | Comments |
---|---|---|---|---|---|---|
graphic_data_00 |
7 | $cb36 |
$1cb36 |
Both nametables and Both Attribute tables | [$2000-$2800) |
Sets all Nametable + Attribute table data to #$00. |
graphic_data_01 |
4 | $aa2d |
$12a2d |
Left and Right Pattern tables | [$0ce0-$1f80) |
Used for intro screen, level title screens, and game over screens. Contains Contra logo, Bill and Lance, letters, numbers, and falcon tiles. |
graphic_data_02 |
2 | $9097 |
$9097 |
Nametable 0 and Attribute table 0 | [$2000-$2400) |
Used for intro screen, contains layout of tiles for intro screen text. |
graphic_data_03 |
4 | $8001 |
$10001 |
Left Pattern table | [$0000-$0680) |
Used in every level. Contains Bill and Lance outdoor sprite tiles, game over letters, lives medals, power-ups (SBFLRM), and explosions. |
graphic_data_04 |
4 | $85ae |
$105ae |
Left Pattern table | [$0680-$08c0) |
Used in indoor/base levels. Player sprite pattern tiles. |
graphic_data_05 |
5 | $8001 |
$14001 |
Left and Right Pattern tables | [$09a0-$0a80) , [$0dc0-$1200) , [$1320-$1600) , [$1bd0-$2000) |
Level 1 bridge, mountain, trees, and water tiles. Player prone, and flying capsule tiles. |
graphic_data_06 |
4 | $99fc |
$119fc |
Left and Right Pattern tables | [$08c0-$1100) |
Used in indoor/base levels. Indoor player sprites, grenades, and som indoor/base background graphics |
graphic_data_07 |
5 | $8a61 |
$14a61 |
Left and Right Pattern tables | [$09a0-$0a80) , [$0dc0-$1200) , [$1320-$1600) , [$1bd0-$2000) |
Level 3 background and sprite pattern table tiles. Player prone, and flying capsule tiles. |
graphic_data_08 |
4 | $886c |
$1086c |
Left and Right Pattern tables | [$09a0-$2000) |
Indoor/base boss screen background and sprite pattern table tiles. |
graphic_data_09 |
4 | $99cd |
$119cd |
Left Pattern table | [$0b00-$0b40) |
Level 4 boss screen sprite pattern table tiles. Just 3 tiles. |
graphic_data_0a |
4 | $a005 |
$12005 |
Right Pattern table | [$1100-$1520) |
Indoor/base pattern table tiles. Same as graphic_data_10 , but horizontally flipped. |
graphic_data_0b |
5 | $93e0 |
$153e0 |
Left and Right Pattern tables | [$09a0-$0a80) , [$0dc0-$1200) , [$1320-$1600) , [$1bd0-$2000) |
Level 5 pattern table tiles. |
graphic_data_0c |
6 | $8001 |
$18001 |
Left and Right Pattern tables | [$09a0-$0a80) , [$0dc0-$0ee0) , [$0fc0-$1200) , [$1320-$2000) |
Level 6 pattern table tiles. |
graphic_data_0d |
6 | $8cdc |
$18cdc |
Left and Right Pattern tables | [$09a0-$0a80) , [$0dc0-$0ee0) , [$0fc0-$1200) , [$1320-$2000) |
Level 7 pattern table tiles. |
graphic_data_0e |
6 | $9bd6 |
$19bd6 |
Left and Right Pattern tables | [$09a0-$2000) |
Level 8 pattern table tiles. |
graphic_data_0f |
4 | $a346 |
$12346 |
Right Pattern table | [$1520-$1600) |
Indoor/base pattern table tiles. #$0e background tiles total. |
graphic_data_10 |
4 | $a003 |
$12003 |
Right Pattern table | [$1600-$1a20) |
Indoor/base pattern table tiles. Same as graphic_data_0a , but horizontally flipped. |
graphic_data_11 |
4 | $a3e7 |
$123e7 |
Right Pattern table | [$1a20-$2000) |
Indoor/base background pattern table tiles. |
graphic_data_12 |
4 | $a940 |
$12940 |
Right Pattern table | [$1b90-$1ca0) |
Level 4 background pattern table tiles. |
graphic_data_13 |
4 | $87a1 |
$107a1 |
Left Pattern table | [$08c0-$09a0) |
Player top-half aiming angled up and player aiming straight. Also contains the laser bullet sprites. |
graphic_data_14 |
5 | $a814 |
$16814 |
Right Pattern table | [$1600-$1bd0) |
Rotating gun and red turret pattern table tiles. |
graphic_data_15 |
6 | $b07a |
$1b07a |
Left Pattern table | [$0ee0-$0fc0) |
Level 5, 6, and 7 sprite pattern table tiles for turret man (basquez). |
graphic_data_16 |
6 | $b15c |
$1b15c |
Right Pattern table | [$1200-$1320) |
Weapon box pattern table tiles. |
graphic_data_17 |
5 | $addf |
$16ddf |
Left and Right Pattern tables | [$0a60-$0fe0) , [$15b0-$18a0) |
Ending scene pattern table tiles. Includes helicopter sprite tiles and island background tiles. |
graphic_data_18 |
5 | $b30d |
$1730d |
Nametable 0 and Attribute table 0 | [$2000-$2400) |
Nametable and attribute table data for ending scene. |
graphic_data_19 |
5 | $a31b |
$1631b |
Left Pattern table | [$0680-$08c0) |
Player killed sprite tiles: recoil from hit and lying on ground. |
graphic_data_1a |
5 | $a500 |
$16500 |
Left Pattern table | [$0a80-$0dc0) |
Enemy soldier sprite pattern table tiles. |
Interestingly, the outdoor player prone and flying capsule sprite tiles are duplicated on all outdoor levels.
The tiles in the pattern tables are ordered by which collision group they are in. There are 4 collision groups in Contra. The level header byte offsets #$09, #$0a, and #$ab define the boundaries of each collision group. For example, level 1's tile collision boundaries are $06, $f9, and $ff. This means
- Pattern tile index $00 has collision code 0 (empty). This is always true
- Pattern tile indexes $01-$05 have collision code 1 (floor)
- Pattern tile indexes $06-$f8 have collision code 0 (empty)
- Pattern tile indexes $f9-$fe have collision code 2 (water)
- Pattern tile index $ff has collision code 3 (solid)
For this reason, sometimes there are duplicate pattern table tiles, simply to create barriers.
The collision codes are defined as follows. Technically Collision Code 2 is treated as Collision code 0 #$00
- Collision Code 0 (Empty) - tiles that have no collision data. Objects can freely pass through the tile
- Collision Code 1 (Floor) - tiles that objects can land on when falling but pass through when moving left, up and right. Can fall through (drop down) with d-pad down, plus A only when there is another non-empty object below (or on vertical level).
- Collision Code 2 (Water) - similar to code 1, but for water, prevents jumping, and causes player/enemy sprites to show only top half
- Collision Code 3 (Solid) - The tile is solid and cannot be gone through regardless of directions or side.
For outdoor levels, the nametable collision codes are stored in CPU memory
BG_COLLISION_DATA
[$0680-$0700). Nametable collision codes are only stored
for every other column and every other row of the nametable. Since a super-tile
is 4 columns wide and 4 columns tall, a single super-tile has 4 collision code
points: top left, middle left, middle top, and middle middle.
Each byte in BG_COLLISION_DATA
can be broken down into 4 collision codes.
Every 2 bits represents the collision code (0, 1, 2, or 3) for every other
pattern table tile. The first 4 bytes are for the top row of collision points
for a single nametable row. The next 4 bytes are the next nametable row of
pattern table tiles. This pattern repeats until all of the first nametable data
is specified and then is repeated for the 2nd nametable. For a given background
collision index, add #$04 to determine the next row down. The following is an
example of the byte offsets from BG_COLLISION_DATA
for the top left nametable.
Note that next nametable starts at offset #$40 and not #$38.
00 00 00 00 01 01 01 01 02 02 02 02 03 03 03 03
04 04 04 04 05 05 05 05 06 06 06 06 07 07 07 07
08 08 08 08 09 09 09 09 0a 0a 0a 0a 0b 0b 0b 0b
0c 0c 0c 0c 0d 0d 0d 0d 0e 0e 0e 0e 0f 0f 0f 0f
10 10 10 10 11 11 11 11 12 12 12 12 13 13 13 13
14 14 14 14 15 15 15 15 16 16 16 16 17 17 17 17
18 18 18 18 19 19 19 19 20 20 20 20 21 21 21 21
22 22 22 22 23 23 23 23 24 24 24 24 25 25 25 25
26 26 26 26 27 27 27 27 28 28 28 28 29 29 29 29
30 30 30 30 31 31 31 31 32 32 32 32 33 33 33 33
34 34 34 34 35 35 35 35 36 36 36 36 37 37 37 37
Indoor/base levels do not really rely on the BG_COLLISION_DATA
for background
collision detection. The level header byte offsets #$09, #$0a, and #$ab for
the indoor/base levels are #$00, #$ff, #$ff, which means all tiles are collision
code 0, i.e. empty. The player is prevented from walking past the left and
right on indoor/base levels walls manually in the code, e.g. see
level_right_edge_x_pos_tbl
, and level_left_edge_x_pos_tbl
.
Contra's level nametable data is made up of "Super-tiles". Each super-tile is #$10 bytes long and made of 4 rows of 4 pattern table entries each. Super-tiles are not RLE-encoded. A super-tile is how the level nametable's are defined and can be thought of as smallest unit of art that can be used for nametable drawing in a level. This isn't technically true, because a nametable can be updated after it's drawn for certain animations (claw, fire beam, etc). For horizontal levels, a background is composed of #$38 super-tiles (8x7). For the vertical level, a background is composed of #$40 super-tiles (8x8).
Within a super-tile, there are 4 2x2 sections each #$4 bytes long.
The location of the pattern table tiles of each super-tile pattern is specified
in the 2-byte level header data byte #$04 (LEVEL_SUPERTILE_DATA_PTR
). For
example, level 1's super-tile data is defined at level_1_supertile_data
(ROM: $c001, MEM: Bank 3 $8001). Each #$10 bytes is a single super-tile. Level
1 has a total of #$3b distinct super-tiles.
For each level, the 2-byte level header offset byte #$02
(LEVEL_SCREEN_SUPERTILES_PTR
) specifies which super-tiles are part of each
'screen'. A 'screen' is the set of #$38 or #$40 super-tiles that make up a
single background. For an example, level 1's screen super-tile index data is
located at level_1_supertiles_screen_ptr_table
(ROM: $8001, MEM: Bank 2 $8001). Each entry specifies the super-tiles used for a
single screen. The super-tiles used in the first screen of level 1 are
specified at level_1_supertiles_screen_00
(ROM: $801d, MEM: Bank 2 $801d).
This data is compressed with RLE-encoding
Super-tiles indexes are stored in CPU memory and referenced when drawing the
background. 2 screens of super-tile indexes are stored in memory at a time.
This is to support scrolling. The super-tiles indexes for the screens are
loaded into CPU memory at location $0600 (LEVEL_SCREEN_SUPERTILES
) by
load_supertiles_screen_indexes
and used when writing the pattern table tile
data to CPU memory by load_level_supertile_data
. The writing of the
super-tiles to the nametable takes place incrementally, one nametable column at
a time.
Sprites are movable objects that are composed of elements from the pattern
table. This documentation refers to a group of sprites together as a single
sprite. For example, Bill is composed of 5 sprites, but will spoken about as
a single sprite. Sprites in Contra are composed of 2-block pattern table
entries stacked on top of each other, making the smallest part of a sprite 8x16.
Like most NES games, Contra does not write sprite data directly to the PPU
byte by byte via the $2004
(OAMDATA) address but instead utilizes the $4114
(OAMDMA) address. OAMDMA stands for Object Attribute Memory Direct Memory
Access. The OAMDMA address is set to #$02, which tells the PPU to load sprite
data from the address range $0200
to $02ff
inclusively every frame.
All of the sprites in Contra are in bank 1 starting with sprite_00
(ROM:
$71ce, MEM: Bank 1 $b1ce). Each sprite can be referred to by an index starting
from 00. I refer to these as the "sprite code" or "sprite number". There are
two large pointer tables that point to each sprite address: sprite_ptr_tbl_0
and sprite_ptr_tbl_1
.
Sprites are drawn on the screen every frame after the current game routine is
run. This done in the draw_sprites
label (ROM: $6e97, MEM: Bank 1 $ae97).
draw_sprites
uses data stored in $0300
to $0367
inclusively. I refer to
this area of memory as the sprite cpu buffer. Contra supports a maximum of
#$19 sprites on screen simultaneously. However, the certain addresses are
reserved for "player" sprites, whereas other addresses are reserved for enemy
sprites. The list below shows how the sprite CPU buffer is partitioned
Address Range | Length | Alias | Usage |
---|---|---|---|
$0300 to 0309 |
#$0a bytes | PLAYER_SPRITES | sprite number for player and player bullets (sprite_ptr_tbl or sprite_ptr_tbl_1 |
$030a to 0319 |
#$10 bytes | ENEMY_SPRITES | enemy sprite number (sprite_ptr_tbl or sprite_ptr_tbl_1 ) |
$031a to 0323 |
#$0a bytes | SPRITE_Y_POS | y position on screen of the each sprite |
$0324 to $0333 |
#$10 bytes | ENEMY_Y_POS | y position of each enemy sprite |
$0334 to $033d |
#$0a bytes | SPRITE_X_POS | x position on screen of the each sprite |
$033e to $034d |
#$10 bytes | ENEMY_X_POS | x position on screen of the each enemy sprite |
$034e to $0357 |
#$0a bytes | SPRITE_ATTR | sprite attributes (palette, whether to flip horizontally, vertically) |
$0358 to $0367 |
#$10 bytes | ENEMY_SPRITE_ATTR | sprite attributes (palette, whether to flip horizontally, vertically) |
draw_sprites
loops from #$19 to #$00 and uses the data from the sprite CPU
buffer to populate the #$100 bytes $0200
to $02ff
. This data is then moved
to the PPU with OAMDMA when in vertical blank nmi_start
.
One neat trick that's done when loading data from the sprite CPU buffer to the
$0200
page memory is that every time the draw_sprites
label is called, an
offset of #$4c is added to the write address. This causes the sprites to be
placed in a different starting spot in the $0200
page every frame. This is
done to work around a limitation of the NES that restricts a scan line from
having more than 8 sprites. By moving the sprites around in memory, if there are
too many sprites on a scan line, different sprites will be drawn on different
frames. The player shouldn't be able to detect a sprite missing if drawn quickly
enough.
When filling the OAMDMA data, the sprite tiles aren't written sequently, but rather spaced every 49 sprite tiles apart (#$c4 bytes) wrapping around. I'm not sure why this was done. Perhaps it has to do with concern about dynamic RAM decay that OAM has.
The sprite data in bank 1 is encoded. Except when #$fe, the first byte specifies the number of entries in the sprite. Then there are that many groups of 4-bytes. Each #$4 bytes specify two tiles that are stacked vertically. Except when the first byte is #$80, these four bytes follow the PPU OAM byte specification. For details on this data structure, NES Dev Wiki has a good article on this. These specifics are also outlined below.
- Byte 0 specifies the y position of the tile relative to where the sprite is
placed on the screen. This can be negative.
- If Byte 0 is #$80, then it is a signal to change the read address. The next two bytes specify the CPU address to move to. This functionality allows sprites to share pieces from other sprites.
- Byte 1 is the tile number, i.e. which tile in the pattern table to use.
- Since sprites are built from 8x16 components, and byte 1 only specifies the first tile, the second tile stacked immediately below this tile is always the next one in the pattern table. For example, if byte 1 is #$05, then the two tiles will be #$05 and #$06. If the tile specified is even, then the tile
- All of Contra's sprites are composed of even bytes, meaning the tile is pulled from the left pattern table ($0000-$0fff).
- Byte 2 specifies various attributes that can be used to modify the tiles
- Bit 7 (
x... ....
) specifies vertical flip- When the vertical flip bit is set, the top and bottom tiles are drawn vertically flipped. Also, the top tile is drawn at the bottom and the bottom tile is drawn at the top.
- Bit 6 (
.x.. ....
) specifies horizontal flip- Whether or not to flip the tile horizontally
- Bit 5 (
..x. ....
) specifies drawing priority- 0 = in front of background; 1 = behind background
- Bits 1 and 0 (
.... ..xx
) specify the palette code- These bits specify the palette to use for the sprite
- Bit 7 (
- Byte 3 specifies the x position of the tile relative to where the sprite is placed on the screen. This can be negative.
When the first byte of the sprite code is #$fe, the sprite is a "small" sprite. Small sprites are composed of #$3 bytes, including the #$fe byte. Small sprites always have their X position shifted left by #$04 and their Y position shifted up by #$08.
- Byte 0 is #$fe and signifies the sprite is a small sprite
- Byte 1 specifies the tile number
- Byte 2 is the sprite attributes
Note: Sprites are also referred to as objects. This explains the names of some addresses in NES like Object Attribute Memory (OAM).
The NES supports 4 palettes for the nametables (background) and 4 palettes for sprites. A palette is a set of 4 colors. For nametables, one palette is shared for each square 16x16 set of 4 pattern table tiles. For sprites, each tile in a sprite has its own palette.
Defining which palette is assigned to which super-tile is located in bank 3. The
level headers specify the location in bank 3 of where the super-tile palette
data is defined (see LEVEL_SUPERTILE_PALETTE_DATA
). This data is used to
populate the attribute table.
For each sprite tile, the palette used is specified in the two least significant bits of byte 2 of the sprite OAM data. For details about how sprites are stored in the PRG ROM, see Sprite Number Encoding.
The initial colors that are associated with each palette are specified in the
level header starting at byte offset 15 (see LEVEL_PALETTE_INDEX
). Each of
the next 8 bytes specify an entry into game_palettes
. The first 4 bytes are
the nametable palette colors, and the next 4 bytes are the sprite palette
colors. Each entry in game_palettes
specifies the 3 colors that make up the
palette. Each palette always starts with a black color #$0f. Combined this
is how the 4 colors of each palette are determined.
Contra supports a fade in effect for nametable palettes. This effect is used
various parts of the game like base/indoor boss screens, dragon, and boss ufo.
This is implemented by adjusting the specified nametable palette colors by an
amount specified in palette_shift_amount_tbl
when BG_PALETTE_ADJ_TIMER
is
specified and within range [#$01-#$09]. To utilize this effect, the code sets
the BG_PALETTE_ADJ_TIMER
to a value larger than #$09. Every frame, this value
is decremented. When loading the palette colors for the frame, if
BG_PALETTE_ADJ_TIMER
is greater than #$09, then the palette color will be
black, but once the value is in range, the palette colors will start to be
modified. For example, to create the fade-in effect of the base/indoor boss
screen background, the value of BG_PALETTE_ADJ_TIMER
is set to #$0c. This
gives #$03 frames of black, before the fade in effect starts.
Contra cycles the colors within nametable palettes during game play. The
palettes specified by the nametable don't change, but rather the colors within
the palettes are changed. By swapping out the colors, Contra can create
animations like blinking stars, waves in water, waterfalls, etc. Each level
natively supports having its 4th nametable (background) palette cycle through up
to 4 different sets of colors, where each set of colors is represented by an
index into the game_palettes
table. The specific indexes to cycle through are
in the level headers (LEVEL_PALETTE_CYCLE_INDEXES
). Level 3 (Waterfall) is
special in that it only cycles among 3 different sets of colors instead of 4
(see lvl_palette_animation_count
).
Additionally, all levels cycle their 3rd nametable palette colors through a
single, shared set of palette indexes (level_palette_2_index
). This cycle of
colors is a flashing red. It is used so that enemies have flashing red lights.
Finally, there are a few special nametable cycles, like for indoor (base) boss
screens (indoor_boss_palette_2_index
), and ending screen
(ending_palette_2_index
).
Typically sprites don't have any palette color cycling support natively, but when invincible, the player sprites will cycle the palette colors.
Like sprite data and nametable data, the palette color data is loaded from PRG
ROM into CPU memory first. Then every frame, that data is read and loaded into
PPU memory. Every 8 frames, the LEVEL_PALETTE_CYCLE
is incremented so that the
palettes colors are changed.
Every frame, the load_palette_indexes
looks at the current
LEVEL_PALETTE_CYCLE
and uses that to determine the appropriate indexes into
game_palettes
to store into CPU memory $52 and $53 (LEVEL_PALETTE_INDEX
).
Then load_palette_colors_to_cpu
will look up the colors from the
game_palettes
table and store the colors in CPU memory starting at $07c0
(PALETTE_CPU_BUFFER
). Then in write_palette_colors_to_ppu
, the data is read
from $07c0 to $07df and written to the PPU starting at $3f00
The graphic data for Contra is largely compressed with a relatively basic compression algorithm known as Run-length encoding (RLE).
The idea of this algorithm is that the graphics data will have long "runs" of repeated information. So instead of specifying the same byte over and over, it can be encoded so that the number of repetitions is specified. In addition, Contra's algorithm specifies when a run of bytes isn't the same. Below is an example that can help clarify.
Below is an example of un-compressed graphics data
#$00 #$00 #$00 #00 #$00 #$00 #$0e #$1f #$07 #$04 #$c0
When compressed, it becomes
#$06 #$00 #$85 #$0e #$1f #$07 #$04 #$c0 #$ff
The special codes in this sequence are #$06, #$85, and #$ff
#$06 means the next byte #$00 is repeated 6 times. Since the next code has bit 7 set, #$85 means to write the next 5 bytes to the PPU. Finally, #$ff means the sequence is complete.
Interestingly, this algorithm is implemented twice in Contra: once for pattern
table tiles (write_graphic_data_to_ppu
and once again for super-tile
screen indexes (load_supertiles_screen_indexes
). Each implementation is
slightly different.
The graphic data is loaded from ROM directly to the PPU. Not only is the
graphic data compressed, it also includes command bytes that specify where in
the PPU to write the data to. This section outlines how the data is read and
decompressed. This logic is all handled by the write_graphic_data_to_ppu
label in bank 7.
The graphic data are simply parts of the ROM that contain bytes. The first two
bytes of any graphic data are the starting address of where to write the graphic
data to on the PPU. For example, the first two bytes of graphic_data_04
are
#$80, #$06. This means that the graphics data will be written to the PPU
starting at PPU address $0680 (pattern table).
After the first two bytes are read, the following algorithm reads the graphics data section. When reading the graphic data, if the most significant bit of the graphic data byte is set, i.e 1, then the byte is treated as a command byte. There are 4 command byte types, evaluated in the following order
- #$ff - specifies the end of graphic data
- #$7f - command byte specifies to change PPU write address to the next 2 bytes specified.
- less than #$7f - or alternatively, any command byte that has its most significant bit clear is treated as a RLE command to write the subsequent byte to the PPU multiple times. The number of times to repeatedly write the next byte is specified by the command byte value.
- greater than #$7f - or alternatively, any command byte that has its most significant bit set is (but not #$ff) is interpreted as a command to write the next string of bytes to the PPU. The number of bytes to write to the PPU is specified by bits 0-6 of the command byte.
Below is some pseudocode for decompressing a graphics section and writing it to the PPU. Note that due to the implementation, you can only flip graphics data horizontally when that data only writes to a single PPU address location.
parse_ppu_address() {
byte b = read_next_byte();
write_to_PPUADDR(b);
b = read_next_byte();
write_to_PPUADDR(b);
if(flip_horizontally) {
read_next_byte();
read_next_byte();
}
}
while(true) {
parse_ppu_address();
byte b = read_next_byte();
if(b == 0xff) {
// finished decompressing graphics data
return;
}
while(true) {
if(b == 0x7f) {
// finished reading one section of compressed graphic
// next bytes are new PPU address
break;
}
if(b < 0x7f) {
// write same byte multiple times
byte numberOfRepetitions = b;
for (byte i = 0; i < numberOfRepetitions; i++) {
if(flip_horizontally) {
b = flip_horizontally(b);
}
write_to_PPUDATA(b);
}
} else if(b > 0x7f) {
// write next numberOfBytesToWrite bytes directly to PPU
byte numberOfBytesToWrite = b & 0x7f;
for (byte i = 0; i < numberOfBytesToWrite; i++) {
byte d = read_next_byte();
if(flip_horizontally) {
d = flip_horizontally(d);
}
write_to_PPUDATA(d);
}
}
}
}
Level section data not encoded exactly the same way
level_1_supertiles_screen_ptr_table
level_graphic_data_tbl
(ROM: $1c8e3, MEM: Bank 7 $c8e3) specifies which pattern table data is loaded for a level, indexes intographic_data_ptr_tbl
graphic_data_ptr_tbl
(ROM: $1c950, MEM: $c950) specifies locations in banks 2, 4, 5, 6, or 7 of RLE compressed graphic data (typically pattern table data)LEVEL_SUPERTILE_DATA_PTR
is specified in the level headers and points to the compressed makeup of the level's super-tiles, entries are into the pattern table. It is a 2-byte pointer stored in the CPU at address $44.LEVEL_SCREEN_SUPERTILES_PTR
is specified in the level headers and it points to a table whose entries are pointers to data specifying which super-tiles are loaded in each screen of the level. That data is RLE-compressed. It is a 2-byte pointer stored in the CPU at address $42.
load_level_graphic_data
(ROM: $1c8c6, MEM: Bank 7 $c8c6) decompresses and loads the "Graphic data" for the current levelload_current_supertiles_screen_indexes
(ROM: $1e169, MEM: Bank 7 $e169) decompresses and loads the super-tile indexes into memoryload_level_supertile_data
(ROM: $1df48, MEM: Bank 7 $df48) decompresses and loads the super-tiles from the pattern tablewrite_graphic_data_to_ppu
(ROM: $1c9a1, MEM: Bank 7 $c9a1) loads and decompresses the entire graphic data specified by the A register as offset intographic_data_ptr_tbl
@flush_cpu_graphics_buffer
(ROM: $1cc3f, MEM: $cc3f) writes all the data in the CPU graphics buffer to the PPUdraw_sprites
(ROM: $6e97, MEM: Bank 1 $ae97) populates $0200-$02ff with sprite data for OAMDMA.