This commit is contained in:
Michael Miceli 2023-04-26 20:04:47 -04:00
commit 8db7384d89
281 changed files with 40055 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
*.o
*.dbg
*.deb
*.nes
*.prg
*.bin

232
README.md Normal file
View File

@ -0,0 +1,232 @@
# Overview
This repository contains an annotated disassembly of the _Contra_ (US) NES ROM
and the build script(s) to reassemble the assembly into a byte-for-byte match of
the game. This repo also contains supplemental documentation, diagrams,
scripts, and tools to further understand the game.
A special thanks goes to Trax. This project would not have gotten started
without the amazing initial disassembly by him for his [Revenge of the Red
Falcon](https://www.romhacking.net/hacks/2701/) project.
```
|-- docs - supplemental documentation
| |-- attachments - files used in other documentation
| |-- diagrams - mermaid diagram files documenting program flow
| |-- lua_scripts - lua scripts for mesen and fceux
| |-- sprite_library - extracted sprites for ease of viewing
|-- src - the source code for the game
| |-- assets - the compressed graphics data and encoded audio for the game
|-- assets.txt - a list of assets, their offset in baserom.nes and their length
|-- build.bat - build script for Windows cmd (no PowerShell)
|-- build.ps1 - recommended build script for Windows (PowerShell)
|-- build.sh - bash build script for Linux/mac
|-- contra.cfg - memory mapping of PRG banks used when building
|-- README.md
|-- set_bytes.vbs - script used by build.bat to extract data from baserom.nes
```
# Building
## Prerequisites
* This repo does not include all assets (graphics data and audio data)
necessary for assembling the ROM. An existing copy of the game is required.
Place a copy of the US NES version of _Contra_ with the name `baserom.nes` in
the project root folder. The file will be used by the build script to extract
the necessary assets. The MD5 hash of `baserom.nes` ROM should be
`7BDAD8B4A7A56A634C9649D20BD3011B`.
* Building requires the [cc65 compiler suite](https://cc65.github.io/) to
assemble and link the 6502 assembly files. Please install it and ensure the
bin folder is added to your path.
## Instructions
There are 3 build scripts in this repository. All of them do the same thing. To
build the resulting .nes rom file, simply execute the appropriate build script
based on your environment.
```
.\build.ps1 <-- Windows
.\build.bat <-- Windows (no PowerShell)
./build.sh <-- Unix
```
* `build.ps1` - PowerShell script recommended for building on Windows machines.
* `build.bat` - bat script that can be used on windows machines without
PowerShell, but requires VBScript support.
* `build.sh` - bash script to be used in unix environments, or on Windows
environments with bash support (Git bash, WSL, etc.)
## Documentation
Supplemental materials have been added that help explain interesting features of
the code. Below are some of the more important documents.
* `docs/Aim Documentation.md` - documentation on how enemy aiming works
* `docs/Bugs.md` - bugs identified while disassembling
* `docs/Contra Control Flow.md` - detailed look at the game routine and level
routine flows.
* `docs/Enemy Glossary.md` - documentation on every enemy type in the game
* `docs/Enemy Routines.md` - documentation on level enemy configuration and
random soldier generation.
* `docs/Graphics Documentation.md` - documentation on pattern tables,
nametables, palettes, palette cycling, super-tiles, background collision, and
compression.
* `docs/Sound Documentation.md` - documentation on the audio engine used as well
as information on all sounds from the game.
All sprites were captured and labeled for easy reference in
`docs/sprite_library/README.md`
### Getting Started
At first, there is a lot to take in and it may be confusing where to start.
The files in the repo under the `src/` folder are broken up based on the memory
banks in the game. The _Contra_ cartridge is a
[UxROM](https://www.nesdev.org/wiki/UxROM) cartridge. The game has 8, 16 KiB
banks of memory for a total size of 128 KiB. Banks 0 through 6 are swapped in
and out of memory at address $8000-$bfff and bank 7 is always in memory at
$c000-$ffff. Knowing this helps understand the code since only 2 banks are
available at any given time. The start of the game and most of the game engine
logic exists in `bank7.asm` since that's in memory all of the time and where
the 3 NES interrupts are.
I think a good way to start reading this code is to look into
`docs/Contra Control Flow.md`. That file shows the responsibilities of the game
engine loop routines and in what order those routines are called.
* `bank0.asm` - used exclusively for enemy routines. Enemy routines are the
logic controlling enemy behaviors and settings: AI, movements, attack
patterns, health, etc. Almost every enemy is coded in bank 0, but some
enemy routines, usually those who appear in more than one level, are in
`bank7.asm`.
* `bank1.asm` - responsible for audio and sprites. The audio code takes up
about 3/4 of the bank. The remaining 1/4 of the bank is for sprite data and
code to draw sprites.
* `bank2.asm` - starts with RLE-encoded level data (graphic super tiles for the
level screens). It then contains compressed tile data and alternate tile data
and occasional attribute table data. Then, bank 2 contains logic for setting
the players' sprite based on player state. Next, bank 2 contains the level
headers, which define specifics about each level. Bank 2 then has the data
that specifies which enemies are on which screen and their attributes. Bank
2 also contains the soldier enemy generation code.
* `bank3.asm` - starts with the data that specifies which pattern table tiles
comprises super-tiles along with the color palettes. This bank also has the
routines to manage the end of levels.
* `bank4.asm` - mostly contains compressed graphic data. The rest of bank 4 is
the code for the ending scene animation and the ending credits, including the
ending credits text data.
* `bank5.asm` - mostly contains compressed graphic data. The rest of bank 5 is
the code and lookup tables for automated input for the 3 demo (attract)
levels.
* `bank6.asm` - contains compressed graphics data, data for short text sequences
like level names and menu options. Bank 6 also contains the code for the
players' weapons and bullets.
* `bank7.asm` - the core of the game's programming. Reset, NMI, and IRQ vectors
are in this bank and is the entry point to the game. Bank 7 contains the code
for drawing of nametables and sprites, bank switching, routines for the intro
sequence, controller input, score calculation, graphics decompression
routines, palette codes, collision detection, pointer table for enemy
routines, shared enemy logic, score table, enemy attributes, and bullet angles
and speeds, and the NES undocumented footer, among other things.
### Annotations
While reviewing the assembly, I used a notation to categorize unexpected, or
interesting observations. You can search the code for these annotations.
* `!(BUG?)` - a possible bug
* `!(HUH)` - unexpected code or logic used
* `!(WHY?)` - unsure of why the code is the way it is
* `!(UNUSED)` - unused code or data that is never executed nor read
* `!(OBS)` - observation or note
# Project History
I first became interested in this project after watching the
[Summoning Salt](https://www.youtube.com/channel/UCtUbO6rBht0daVIOGML3c8w) video
[The History of Contra World Records](https://www.youtube.com/watch?v=GgOE64kgjjo).
In the video starting at 35m 19s, there was a section where a speedrunner named
`dk28` died in the middle of level 1, and was advanced to level 2 for their next
life. The video explains that there still isn't a known explanation as to why
this happened. This got me interested in the assembly and thus this project
began and has since changed in scope as my interest have changed.
The _Contra_ (US) NES ROM was disassembled using a NES disassembly tool. This
provided a starting point for code investigation. The disassembler incorrectly
marked many lines of code as data and this had to be manually corrected. At
this point, there was an .asm file for each ROM bank in _Contra_.
Build scripts were then created for both windows console (.bat), and powershell
(.ps1). These scripts are equivalent and use ca65 to assemble the .asm files
and cl65 to link the .o files into a single ROM file. Creating these build
scripts required defining the memory addresses and bank layouts in the
`contra.cfg` file.
At this point, I had a repository that could be built that matched the NES rom
byte for byte. Then, I found and was able to incorporate the comments from
Trax's IDA Pro disassembly. This was incredibly helpful, but more work had to
be done as Trax's disassembly wasn't appropriate for disassembly. For instance,
it had a label for every line and all jump and branch statements were
hard-coded memory address offsets and not label references.
I then worked on updating all branch and jump offsets to point to label
addresses. This helped ensure that the code was more readable. At this point,
I also started updating data blocks that included memory offsets to use label
offsets. The goal here was twofold:
* make the code more readable and similar to what the original developers would
have written
* allow rom hacking without breaking the entire build due to breaking
hard-coded memory offsets
When all of this was done, the project could assemble and be byte-for-byte
exactly as the _Contra_ (US) ROM. However, this was just the prerequisite to
documenting the codebase. Every label had to be given an appropriate name, and
each line of assembly had to be documented.
# Build Details
The build scripts accomplish the following tasks:
* extracts necessary data from `baserom.nes` into `src/assets`
* assemble each bank .asm file into a .o file
* assemble constants.asm and ines_header into .o files
* link all output .o files into a single .nes rom file
The build scripts all utilize [cc65 compiler suite](https://cc65.github.io/) to
assemble and link the 6502 assembly files. The asset data is pulled from
`baserom.nes` as specified in `assets.txt`. Each line in `assets.txt` specifies
the file name of the asset, its offset into `baserom.nes`, and its length.
## 1. Assembly
Each .asm file is assembled into a .o object file. This section outlines what
is happening as part of that process.
### Import Export Validation
During assembly, if any symbol is undefined and not explicitly defined in an
`.import` directive, an error will be generated.
## 2. Linking
Linking is the concept of combining the various .o object files into a single
.nes rom file. This is done by the cl65 linker. Its job is to replace labels
with actual CPU memory addresses where the labels will exist when loaded. These
are then stored in the resulting contra.nes rom file. This is aided by the
linker configuration file contra.cfg.
This file specifies the layout of the .net rom file. Without this file, or with
a misconfigured file, the linker would not generate an identical .nes file.
There are two parts to the contra.cfg file: MEMORY layout and SEGMENTS layout.
### Contra.cfg MEMORY Layout
This section tells the linker where in memory each bank will exist. The linker
needs this to know to replace a label with the correct address. For example,
the opcodes that get generated for `jsr zero_out_nametables` depend on where in
memory the label `zero_out_nametables` will exist. This is what the
configuration file memory section specifies.
### Contra.cfg SEGMENTS Layout
This section specifies defines in which order the .o files should appear in the
resulting .nes rom file.
# References
* Trax's [Disassembly of _Contra (US)_](https://www.bwass.org/romhack/contra/)
* Trax's [documentation on romhacking.net](https://www.romhacking.net/documents/713/)
* [Tomorrow Corporation's Retro Game Internal Series on Contra](http://tomorrowcorporation.com/posts/retro-game-internals)
* [Overview of NES Rendering by Austin Morlan](https://austinmorlan.com/posts/nes_rendering_overview/)
* [Rom Detective article on Contra](http://www.romdetectives.com/Wiki/index.php?title=Contra_(NES))

347
assets.txt Normal file
View File

@ -0,0 +1,347 @@
src\assets\graphic_data\alt_graphic_data_00.bin 37474 1408
src\assets\graphic_data\alt_graphic_data_01.bin 38882 2976
src\assets\graphic_data\alt_graphic_data_02.bin 41858 928
src\assets\graphic_data\alt_graphic_data_03.bin 42786 1088
src\assets\graphic_data\alt_graphic_data_04.bin 43874 1184
src\assets\graphic_data\graphic_data_01.bin 76349 3724
src\assets\graphic_data\graphic_data_02.bin 37031 443
src\assets\graphic_data\graphic_data_03.bin 65553 1453
src\assets\graphic_data\graphic_data_04.bin 67006 499
src\assets\graphic_data\graphic_data_05.bin 81937 2656
src\assets\graphic_data\graphic_data_06.bin 72204 1543
src\assets\graphic_data\graphic_data_07.bin 84593 2431
src\assets\graphic_data\graphic_data_08.bin 67708 4449
src\assets\graphic_data\graphic_data_09.bin 72157 47
src\assets\graphic_data\graphic_data_0a.bin 73749 833
src\assets\graphic_data\graphic_data_0b.bin 87024 3899
src\assets\graphic_data\graphic_data_0c.bin 98321 3291
src\assets\graphic_data\graphic_data_0d.bin 101612 3834
src\assets\graphic_data\graphic_data_0e.bin 105446 5284
src\assets\graphic_data\graphic_data_0f.bin 74582 161
src\assets\graphic_data\graphic_data_11.bin 74743 1369
src\assets\graphic_data\graphic_data_12.bin 76112 237
src\assets\graphic_data\graphic_data_13.bin 67505 203
src\assets\graphic_data\graphic_data_14.bin 92196 1483
src\assets\graphic_data\graphic_data_15.bin 110730 226
src\assets\graphic_data\graphic_data_16.bin 110956 262
src\assets\graphic_data\graphic_data_17.bin 93679 1326
src\assets\graphic_data\graphic_data_18.bin 95005 81
src\assets\graphic_data\graphic_data_19.bin 90923 485
src\assets\graphic_data\graphic_data_1a.bin 91408 788
src\assets\audio_data\sound_02.bin 18962 8
src\assets\audio_data\sound_03.bin 18997 17
src\assets\audio_data\sound_04.bin 19014 11
src\assets\audio_data\sound_05.bin 19025 55
src\assets\audio_data\sound_06.bin 19080 10
src\assets\audio_data\sound_07.bin 19090 14
src\assets\audio_data\sound_08.bin 19104 8
src\assets\audio_data\sound_08_part_00.bin 19114 25
src\assets\audio_data\sound_09.bin 19601 57
src\assets\audio_data\sound_0a.bin 19139 41
src\assets\audio_data\sound_0b.bin 19180 16
src\assets\audio_data\sound_0b_part_00.bin 19198 23
src\assets\audio_data\sound_0c.bin 19221 14
src\assets\audio_data\sound_0c_part_00.bin 19237 29
src\assets\audio_data\sound_0d.bin 19266 14
src\assets\audio_data\sound_0d_part_00.bin 19282 23
src\assets\audio_data\sound_0e.bin 19305 55
src\assets\audio_data\sound_0f.bin 19360 99
src\assets\audio_data\sound_10.bin 19459 12
src\assets\audio_data\sound_10_part_00.bin 19473 6
src\assets\audio_data\sound_11.bin 19479 10
src\assets\audio_data\sound_11_part_00.bin 19491 6
src\assets\audio_data\sound_12.bin 19497 14
src\assets\audio_data\sound_13.bin 19511 90
src\assets\audio_data\sound_14.bin 19658 10
src\assets\audio_data\sound_14_part_00.bin 19670 14
src\assets\audio_data\sound_15.bin 19801 45
src\assets\audio_data\sound_16.bin 19684 35
src\assets\audio_data\sound_17.bin 19719 37
src\assets\audio_data\sound_18.bin 19756 13
src\assets\audio_data\sound_18_part_00.bin 19771 8
src\assets\audio_data\sound_19.bin 19779 22
src\assets\audio_data\sound_1a.bin 19949 82
src\assets\audio_data\sound_1b.bin 19846 5
src\assets\audio_data\sound_1b_part_00.bin 19860 42
src\assets\audio_data\sound_1b_part_02.bin 19910 39
src\assets\audio_data\sound_1c.bin 20149 8
src\assets\audio_data\sound_1c_part_00.bin 20159 23
src\assets\audio_data\sound_1d.bin 20182 6
src\assets\audio_data\sound_1d_part_00.bin 20190 21
src\assets\audio_data\sound_1e.bin 20031 24
src\assets\audio_data\sound_1f.bin 20055 23
src\assets\audio_data\sound_20.bin 20078 71
src\assets\audio_data\sound_21.bin 20211 13
src\assets\audio_data\sound_21_part_00.bin 20224 11
src\assets\audio_data\sound_21_part_02.bin 20250 11
src\assets\audio_data\sound_21_part_03.bin 20263 11
src\assets\audio_data\sound_21_part_04.bin 20276 11
src\assets\audio_data\sound_21_part_05.bin 20289 11
src\assets\audio_data\sound_21_part_06.bin 20302 11
src\assets\audio_data\sound_21_part_07.bin 20315 11
src\assets\audio_data\sound_21_part_08.bin 20328 12
src\assets\audio_data\sound_21_part_09.bin 20342 12
src\assets\audio_data\sound_21_part_0a.bin 20356 12
src\assets\audio_data\sound_23.bin 20371 2
src\assets\audio_data\sound_23_part_00.bin 20373 8
src\assets\audio_data\sound_23_part_01.bin 20383 6
src\assets\audio_data\sound_23_part_02.bin 20391 6
src\assets\audio_data\sound_23_part_03.bin 20399 6
src\assets\audio_data\sound_23_part_04.bin 20407 6
src\assets\audio_data\sound_23_part_05.bin 20415 6
src\assets\audio_data\sound_23_part_06.bin 20423 8
src\assets\audio_data\sound_24.bin 20434 8
src\assets\audio_data\sound_24_part_00.bin 20444 53
src\assets\audio_data\sound_25.bin 20497 8
src\assets\audio_data\sound_25_part_00.bin 20507 42
src\assets\audio_data\sound_25_part_01.bin 20549 7
src\assets\audio_data\sound_26.bin 20901 22
src\assets\audio_data\sound_27.bin 20923 24
src\assets\audio_data\sound_28.bin 20947 16
src\assets\audio_data\sound_29.bin 20963 10
src\assets\audio_data\sound_2a.bin 21560 29
src\assets\audio_data\sound_2a_part_00.bin 21589 21
src\assets\audio_data\sound_2a_part_01.bin 21612 361
src\assets\audio_data\sound_2b_part_00.bin 21115 26
src\assets\audio_data\sound_2b_part_01.bin 21143 415
src\assets\audio_data\sound_2c.bin 21975 24
src\assets\audio_data\sound_2c_part_00.bin 21999 39
src\assets\audio_data\sound_2c_part_01.bin 22040 180
src\assets\audio_data\sound_2c_part_02.bin 22220 5
src\assets\audio_data\sound_2c_part_03.bin 22227 6
src\assets\audio_data\sound_2c_part_04.bin 22235 168
src\assets\audio_data\sound_2d.bin 22405 7
src\assets\audio_data\sound_2d_part_00.bin 22414 4
src\assets\audio_data\sound_2d_part_01.bin 22418 34
src\assets\audio_data\sound_2d_part_02.bin 22454 176
src\assets\audio_data\sound_2d_part_03.bin 22630 18
src\assets\audio_data\sound_2d_part_04.bin 22650 13
src\assets\audio_data\sound_2d_part_05.bin 22663 17
src\assets\audio_data\sound_2d_part_06.bin 22682 16
src\assets\audio_data\sound_2d_part_07.bin 22698 17
src\assets\audio_data\sound_2d_part_08.bin 22717 12
src\assets\audio_data\sound_2d_part_09.bin 22729 3
src\assets\audio_data\sound_2d_part_0a.bin 22734 10
src\assets\audio_data\sound_2e.bin 22933 20
src\assets\audio_data\sound_2e_part_00.bin 22841 57
src\assets\audio_data\sound_2e_part_01.bin 22900 33
src\assets\audio_data\sound_2e_part_03.bin 22956 53
src\assets\audio_data\sound_2e_part_04.bin 23011 85
src\assets\audio_data\sound_2e_part_05.bin 23098 42
src\assets\audio_data\sound_2e_part_06.bin 23142 27
src\assets\audio_data\sound_2f.bin 23169 4
src\assets\audio_data\sound_2f_part_00.bin 23173 17
src\assets\audio_data\sound_2f_part_02.bin 23195 4
src\assets\audio_data\sound_2f_part_03.bin 23199 26
src\assets\audio_data\sound_2f_part_04.bin 23230 92
src\assets\audio_data\sound_2f_part_05.bin 23324 32
src\assets\audio_data\sound_2f_part_06.bin 23358 57
src\assets\audio_data\sound_30.bin 23415 3
src\assets\audio_data\sound_30_part_00.bin 23418 3
src\assets\audio_data\sound_30_part_02.bin 23426 28
src\assets\audio_data\sound_30_part_03.bin 23459 57
src\assets\audio_data\sound_31_part_01.bin 23524 3
src\assets\audio_data\sound_31_part_02.bin 23529 31
src\assets\audio_data\sound_31_part_03.bin 23562 71
src\assets\audio_data\sound_32.bin 23732 6
src\assets\audio_data\sound_32_part_00.bin 23681 33
src\assets\audio_data\sound_32_part_01.bin 23714 9
src\assets\audio_data\sound_32_part_02.bin 23725 7
src\assets\audio_data\sound_32_part_03.bin 23738 3
src\assets\audio_data\sound_32_part_05.bin 23744 17
src\assets\audio_data\sound_32_part_06.bin 23763 20
src\assets\audio_data\sound_32_part_07.bin 23783 34
src\assets\audio_data\sound_32_part_08.bin 23822 3
src\assets\audio_data\sound_32_part_09.bin 23827 3
src\assets\audio_data\sound_32_part_0a.bin 23832 42
src\assets\audio_data\sound_33.bin 23874 5
src\assets\audio_data\sound_33_part_00.bin 23879 3
src\assets\audio_data\sound_33_part_01.bin 23884 18
src\assets\audio_data\sound_33_part_02.bin 23904 21
src\assets\audio_data\sound_33_part_03.bin 23925 36
src\assets\audio_data\sound_33_part_04.bin 23966 4
src\assets\audio_data\sound_33_part_05.bin 23972 6
src\assets\audio_data\sound_34.bin 23978 3
src\assets\audio_data\sound_34_part_00.bin 23981 3
src\assets\audio_data\sound_34_part_01.bin 23986 16
src\assets\audio_data\sound_34_part_02.bin 24004 16
src\assets\audio_data\sound_34_part_03.bin 24022 30
src\assets\audio_data\sound_34_part_04.bin 24022 3
src\assets\audio_data\sound_34_part_05.bin 24057 3
src\assets\audio_data\sound_34_part_06.bin 24062 4
src\assets\audio_data\sound_34_part_07.bin 24068 3
src\assets\audio_data\sound_34_part_08.bin 24071 3
src\assets\audio_data\sound_34_part_09.bin 24076 4
src\assets\audio_data\sound_34_part_0a.bin 24086 11
src\assets\audio_data\sound_34_part_0b.bin 24099 11
src\assets\audio_data\sound_35_part_00.bin 24111 3
src\assets\audio_data\sound_35_part_02.bin 24122 11
src\assets\audio_data\sound_35_part_03.bin 24135 10
src\assets\audio_data\sound_35_part_04.bin 24145 11
src\assets\audio_data\sound_35_part_05.bin 24158 9
src\assets\audio_data\sound_35_part_06.bin 24167 11
src\assets\audio_data\sound_35_part_07.bin 24180 11
src\assets\audio_data\sound_36_part_00.bin 24255 54
src\assets\audio_data\sound_36_part_01.bin 24309 17
src\assets\audio_data\sound_36_part_02.bin 24328 19
src\assets\audio_data\sound_36_part_03.bin 24349 55
src\assets\audio_data\sound_37.bin 24406 6
src\assets\audio_data\sound_37_part_00.bin 24412 8
src\assets\audio_data\sound_37_part_01.bin 24420 20
src\assets\audio_data\sound_37_part_02.bin 24442 4
src\assets\audio_data\sound_37_part_03.bin 24446 9
src\assets\audio_data\sound_37_part_04.bin 24457 11
src\assets\audio_data\sound_37_part_05.bin 24470 48
src\assets\audio_data\sound_38.bin 24520 4
src\assets\audio_data\sound_38_part_00.bin 24524 6
src\assets\audio_data\sound_38_part_01.bin 24530 5
src\assets\audio_data\sound_38_part_02.bin 24537 3
src\assets\audio_data\sound_38_part_03.bin 24540 5
src\assets\audio_data\sound_38_part_04.bin 24547 5
src\assets\audio_data\sound_38_part_05.bin 24552 7
src\assets\audio_data\sound_38_part_06.bin 24561 10
src\assets\audio_data\sound_38_part_07.bin 24573 20
src\assets\audio_data\sound_39_part_00.bin 24596 3
src\assets\audio_data\sound_39_part_01.bin 24601 4
src\assets\audio_data\sound_39_part_02.bin 24605 12
src\assets\audio_data\sound_39_part_03.bin 24619 10
src\assets\audio_data\sound_39_part_04.bin 24629 12
src\assets\audio_data\sound_39_part_05.bin 24643 10
src\assets\audio_data\sound_39_part_06.bin 24653 12
src\assets\audio_data\sound_39_part_07.bin 24667 11
src\assets\audio_data\sound_39_part_08.bin 24678 5
src\assets\audio_data\sound_39_part_09.bin 24685 5
src\assets\audio_data\sound_3a_part_00.bin 24747 33
src\assets\audio_data\sound_3a_part_01.bin 24782 7
src\assets\audio_data\sound_3a_part_02.bin 24789 15
src\assets\audio_data\sound_3a_part_03.bin 24806 16
src\assets\audio_data\sound_3a_part_04.bin 24822 6
src\assets\audio_data\sound_3a_part_05.bin 24830 38
src\assets\audio_data\sound_3a_part_06.bin 24868 6
src\assets\audio_data\sound_3a_part_07.bin 24876 39
src\assets\audio_data\sound_3a_part_08.bin 24915 6
src\assets\audio_data\sound_3a_part_09.bin 24923 15
src\assets\audio_data\sound_3a_part_0a.bin 24938 6
src\assets\audio_data\sound_3a_part_0b.bin 24946 67
src\assets\audio_data\sound_3b.bin 25015 12
src\assets\audio_data\sound_3b_part_00.bin 25027 30
src\assets\audio_data\sound_3b_part_01.bin 25059 19
src\assets\audio_data\sound_3b_part_02.bin 25080 171
src\assets\audio_data\sound_3c.bin 25253 11
src\assets\audio_data\sound_3c_part_00.bin 25266 8
src\assets\audio_data\sound_3c_part_01.bin 25276 26
src\assets\audio_data\sound_3c_part_02.bin 25302 20
src\assets\audio_data\sound_3c_part_03.bin 25324 46
src\assets\audio_data\sound_3c_part_04.bin 25370 6
src\assets\audio_data\sound_3c_part_05.bin 25378 27
src\assets\audio_data\sound_3d_part_00.bin 25408 3
src\assets\audio_data\sound_3d_part_02.bin 25413 8
src\assets\audio_data\sound_3d_part_03.bin 25421 24
src\assets\audio_data\sound_3d_part_04.bin 25447 12
src\assets\audio_data\sound_3d_part_05.bin 25461 10
src\assets\audio_data\sound_3d_part_06.bin 25471 14
src\assets\audio_data\sound_3d_part_07.bin 25487 90
src\assets\audio_data\sound_3e.bin 25720 20
src\assets\audio_data\sound_3e_part_00.bin 25644 18
src\assets\audio_data\sound_3e_part_01.bin 25664 17
src\assets\audio_data\sound_3e_part_02.bin 25681 20
src\assets\audio_data\sound_3e_part_03.bin 25703 17
src\assets\audio_data\sound_3e_part_04.bin 25742 17
src\assets\audio_data\sound_3e_part_05.bin 25759 20
src\assets\audio_data\sound_3e_part_06.bin 25781 16
src\assets\audio_data\sound_3e_part_07.bin 25797 68
src\assets\audio_data\sound_3e_part_08.bin 25867 11
src\assets\audio_data\sound_3e_part_09.bin 25880 10
src\assets\audio_data\sound_3e_part_0a.bin 25899 21
src\assets\audio_data\sound_3e_part_0b.bin 25922 19
src\assets\audio_data\sound_3e_part_0c.bin 25941 21
src\assets\audio_data\sound_3e_part_0d.bin 25964 20
src\assets\audio_data\sound_3f.bin 25984 3
src\assets\audio_data\sound_3f_part_00.bin 25989 66
src\assets\audio_data\sound_3f_part_01.bin 26057 14
src\assets\audio_data\sound_3f_part_02.bin 26073 14
src\assets\audio_data\sound_3f_part_03.bin 26093 3
src\assets\audio_data\sound_3f_part_04.bin 26096 3
src\assets\audio_data\sound_3f_part_05.bin 26101 3
src\assets\audio_data\sound_40.bin 26107 10
src\assets\audio_data\sound_40_part_00.bin 26119 4
src\assets\audio_data\sound_40_part_01.bin 26125 49
src\assets\audio_data\sound_40_part_02.bin 26176 9
src\assets\audio_data\sound_40_part_03.bin 26187 10
src\assets\audio_data\sound_40_part_04.bin 26206 10
src\assets\audio_data\sound_40_part_05.bin 26218 9
src\assets\audio_data\sound_40_part_06.bin 26227 10
src\assets\audio_data\sound_40_part_07.bin 26239 11
src\assets\audio_data\sound_41.bin 26250 8
src\assets\audio_data\sound_41_part_00.bin 26260 7
src\assets\audio_data\sound_41_part_01.bin 26267 10
src\assets\audio_data\sound_41_part_02.bin 26279 9
src\assets\audio_data\sound_41_part_03.bin 26288 14
src\assets\audio_data\sound_41_part_04.bin 26304 16
src\assets\audio_data\sound_41_part_05.bin 26322 34
src\assets\audio_data\sound_41_part_06.bin 26356 6
src\assets\audio_data\sound_41_part_07.bin 26364 4
src\assets\audio_data\sound_41_part_08.bin 26368 6
src\assets\audio_data\sound_41_part_09.bin 26376 8
src\assets\audio_data\sound_42.bin 26531 91
src\assets\audio_data\sound_42_part_00.bin 26624 134
src\assets\audio_data\sound_43.bin 26760 5
src\assets\audio_data\sound_43_part_00.bin 26437 15
src\assets\audio_data\sound_43_part_01.bin 26454 17
src\assets\audio_data\sound_43_part_02.bin 26473 19
src\assets\audio_data\sound_43_part_03.bin 26494 37
src\assets\audio_data\sound_43_part_04.bin 26770 119
src\assets\audio_data\sound_44.bin 26891 135
src\assets\audio_data\sound_44_part_00.bin 27028 119
src\assets\audio_data\sound_45.bin 27166 27
src\assets\audio_data\sound_45_part_00.bin 27149 6
src\assets\audio_data\sound_45_part_01.bin 27155 8
src\assets\audio_data\sound_45_part_02.bin 27195 25
src\assets\audio_data\sound_45_part_03.bin 27231 14
src\assets\audio_data\sound_46.bin 27298 33
src\assets\audio_data\sound_47.bin 27331 33
src\assets\audio_data\sound_48.bin 27364 27
src\assets\audio_data\sound_49.bin 27391 21
src\assets\audio_data\sound_4a.bin 27823 2
src\assets\audio_data\sound_4a_part_00.bin 27644 179
src\assets\audio_data\sound_4a_part_01.bin 27827 4
src\assets\audio_data\sound_4a_part_02.bin 27833 4
src\assets\audio_data\sound_4a_part_03.bin 27837 109
src\assets\audio_data\sound_4b_part_00.bin 27949 3
src\assets\audio_data\sound_4b_part_01.bin 27954 3
src\assets\audio_data\sound_4b_part_02.bin 27957 15
src\assets\audio_data\sound_4b_part_03.bin 27974 35
src\assets\audio_data\sound_4b_part_04.bin 28009 14
src\assets\audio_data\sound_4b_part_05.bin 28025 15
src\assets\audio_data\sound_4b_part_06.bin 28042 35
src\assets\audio_data\sound_4b_part_07.bin 28077 14
src\assets\audio_data\sound_4b_part_08.bin 28093 88
src\assets\audio_data\sound_4c_part_00.bin 28184 14
src\assets\audio_data\sound_4c_part_01.bin 28200 6
src\assets\audio_data\sound_4c_part_02.bin 28206 12
src\assets\audio_data\sound_4c_part_03.bin 28220 10
src\assets\audio_data\sound_4c_part_04.bin 28230 11
src\assets\audio_data\sound_4c_part_05.bin 28243 9
src\assets\audio_data\sound_4c_part_06.bin 28252 11
src\assets\audio_data\sound_4c_part_07.bin 28265 10
src\assets\audio_data\sound_4c_part_08.bin 28275 11
src\assets\audio_data\sound_4c_part_09.bin 28288 9
src\assets\audio_data\sound_4c_part_0a.bin 28297 11
src\assets\audio_data\sound_4d_part_00.bin 28314 11
src\assets\audio_data\sound_4d_part_01.bin 28327 6
src\assets\audio_data\sound_4e.bin 27460 40
src\assets\audio_data\sound_4f.bin 27500 42
src\assets\audio_data\sound_50.bin 27542 44
src\assets\audio_data\sound_51.bin 27586 32
src\assets\audio_data\sound_52.bin 20775 83
src\assets\audio_data\sound_53.bin 20858 43
src\assets\audio_data\sound_54.bin 18970 26
src\assets\audio_data\sound_55.bin 20556 25
src\assets\audio_data\sound_56.bin 20581 8
src\assets\audio_data\sound_56_part_00.bin 20591 53
src\assets\audio_data\sound_57.bin 20644 4
src\assets\audio_data\sound_58.bin 20648 68
src\assets\audio_data\sound_59.bin 20716 8
src\assets\audio_data\sound_59_part_00.bin 20726 49
src\assets\audio_data\dpcm_sample_00.bin 130064 81
src\assets\audio_data\dpcm_sample_01.bin 130256 593

54
build.bat Normal file
View File

@ -0,0 +1,54 @@
@echo off
rem Assembles and links the source assemblies into a .nes ROM.
rem Run this script from the windows command prompt.
rem If desired, there is also a build.ps1 powershell script
rem that can be used as well.
IF EXIST contra.nes (
echo Deleting contra.nes.
del contra.nes
)
IF NOT EXIST "obj" (
mkdir "obj"
)
IF EXIST "obj\*.o" (
echo Deleting object files.
del "obj\*.o"
)
IF NOT EXIST base.nes (
echo No baserom.nes file found. If assets are missing, then the build will fail.
)
rem show commands run in output
echo Assembling PRG Rom Banks
rem loop through assets defined in assets.txt and extract bytes from baserom.nes
echo Extracting binary data from baserom.nes
for /f "tokens=1,2,3 delims= " %%i in (assets.txt) do (
cscript /nologo set_bytes.vbs %%j %%k %%i
)
@echo on
ca65 -g --debug-info -o obj\constants.o src\constants.asm
ca65 -g --debug-info -o obj\ines_header.o src\ines_header.asm
ca65 -g --debug-info -o obj\bank0.o src\bank0.asm
ca65 -g --debug-info -o obj\bank1.o src\bank1.asm
ca65 -g --debug-info -o obj\bank2.o src\bank2.asm
ca65 -g --debug-info -o obj\bank3.o src\bank3.asm
ca65 -g --debug-info -o obj\bank4.o src\bank4.asm
ca65 -g --debug-info -o obj\bank5.o src\bank5.asm
ca65 -g --debug-info -o obj\bank6.o src\bank6.asm
ca65 -g --debug-info -o obj\bank7.o src\bank7.asm
@echo off
rem link assemblies together to single .nes ROM
echo "Creating .nes ROM"
@echo on
ld65 -C contra.cfg --dbgfile contra.dbg .\obj\constants.o .\obj\ines_header.o .\obj\bank0.o .\obj\bank1.o .\obj\bank2.o .\obj\bank3.o .\obj\bank4.o .\obj\bank5.o .\obj\bank6.o .\obj\bank7.o -o contra.nes
@echo off

97
build.ps1 Normal file
View File

@ -0,0 +1,97 @@
# Assembles and links the source assemblies into a .nes ROM.
# Run this script from powershell if available, if not available use build.bat
# batch file from the windows command prompt.
$global:SOURCE_CONTRA = $null
$US_CONTRA_HASH = "1C747C78C678F14A68D4E5FCAE065298A103F833638775860F0E5C5FFAA061F62D45FD8942148B1507C1FD57FDE950A5D83F9F84A9782EC048A56067740C48E9"
<#
.SYNOPSIS
Copies bytes from the Contra US NES rom file into binary files for use when
assembling.
#>
function Set-Bytes {
param ($Skip, $Take, $Output)
IF (Test-Path -Path $Output) {
return
}
# only read baserom.nes once for speed improvements
IF ($global:SOURCE_CONTRA -eq $null) {
Write-Output " Reading input file baserom.nes."
$global:SOURCE_CONTRA = Get-Content .\baserom.nes -AsByteStream
}
Write-Output " Writing file $Output."
$global:SOURCE_CONTRA | Select-Object -Skip $Skip -First $Take | Set-Content $Output -AsByteStream
}
IF (Test-Path -Path "contra.nes") {
Write-Output "Deleting contra.nes."
Remove-Item -Path "contra.nes"
}
IF (-not (Test-Path -Path "obj")) {
New-Item -ItemType Directory -Path obj
}
IF (Test-Path -Path obj\*.o) {
Write-Output "Deleting object files."
Remove-Item -Path obj\*.o
}
IF (-not (Test-Path -Path "baserom.nes")) {
Write-Output "No baserom.nes file found. If assets are missing, then the build will fail."
} ELSE {
$SHA512_HASH = (Get-FileHash baserom.nes -Algorithm SHA512).Hash
IF ($SHA512_HASH -ne $US_CONTRA_HASH) {
Write-Warning "baserom.nes file integrity does NOT match expected result."
}
}
# loop through assets defined in assets.txt and extract bytes from baserom.nes
Write-Output "Extracting binary data from baserom.nes"
ForEach ($line in Get-Content -Path assets.txt) {
$tokens = -split $line
Set-Bytes -Skip $tokens[1] -Take $tokens[2] -Output $tokens[0]
}
# prevent write race condition
Start-Sleep -Milliseconds 100
Write-Output "Assembling PRG Rom Banks"
# show commands run in output
Set-PSDebug -Trace 1
ca65 -g --debug-info -o obj\constants.o src\constants.asm
ca65 -g --debug-info -o obj\ines_header.o src\ines_header.asm
ca65 -g --debug-info -o obj\bank0.o src\bank0.asm
ca65 -g --debug-info -o obj\bank1.o src\bank1.asm
ca65 -g --debug-info -o obj\bank2.o src\bank2.asm
ca65 -g --debug-info -o obj\bank3.o src\bank3.asm
ca65 -g --debug-info -o obj\bank4.o src\bank4.asm
ca65 -g --debug-info -o obj\bank5.o src\bank5.asm
ca65 -g --debug-info -o obj\bank6.o src\bank6.asm
ca65 -g --debug-info -o obj\bank7.o src\bank7.asm
Set-PSDebug -Trace 0
# link assemblies together to single .nes ROM
Write-Output "Creating .nes ROM"
Set-PSDebug -Trace 1
ld65 -C contra.cfg --dbgfile contra.dbg .\obj\constants.o .\obj\ines_header.o .\obj\bank0.o .\obj\bank1.o .\obj\bank2.o .\obj\bank3.o .\obj\bank4.o .\obj\bank5.o .\obj\bank6.o .\obj\bank7.o -o contra.nes
# compare assembled ROM hash to expected hash if file exists
Set-PSDebug -Trace 0
IF (Test-Path -Path "contra.nes") {
$SHA512_HASH = (Get-FileHash contra.nes -Algorithm SHA512).Hash
IF ($SHA512_HASH -eq $US_CONTRA_HASH) {
Write-Output "File integrity matches."
} ELSE {
Write-Warning "File integrity does NOT match."
}
}

88
build.sh Normal file
View File

@ -0,0 +1,88 @@
#!/bin/bash
# Assembles and links the source assemblies into a .nes ROM.
# Run this script from a bash terminal if on linux or mac.
# If you are on windows, use either build.ps1, or build.bat
us_contra_hash=1c747c78c678f14a68d4e5fcae065298a103f833638775860f0e5c5ffaa061f62d45fd8942148b1507c1fd57fde950a5d83f9f84a9782ec048a56067740c48e9
setBytes(){
if test -f $3
then
return
fi
echo " Writing file $3."
dd bs=1 skip=$1 count=$2 if=baserom.nes of=$3 status=none
}
if ! ld65 --version &> /dev/null
then
echo "cc65 compiler suite could not be found. Please install cc65 and add it to your path."
exit
fi
mkdir -p obj
if test -f "contra.nes"
then
echo "Deleting contra.nes."
rm contra.nes
fi
if test -f "obj/*.o"
then
echo "Deleting object files."
rm obj/*.o
fi
if ! test -f "baserom.nes"
then
echo "No baserom.nes file found. If assets are missing, then the build will fail."
else
ROM_HASH=$(sha512sum baserom.nes | awk '{print $1}')
if [[ "$ROM_HASH" != $us_contra_hash ]]
then
echo "baserom.nes file integrity does NOT match expected result."
fi
fi
# loop through assets defined in assets.txt and extract bytes from baserom.nes
echo "Extracting binary data from baserom.nes"
while read -r line || [ -n "$p" ]
do
set $line
file=$1
start=$2
length=$3
length=$(echo $length | tr -d '\r')
setBytes $start $length $file
done < assets.txt
echo "Assembling PRG Rom Banks"
ca65 -g --debug-info -o obj/constants.o src/constants.asm
ca65 -g --debug-info -o obj/ines_header.o src/ines_header.asm
ca65 -g --debug-info -o obj/bank0.o src/bank0.asm
ca65 -g --debug-info -o obj/bank1.o src/bank1.asm
ca65 -g --debug-info -o obj/bank2.o src/bank2.asm
ca65 -g --debug-info -o obj/bank3.o src/bank3.asm
ca65 -g --debug-info -o obj/bank4.o src/bank4.asm
ca65 -g --debug-info -o obj/bank5.o src/bank5.asm
ca65 -g --debug-info -o obj/bank6.o src/bank6.asm
ca65 -g --debug-info -o obj/bank7.o src/bank7.asm
echo "Creating .nes ROM"
ld65 -C contra.cfg --dbgfile contra.dbg ./obj/constants.o ./obj/ines_header.o ./obj/bank0.o ./obj/bank1.o ./obj/bank2.o ./obj/bank3.o ./obj/bank4.o ./obj/bank5.o ./obj/bank6.o ./obj/bank7.o -o contra.nes
if test -f "contra.nes"
then
# compare assembled ROM hash to expected hash
ROM_HASH=$(sha512sum contra.nes | awk '{print $1}')
if [[ "$ROM_HASH" == $us_contra_hash ]]
then
echo "File integrity matches."
else
echo "File integrity does NOT match."
fi
fi

37
contra.cfg Normal file
View File

@ -0,0 +1,37 @@
# defines where the banks will be loaded into CPU address space
MEMORY {
# INES Cartridge Header, not loaded into CPU memory
HEADER: start = $0000, size = $0010, fill = yes;
OAMRAM: file="", start = $0200, size = $0100, type = rw;
# UxROM (002) bank layout
BANK_0: start = $8000, size = $4000, type = ro, fill = yes, fillval = $FF;
BANK_1: start = $8000, size = $4000, type = ro, fill = yes, fillval = $FF;
BANK_2: start = $8000, size = $4000, type = ro, fill = yes, fillval = $FF;
BANK_3: start = $8000, size = $4000, type = ro, fill = yes, fillval = $FF;
BANK_4: start = $8000, size = $4000, type = ro, fill = yes, fillval = $FF;
BANK_5: start = $8000, size = $4000, type = ro, fill = yes, fillval = $FF;
BANK_6: start = $8000, size = $4000, type = ro, fill = yes, fillval = $FF;
BANK_7: start = $C000, size = $3FFA, type = ro, fill = yes, fillval = $FF;
# BANK_7 is actually still $4000 bytes large, but the hardware vectors (ROMV),
# which are on BANK7, are counted separately
# Hardware Vectors at End of 2nd 8K ROM (NES hard-coded location)
ROMV: start = $FFFA, size = $0006, type = ro, fill = yes;
}
# defines the order of the segments as they are stored in the .nes ROM file
SEGMENTS {
HEADER: load = HEADER, type = ro;
OAMRAM: load = OAMRAM, type = bss, define=yes, optional=yes;
BANK_0: load = BANK_0, type = ro;
BANK_1: load = BANK_1, type = ro;
BANK_2: load = BANK_2, type = ro;
BANK_3: load = BANK_3, type = ro;
BANK_4: load = BANK_4, type = ro;
BANK_5: load = BANK_5, type = ro;
BANK_6: load = BANK_6, type = ro;
BANK_7: load = BANK_7, type = ro;
VECTORS: load = ROMV, type = ro;
}

189
docs/Aim Documentation.md Normal file
View File

@ -0,0 +1,189 @@
# Aiming
16 enemies aim towards the player to target them. Some of those enemies, for
example a sniper (enemy type = #$06), will aim towards the closest player and
fire bullets. Others will track the player actively. For example, the white
blobs (enemy type = #$13) will follow the player for a specified amount of time.
Note that some enemies will fire, but they aren't aiming like the scuba soldier,
or even the regular soldier.
Some enemies can aim in more directions than others. For example, snipers
(enemy type = #$06) can aim in #$17 directions, whereas rotating guns (enemy
type = #$04) can only aim in #$0b directions. The number of directions that an
enemy can aim are determined by which table is used when aiming.
* `quadrant_aim_dir_00` is used for outdoor enemies and has #$03 aiming
directions per quadrant for a total of #$0b aiming directions.
* `quadrant_aim_dir_01` is used for all indoor/base enemies, tank, sniper, white
blob and spinning bubbles. The table has values #$06 aiming directions per
quadrant for a total of #$17 aiming directions.
* `quadrant_aim_dir_02` is only used by dragon arm when seeking (stage 3 boss)
and has #$0f aiming directions per quadrant for a total of #$3b aiming
directions.
Aiming direction values start at #$00, which represents 3 o'clock. Incrementing
the aiming directions moves the direction clockwise.
The main method to determine what value within a quadrant to aim is
`get_quadrant_aim_dir`. This method targets the location ($0b, $0a) from
position ($09, $08). This location is almost always the location of the closest
player to the enemy on the x-axis, which is determined by the method
`player_enemy_x_dist`. However, white blobs (enemy type = #$13) will target
players randomly based on the frame counter.
`get_quadrant_aim_dir` determines where the player is in relation to the enemy,
i.e. above or below, to the left or to the right. It will also calculate an
index into a single quadrant that most closely targets the player position based
on `quadrant_aim_dir_xx`. The quadrant aim direction is then converted to a
full aim direction according to the table below. Essentially, the quadrant code
is either added or subtracted from the x-axis aim direction depending on the
relative quadrant of the player in relation to the enemy.
| Quadrant | Math | Description |
|----------|-------------------------------------------------------|------------------------------------------------|
| I | new_aim_dir = max_aim_dir - new_aim_dir | result subtracted from 3 o'cock aim direction |
| II | new_aim_dir = max_aim_dir - mid_aim_dir + new_aim_dir | result added to 9 o'clock aim direction |
| III | new_aim_dir = mid_aim_dir - new_aim_dir | result subtracted from 9 o'clock aim direction |
| IV | new_aim_dir | no math |
Many enemies will use the full aim direction value to set their graphic to the
appropriate value. For example, the rotating gun will take the calculated full
aim direction from `aim_var_1_for_quadrant_aim_dir_00`, adjust it by adding or
subtracting, then setting the super-tile to the value as an offset into
`level_xx_nametable_update_supertile_data`.
## Bullets
When creating bullets, the value returned by `get_quadrant_aim_dir` is converted
into the full aim direction. Then the full aim direction is used by
`calc_bullet_velocities` as an offset into `bullet_fract_vel_dir_lookup_tbl`.
This value is then used as an offset into `bullet_fract_vel_tbl`. These values,
along with the bullet speed code and quadrant, are used to determine the bullet
x and y velocities.
For most enemy generated bullets, the method `aim_and_create_enemy_bullet` is
used. This method calls `get_quadrant_aim_dir` and `calc_bullet_velocities`.
However, some enemies instead call `get_quadrant_aim_dir_for_player` directly
and then call `calc_bullet_velocities` separately to set the bullet velocities.
| Enemy | Type | Calling Method |
|-------------------------------|------|-----------------------------------|
| Wall Turret | #$13 | `aim_and_create_enemy_bullet` |
| Core | #$14 | `aim_and_create_enemy_bullet` |
| Dragon Tentacle Orb | #$15 | `aim_and_create_enemy_bullet` |
| Jumping Soldier | #$16 | `aim_and_create_enemy_bullet` |
| Boss Eye Fire Ring Projectile | #$1b | `get_quadrant_aim_dir_for_player` |
| Gardegura | #$1d | `get_quadrant_aim_dir_for_player` |
| Rangel | #$1f | `aim_and_create_enemy_bullet` |
All of the enemies using this pattern to create bullets use
`quadrant_aim_dir_01`, which gives quadrant offsets between 0-6
inclusively. Since each quadrant has 6 values, there are 24 (#$18) possible
aim directions. Below is the table that is used to to determine the velocity
from the calculated full aim direction. Once the velocity is obtained from this
table, the quadrant is incorporated to adjust the signs (+/-) on the direction.
Finally, the velocity is adjusted again based on bullet speed code
(`adjust_bullet_velocity`).
| `bullet_fract_vel_dir_lookup_tbl` | `bullet_fract_vel_tbl` | x vel | y vel | degree off axis |
|-----------------------------------|------------------------|--------------|--------------|-----------------|
| #$00 | #$00 | #$ff (.9961) | #$00 | 0° |
| #$01 | #$02 | #$f7 (.9648) | #$42 (.2578) | 15° |
| #$02 | #$04 | #$dd (.8633) | #$80 (.5) | 30° |
| #$03 | #$06 | #$b5 (.7070) | #$b5 (.7070) | 45° |
| #$04 | #$08 | #$80 (.5) | #$dd (.8633) | 60° |
| #$05 | #$0a | #$42 (.2578) | #$f7 (.9648) | 75° |
| #$06 | #$0c | #$00 | #$ff (.9961) | 90° |
| #$07 | #$0a | #$42 (.2578) | #$f7 (.9648) | 75° |
| #$08 | #$08 | #$80 (.5) | #$dd (.8633) | 60° |
| #$09 | #$06 | #$b5 (.7070) | #$b5 (.7070) | 45° |
| #$0a | #$04 | #$dd (.8633) | #$80 (.5) | 30° |
| #$0b | #$02 | #$f7 (.9648) | #$42 (.2578) | 15° |
| #$0c | #$00 | #$00 | #$ff (.9961) | 90° |
| #$0d | #$02 | #$f7 (.9648) | #$42 (.2578) | 15° |
| #$0e | #$04 | #$dd (.8633) | #$80 (.5) | 30° |
| #$0f | #$06 | #$b5 (.7070) | #$b5 (.7070) | 45° |
| #$10 | #$08 | #$80 (.5) | #$dd (.8633) | 60° |
| #$11 | #$0a | #$42 (.2578) | #$f7 (.9648) | 75° |
| #$12 | #$0c | #$00 | #$ff (.9961) | 90° |
| #$13 | #$0a | #$42 (.2578) | #$f7 (.9648) | 75° |
| #$14 | #$08 | #$80 (.5) | #$dd (.8633) | 60° |
| #$15 | #$06 | #$b5 (.7070) | #$b5 (.7070) | 45° |
| #$16 | #$04 | #$dd (.8633) | #$80 (.5) | 30° |
| #$17 | #$02 | #$f7 (.9648) | #$42 (.2578) | 15° |
## Dragon seeking
Dragon Tentacle Orb (enemy type = #$15) is the only enemy type that uses
`quadrant_aim_dir_02`. It uses this during the 'arm seeking player' attack
pattern (attack pattern #$04). `quadrant_aim_dir_02` has the most precision
with #$0f aim directions per quadrant.
## Aim Call Flow
```mermaid
flowchart TD
get_rotate_dir_for_index --> get_quadrant_aim_dir
get_rotate_00 --> get_rotate_dir_for_index
get_rotate_01 --> get_rotate_dir_for_index
rotating_gun_routine_03:::enemy --> aim_var_1_for_quadrant_aim_dir_00
alien_fetus_routine_01:::enemy --> aim_var_1_for_quadrant_aim_dir_00
red_turret_routine_03:::enemy --> get_rotate_00
white_blob_routine_00:::enemy --> get_rotate_01
sniper_routine_02:::enemy --> get_rotate_01
aim_var_1_for_quadrant_aim_dir_00 --> get_rotate_00
aim_var_1_for_quadrant_aim_dir_01 --> get_rotate_01
spinning_bubbles_routine_01:::enemy --> aim_var_1_for_quadrant_aim_dir_01
tank_routine_02:::enemy --> aim_var_1_for_quadrant_aim_dir_01
white_blob_aim_to_player:::enemy --> aim_var_1_for_quadrant_aim_dir_01
spinning_bubbles_routine_00:::enemy --> get_quadrant_aim_dir_for_player
eye_projectile_routine_00:::enemy --> get_quadrant_aim_dir_for_player
dragon_arm_orb_seek_should_move:::enemy --> get_quadrant_aim_dir_for_player
dragon_arm_orb_fire_projectile:::enemy --> aim_and_create_enemy_bullet
aim_and_create_enemy_bullet --> get_quadrant_aim_dir_for_player
aim_and_create_enemy_bullet --> get_quadrant_aim_dir
get_quadrant_aim_dir_for_player --> get_quadrant_aim_dir
wall_turret_routine_03:::enemy --> aim_and_create_enemy_bullet
wall_core_routine_03:::enemy --> aim_and_create_enemy_bullet
jumping_soldier_routine_01:::enemy --> aim_and_create_enemy_bullet
red_soldier_routine_02:::enemy --> aim_and_create_enemy_bullet
get_rotate_dir_for_index --> get_quadrant_aim_dir_for_player
classDef enemy fill:#f96
```
## Pseudocode
Below is a c-like pseudo-code showing how the quadrant aim direction from
`get_quadrant_aim_dir` value is converted to a full aim direction in
`get_rotate_dir`.
```
int quadrant_aim_dir = get_quadrant_aim_dir();
// mid_aim_dir is 9 o'clock
// max_aim_dir is 3 o'clock
int mid_aim_dir, max_aim_dir, new_aim_dir;
if (quadrant_aim_dir_00 || quadrant_aim_dir_02) {
mid_aim_dir = 0x06;
max_aim_dir = 0x0c;
} else {
mid_aim_dir = 0x0c;
max_aim_dir = 0x18;
}
if(player_left_of_enemy) {
new_aim_dir = mid_aim_dir - quadrant_aim_dir;
}
if(player_above_enemy) {
if(quadrant_aim_dir != 0x00) {
new_aim_dir = max_aim_dir - quadrant_aim_dir;
} else {
new_aim_dir = 0x00;
}
}
// new full aim direction calculated
new_aim_dir = quadrant_aim_dir;
```

68
docs/Bugs.md Normal file
View File

@ -0,0 +1,68 @@
# Overview
This document outlines the bugs discovered in the code during the disassembly
process.
# 1. Missing Animation Entering Water
When animating the player falling into water, a comparison was missed which
causes `sprite_18` to not be used for the animation sequence. This bug also
exists in the Japanese version as well as _Probotector_. It's clear from the
code that there was an attempt to show `sprite_18` (frame 3 in the table below)
as part of the animation sequence. `sprite_18` is the same sprite as when the
player is in water and presses the d-pad down button.
| Frame 1 | Frame 2 | Frame 3 (missing) | Frame 4 |
|-------------------------------------|-------------------------------------|-------------------------------------|-------------------------------------|
| ![0](attachments/pw_0.png?raw=true) | ![1](attachments/pw_1.png?raw=true) | ![2](attachments/pw_2.png?raw=true) | ![3](attachments/pw_3.png?raw=true) |
```
@set_enter_water_sprite:
...
lda PLAYER_WATER_TIMER,x ; load water animation timer
beq ... ; if timer has elapsed, branch
cmp #$0c ; see if timer is greater than or equal to #$0c
bcs ... ; branch if timer >= #$0c (keep current sprite)
lda #$73 ; PLAYER_WATER_TIMER less than #$0c, update sprite
sta PLAYER_SPRITE_CODE,x ; set player sprite (sprite_73) water splash
cmp #$08 ; !(BUG?) always branch, doesn't compare to PLAYER_WATER_TIMER
; but instead compares against #$73
; no lda PLAYER_WATER_TIMER,x before this line, so part of splash animation is missing
bcs ... ; branch using sprite_73
lda #$18 ; dead code - a = #$18 (sprite_18) water splash/puddle
sta PLAYER_SPRITE_CODE,x ; dead code - set player animation frame to water splash/puddle
```
# 2. Accidental Sound Sample In Snow Field
After defeating the boss UFO (Guldaf), before calling the method
`level_boss_defeated`, the developers forgot to load into the accumulator the
appropriate sound code, usually #$57 for `sound_57`. So bytes in bank 7 are
interpreted as an audio sample incorrectly and a short DMC channel audio clip is
played.
`level_boss_defeated` uses whatever is in the accumulator as the sound code to
play. Since the accumulator wasn't set, it has the previous value of #$ff (see
`boss_ufo_routine_0b`). This is interpreted by the audio engine as a DMC sound
code that doesn't exist.
The audio engine will incorrectly read bytes from `sound_table_00` as the dmc
data below.
* sampling rate #$03 (5593.04 Hz), no loop
* counter length #$77
* offset #$97 --> $c000 + (#$97 * #$40) --> $e5c0
* sample length #$19
The data that is interpreted as a DPCM-encoded DMC sample is $e5c0, which is
in bank 7's `collision_box_codes_03` data. This bug does not exist in the
Japanese version of the game because the `level_boss_defeated` is not
responsible for playing the boss defeated sound. This bug does exist in
_Probotector_.
Extracted sound sample: [sound_ff.mp3](attachments/sound_ff.mp3?raw=true)
Note that the boss defeated audio (`sound_55`) is still played because the enemy
defeated routine is set to `boss_ufo_routine_09` (see
`enemy_destroyed_routine_05`). `boss_ufo_routine_09` plays `sound_55`.

253
docs/Contra Control Flow.md Normal file
View File

@ -0,0 +1,253 @@
# Game Loop
The control flow of _Contra_ is dictated by the CPU memory address
`GAME_ROUTINE_INDEX` ($18). You can think of the `game_routine_xx` methods like
canal locks. As the game progresses, the flow moves to the next
`game_routine`.
* `game_routine_00` and `game_routine_01` are for setting up the game intro
and player select UI.
* `game_routine_02` is for showing the demo.
* `game_routine_03` loads and auto-plays the demo level
* `game_routine_04` clears level memory and player-specific memory
* `game_routine_05` is where majority of level logic is executed
* `game_routine_06` runs at the end of the game after defeating the final boss
alien
## game_routine_00
This label is only run once to initialize the NES's graphics and CPU memory that
is used for the introduction animation and player select.
* Set nametable tiles all to #$00 - `zero_out_nametables` (ROM: $1c9a2, MEM:
Bank 7 $c9a2)
* Load intro pattern table, nametable, and palette (`load_intro_graphics`)
* Reset Komani code number of correct sequence inputs
(`KONAMI_CODE_NUM_CORRECT`) to back to #$00
* Initialize the horizontal scroll to #$01 for the sliding intro animation
* Initialize the high byte of the delay timer that is used for waiting before
showing the demo
## game_routine_01
This label is executed once per frame repeatedly while the _Contra_ logo is
scrolled across the screen (intro animation). This label also checks for the
Konami code is checked. Once the logo is finished scrolling from right to left,
the label will load the sprites needed to show Bill and Lance as well as the
player select UI and cursor. Then the intro theme music will be played.
Finally, this label waits for both timers to complete before either starting
the game (if the player has made a selection), or show a demo level.
* Check for Konami code (`konami_input_check`)
* Scroll intro graphic
* This is executed repeatedly until scrolling stops, the the logic below is
executed
* Load assets after scrolling is complete (`game_routine_01_scroll_complete`)
* Load player select menu
* Play intro explosion sound
* Load sprite for yellow falcon cursor and sprites for Bill and Lance
* Wait for timer to complete before moving on to `game_routine_02`
(`dec_theme_delay_check_user_input`)
* The timer resets if select is pressed
## game_routine_02
* Load demo level and plays the level
* Stop level when demo timer elapsed and loads next level to demo (only
levels 0-2)
* Reset `GAME_ROUTINE_INDEX` to #$0 between demo levels to reshow intro
scroll and player select
## game_routine_03
This label is executed once the player has pressed start to begin a game while
in `game_routine_02`. It simply ensures the intro theme is finished playing and
then flashes the player selection until all timers are elapsed
* Wait for intro theme to finish playing
* Flash "1 PLAYER" or "2 PLAYER" for a bit
## game_routine_04
This label clears level memory and player-specific memory like number of lives,
number of continues, as well as level header data.
* `init_score_player_lives` - clears memory addresses $0028 to $00f0 then
`CPU_SPRITE_BUFFER` ($300) up to but not including `CPU_GRAPHICS_BUFFER`
($700)
## game_routine_05
This is the where the majority of the game logic is executed from.
`game_routine_05` maintains its own separate set of routines for managing the
level state. See Level Routines. There are #$0a level routines.
## game_routine_06
This routine runs at the end of the game after defeating the final boss alien.
Begins the `game_end_routine_XX` routine execution flow and runs through each
routine
* `game_end_routine_00`
* `game_end_routine_01`
* `game_end_routine_02`
* `game_end_routine_03`
* `end_game_sequence_00`
* `end_game_sequence_01`
* `end_game_sequence_02`
* `game_end_routine_04`
* `game_end_routine_05`
# Level Routines
`game_routine_05` maintains a `LEVEL_ROUTINE_INDEX` ($2c). This is an offset
into the `level_routine_ptr_tbl` (ROM: $1ce35, MEM: Bank 7 $ce35). These
routines manage the state of the level as the player progresses. These routines
are not necessarily executed in order like a waterfall. There are #$0a level
routines in total.
These routines manage
* level initialization
* showing score before starting a level
* handle player input, including pause
* level completion, advancing level
* game over screen - continue / end
* game over with no more continues
* showing game ending sequence
Below is a mermaid diagram of the logic that dictates the state changes among
the level routines.
```mermaid
graph TD
level_routine_00 --> level_routine_01
level_routine_01 --> level_routine_02
level_routine_02 --> level_routine_03
level_routine_03 --> level_routine_04
level_routine_04 --> |Level Complete|level_routine_08
level_routine_04 --> |Game Over|level_routine_0a
level_routine_05 --> |Level Complete|level_routine_00
level_routine_05 --> |Complete Last Level/Game Over|level_routine_06
level_routine_05 --> |Game Over No Continues|level_routine_07
level_routine_06 --> |Continue/End Selected|level_routine_00
level_routine_08 --> |Game Over|level_routine_0a
level_routine_08 --> |Level Complete|level_routine_09
level_routine_0a --> |Game Over Timer Expired|level_routine_05
level_routine_09 --> |Level Complete|level_routine_05
```
## level_routine_00
This routine is responsible for loading the level header data into memory. This
specifies things like what type of level (indoor/base, outdoor), where graphics
data for the level are (super-tile pattern tiles, which super-tiles are on each
screen, and palette data). Then the routine initializes many PPU write
addresses, and loads the data which specifies the super-tiles on the first
screen. Note that the actual super-tile pattern tiles aren't loaded into memory
until `level_routine_03`.
* Load the level header data
* Load palette for screen that displays number of lives remaining and level/
stage name.
* Initialize `BOSS_DEFEATED_FLAG` and `LEVEL_END_PLAYERS_ALIVE` to #$00
* Call `init_ppu_write_screen_supertiles` to initialize PPU scroll offset,
PPU write offsets and call `load_current_supertiles_screen_indexes` to
decompress super-tiles indexes for level's screen to load into CPU memory at
`LEVEL_SCREEN_SUPERTILES`
* Loads bank 0 where the enemy routines are and initializes the memory address
$80 to point to where the enemy routines are for the current level
## level_routine_01
Responsible for displaying the number of lives remaining for players before the
level loads. This routine does not execute when demoing (in demo mode). It is
only executed once, whereas `level_routine_02` is executed repeatedly to flash
the score. The number of lives does not flash.
## level_routine_02
* Flash the score text until timer elapses
* Load the theme music for the level `level_vert_scroll_and_song` when not in
demo mode
* Sets the vertical scroll
## level_routine_03
Responsible for rendering the nametable super-tiles.
## level_routine_04
* Sees if the player is pausing or un-pausing by reading input.
* If the player is pausing, the pause sound is played and the game state is
updated
* If the player is paused, then the routine ends
* Check if BOSS_DEFEATED_FLAG is set, the the current level routine is
updated to `level_routine_08` and the current routine ends
* Check if player(s) have entered game over state, and if so, updates the
current level routine to `level_routine_0a` and the current routine ends
* Run all enemy logic
* Run soldier generation
* Update palette for flashing palettes, see palette cycling documentation in
`Graphics Documentation.md`
* Load alternate graphics if necessary
## level_routine_05
Handles when the the level is complete or ended due to game over. If it's not
the last level, then the sets things up so the next level is loaded. If it's the
last level, then configures things for the end of game sequences. During game
over, after GAME_OVER_DELAY_TIMER elapses, level routine #$0a sets the next
routine to be level_routine_05 so that the game over high score screen can be
shown
* Clear memory $40 to $f0, then $300 to $600 (exclusively)
* If game over
* Load game over screen pattern table and show game over screen
`show_game_over_screen`
* Increment `CURRENT_LEVEL`
* If the last level
* Start game ending sequence `inc_routine_index_set_timer`
* Increment `GAME_COMPLETION_COUNT` so next play through is more challenging
* Reset `LEVEL_ROUTINE_INDEX` back to level_routine_00
* If not the last level
* Loads graphics for current score screen and level intro screen in
`load_level_intro`
* Increments `LEVEL_ROUTINE_INDEX` to go to level_routine_06
`inc_routine_index_set_timer`
## level_routine_06
This level routine is the game over screen routine. It is executed after the
player has died. This routine shows the player scores, the high score. It also
shows the text "GAME OVER" and gives the player the option to either "CONTINUE"
or "END".
* Display score and "CONTINUE"/"END"
* Handle player input to change cursor between "CONTINUE" and "END" (select
button)
* Handle player input to select either "CONTINUE" and "END" (start button)
## level_routine_07
This routine is executed when there are no continues remaining. It shows the
score and the text "GAME OVER" while waiting for the player to press start. Once
the player presses start, the level routine is set to #$00
`level_routine_07` is executed after checking the number of remaining continues
in `level_routine_05` (`@no_continues_remaining`).
## level_routine_08
Boss destroy animation. Plays level end music
* Check if player(s) have entered game over state, and if so, updates the
current level routine to `level_routine_0a` and the current routine ends.
This seems unlikely as this check happens in `level_routine_04` as well.
This same code is part of `level_routine_07`, and seems equally unlikely to
happen for that level routine as well.
## level_routine_09
This routine runs the appropriate level routine from the
`end_level_sequence_ptr_tbl` table. After each sequence is finished executing,
control is sent back to `level_routine_05`.
* `end_level_sequence_00`
* Wait for the players to land if they were jumping
* Set a small delay before `end_level_sequence_01` does its logic
* `end_level_sequence_01`
* Wait for timer to elapse
* Run level-specific level routine ending animation from
`end_of_lvl_lvl_routine_ptr_tbl`
* `end_level_sequence_02`
## level_routine_0a
This is the last level routine, but is executed as part of a game over sequence.
After the player(s) get a game over, this is the routine that waits until
`GAME_OVER_DELAY_TIMER` has elapsed before setting the level routine to #$05
(`level_routine_05`) to reinitialize the level.
The `GAME_OVER_DELAY_TIMER` delay timer starts at #60 and is used to wait a bit
before showing the player scores.

1178
docs/Enemy Glossary.md Normal file

File diff suppressed because it is too large Load Diff

210
docs/Enemy Routines.md Normal file
View File

@ -0,0 +1,210 @@
# Overview
Enemy logic is controlled by enemy routines, every enemy has a set of routines
that it can execute. The list of routines per enemy is specified in the
`*_routine_ptr_tbl` tables all in bank 7. Which enemies are in which screen of
a level are specified in the `enemy_routine_ptr_tbl` (for shared enemies) or one
of the 7 `enemy_routine_level_x` tables. Level 2 and level 4 share the same
enemy routine `enemy_routine_level_2_4` table.
Upon execution of `level_routine_04` and `level_routine_0a`, every enemy is
given the opportunity to execute logic specific to that enemy type. This is
done by looping down #$f to #$0 through the `ENEMY_ROUTINE` memory addresses in
the `exe_all_enemy_routine` method. Enemies are added to the end going down,
i.e. they start at #$f and go down.
# Generation
Enemies can be generated in the level in one of two ways: random generation, and
level-specific hard-coded locations.
# Level Enemies
Each level defines hard-coded locations on each screen where an enemy will be
generated. These enemies are always at that location in the level. This is
defined in the `level_enemy_screen_ptr_ptr_tbl` in bank 2. There are #$08
2-byte entries in this table, one for each level, e.g.
`level_1_enemy_screen_ptr_tbl`. Each 2-byte entry is a memory address to
another table of addresses, e.g. `level_1_enemy_screen_02`. Each entry here
defines the enemies within a single screen of a level. Screen 0 does never has
enemies, so the first entry in this table is associated to the second screen of
the level. There is always one more entry for a level than there are screens.
## Data Structure
### Outdoor levels
For a given screen, each enemy is defined with at least #$03 bytes. For
example, the first enemy defined on `level_1_enemy_screen_00` is
`.byte $10,$05,$60`. These three bytes define a soldier who runs left, but
doesn't shoot. These bytes need to be broken up into bits to further understand
their meaning.
```
0001 0000 0000 0101 0110 0000
XXXX XXXX RRTT TTTT YYYY YAAA
```
* `X` - X offset
* `R` - Repeat
* `T` - Enemy Type
* `Y` - Y Offset
* `A` - Enemy Attribute
### Byte 1 - XX byte
The first byte, #$10, from the example above specifies the x position of the
enemy.
### Byte 2 - Repeat and Enemy Type
The second byte, #$05, from the example above defines two things: repeat, and
enemy type. The most significant 2 bits define the number of times to repeat
an enemy, the least significant 6 bits define the enemy type. To see a list of
all enemy types and what they are, see `Enemy Glossary.md`. For example, #$05
has a repeat of 0 and a enemy type of #$05. #$05 is the soldier.
If the repeat value is 0, then the enemy is not repeated and will take a total
of #$3 bytes. However, if there is a repeat, for each repetition, one more byte
is added and has the same structure as the `Y Offset and Attribute` byte. This
means an enemy with a repeated enemy will have the same XX position and the same
type, but have its own Y position and attributes.
Here is an example of a screen enemy definition with a repeat
```
level_1_enemy_screen_09:
.byte $10,$43,$40,$b4 ; flying capsule (enemy type #$03), attribute: 000 (R), location: (#$10, #$40)
; repeat: 1 [(y = #$b0, attr = 100)]
.byte $e0,$07,$81 ; red turret (enemy type #$07), attribute: 001, location: (#$e0, #$80)
.byte $ff
```
### Byte 3 - Y Offset and Attribute
The third byte, #$60, from the example above defines the vertical position of
the enemy as well as that enemy's attributes. The #$05 most significant bits
specify the vertical offset and the least significant 3 bits are for the
attributes. Each enemy can use the 3 attribute bits however they see fit. For
example, a soldier uses the attributes to know which way to start running, and
whether or not the soldier fires bullets from their gun. For a detailed list of
each enemy type and their attributes, see `Enemy Glossary.md`.
### Indoor/Base Levels
```
XXXX YYYY CDTT TTTT AAAA AAAA
```
* `X` - X offset
* `Y` - Y Offset
* `C` - Whether or not to add #$08 to Y position
* `D` - Whether or not to add #$08 to X position
* `T` - Enemy Type
* `A` - Enemy Attribute
# Enemy Destroyed
When an enemy is determined to be destroyed, e.g. their `ENEMY_HP` has gone to 0
after collision with a bullet, then the enemy routine for the active enemy is
immediately adjusted to a routine index specified by
`enemy_destroyed_routine_xx`. These are grouped in the
`enemy_destroyed_routine_ptr_tbl`.
For example, when a soldier is destroyed, `enemy_destroyed_routine_01` specifies
byte #$05 for the soldier. This corresponds to `soldier_routine_04`
# Soldier Generation
In addition to the hard-coded screen-specific enemies that appear in the same
location every play through (specified in the `level_x_enemy_screen_xx` data
structures). _Contra_ generates soldiers at regular intervals with slightly
random enemy logic so that each play through has a different experience.
When playing a level, the game state is in `game_routine_05`, and specifically
in `level_routine_04`. `level_routine_04` is run every frame. One part of
`level_routine_04` is to run the logic to determine if an enemy soldier (enemy
type #$05) should be created. This logic is in bank 2's
`exe_soldier_generation` method.
`exe_soldier_generation` runs one of three soldier generation routines depending
on the current value of `SOLDIER_GENERATION_ROUTINE`. Initially, this value is
#$00 and `soldier_generation_00` is executed.
## soldier_generation_00
`soldier_generation_00` initializes a level-specific timer that controls the
speed of soldier generation. This timer is subsequently adjusted based on the
number of times the game has been completed, and the player's current weapon
strength. Every time the game has been completed (max of 3 times), #$28 is
subtracted from the initial level-specific soldier generation timer.
Additionally, the player weapon strength multiplied by #$05 is subtracted from
the soldier generation timer.
For example, level 3 (waterfall) has an initial level-specific timer value of
#$d8 (specified in the `level_soldier_generation_timer` table). If the player
has beaten the game once and has a `PLAYER_WEAPON_STRENGTH` of #$03 (S weapon),
then the computed soldier generation timer would be #$a1.
```
#$a1 = #$d8 - (#$01 * #$28) - (#$05 * #$03)
```
Soldier generation is disabled on the indoor/base levels (level 2 and level 4)
along with level 8 (alien's lair). They are disabled by a value of #$00 being
specified in the `level_soldier_generation_timer` table. For these levels, no
other soldier generation routine will be run, only `soldier_generation_00`.
Once the soldier generation timer has been initialized and adjusted, the
`SOLDIER_GENERATION_ROUTINE` is incremented so that the next game loop's
`exe_soldier_generation` causes `soldier_generation_01` to execute.
## soldier_generation_01
`soldier_generation_01` is responsible for decrementing the soldier generation
timer until it elapses. Then it is responsible for creating the soldier, if
certain conditions are met. This includes randomizing the soldier's location and
enemy attributes.
`soldier_generation_01` will first look at the current soldier generation timer,
if the timer is not yet less than #$00, then the timer is decremented by #$02,
unless the frame is scrolling on an odd frame number. Then the timer is only
decremented by #$01.
Once the soldier generation timer has elapsed, the routine looks for an
appropriate location to generate the soldier on the screen. Soldiers are
always generated from the left or right edge of the screen. First the starting
horizontal position is determined. This is essentially determined randomly by
the current frame number and values in the `gen_soldier_initial_x_pos` table.
The result will be either the left edge (#$0a) or the right edge (#$fa or #$fc).
There is an exception for level one. Until a larger number of soldiers have
already been generated, soldiers will only appear from the right, probably to
make the beginning of the game slightly easier.
Once the x position is decided, the routine will start looking for a vertical
location that has a ground for the soldier to stand on. It does this in one of
3 ways randomly to ensure soldiers are generated from multiple locations if
possible. The 3 methods are from top of the screen to the bottom, from the
bottom of the screen to the top, and from the player vertical position up to the
top.
If a horizontal and vertical position is found where a soldier can be placed on
the ground, then some memory is updated to specify the location and the soldier
generation routine is incremented to `soldier_generation_02`.
## soldier_generation_02
At this point, a location is found for the soldier to generate.
`soldier_generation_02` is responsible for actually initializing and creating
the soldier. Some checks are performed to make sure it's appropriate to
generate a soldier, for example, when `ENEMY_ATTACK_FLAG` is set to #$00 (off),
then a soldier will not be generated. Other checks include that there are no
solid blocks (collision code #$80) right in front of the soldier to generate,
and that there is no player right next to the edge of the screen where the
soldier would be generated from (this check doesn't happen after beating the
game at least once). If any checks determine that the soldier should not be
generated, then the routine resets the `SOLDIER_GENERATION_ROUTINE` back to #$00
and stops.
To randomize the various behaviors of the generated soldiers, this routine will
look up initial behavior from one of the `soldier_level_attributes_xx` tables
based on the level. This will randomize the soldier direction, whether or not
the soldier will shoot and how frequently, and a value specifying the
probability of ultimately not generating the soldier. Finally, the soldier is
generated and the values are moved into the standard enemy memory location
addresses, creating the soldier.

View File

@ -0,0 +1,517 @@
# Overview
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.
## Terms
* 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](https://tomorrowcorporation.com/posts/retro-game-internals-contra-levels).
Some people may refer to this as a meta tile.
* Tile - an element of the nametable, represents an 8x8 pixel from the pattern
table
# Background
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](https://en.wikipedia.org/wiki/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.
## Graphics Data Locations
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 + 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 |
| `graphic_data_02` | 2 | $9097 | $9097 | Nametable 0 + Attribute table 0 | [$2000-$2400) | Used for intro screen |
| `graphic_data_03` | 4 | $8001 | $10001 | Left Pattern table | [$0000-$0680) | Used in every level. Contains Bill and Lance blocks, game over letters, lives medals, power-ups (SBFLRM), and explosions |
| `graphic_data_04` | 4 | $85ae | $105ae | Left Pattern table | [$0680-$08c0) | |
| `graphic_data_05` | 5 | $8001 | $14001 | Left and Right Pattern tables | [$09a0-$0a80), [$0dc0-$1200), [$1320-$1600), [$1bd0-$2000) | Character when immobile, and prone. Also has weapon zeppelin. writes to same PPU addresses as `graphic_data_07` and `graphic_data_0b` |
| `graphic_data_06` | 4 | $99fc | $119fc | Left and Right Pattern tables | [$08c0-$1100) | Most Base graphics |
| `graphic_data_07` | 5 | $8a61 | $14a61 | Left and Right Pattern tables | [$09a0-$0a80), [$0dc0-$1200), [$1320-$1600), [$1bd0-$2000) | Writes to same PPU addresses as `graphic_data_05` and `graphic_data_0b` |
| `graphic_data_08` | 4 | $886c | $1086c | Left and Right Pattern tables | [$09a0-$2000) | |
| `graphic_data_09` | 4 | $99cd | $119cd | Left Pattern table | [$0b00-$0b40) | |
| `graphic_data_0a` | 4 | $a005 | $12005 | Right Pattern table | [$1100-$1520) | |
| `graphic_data_0b` | 5 | $93e0 | $153e0 | Left and Right Pattern tables | [$09a0-$0a80), [$0dc0-$1200), [$1320-$1600), [$1bd0-$2000) | Writes to same PPU addresses as `graphic_data_05` and `graphic_data_07` |
| `graphic_data_0c` | 6 | $8001 | $18001 | Left and Right Pattern tables | [$09a0-$0a80), [$0dc0-$0ee0), [$0fc0-$1200), [$1320-$2000) | |
| `graphic_data_0d` | 6 | $8cdc | $18cdc | Left and Right Pattern tables | [$09a0-$0a80), [$0dc0-$0ee0), [$0fc0-$1200), [$1320-$2000) | |
| `graphic_data_0e` | 6 | $9bd6 | $19bd6 | Left and Right Pattern tables | [$09a0-$2000) | |
| `graphic_data_0f` | 4 | $a346 | $12346 | Right Pattern table | [$1520-$1600) | |
| `graphic_data_10` | 4 | $a003 | $12003 | Right Pattern table | [$1a20-$2000) | Same as `graphic_data_11`, but horizontally flipped |
| `graphic_data_11` | 4 | $a3e7 | $123e7 | Right Pattern table | [$1a20-$2000) | |
| `graphic_data_12` | 4 | $a940 | $12940 | Right Pattern table | [$1b90-$1ca0) | |
| `graphic_data_13` | 4 | $87a1 | $107a1 | Left Pattern table | [$08c0-$09a0) | |
| `graphic_data_14` | 5 | $a814 | $16814 | Right Pattern table | [$1600-$1bd0) | |
| `graphic_data_15` | 6 | $b07a | $1b07a | Left Pattern table | [$0ee0-$0fc0) | |
| `graphic_data_16` | 6 | $b15c | $1b15c | Right Pattern table | [$1200-$1320) | |
| `graphic_data_17` | 5 | $addf | $16ddf | Left and Right Pattern tables | [$0a60-$0fe0), [$15b0-$18a0) | |
| `graphic_data_18` | 5 | $b30d | $1730d | Nametable 0 + Attribute table 0 | [$2000-$2400) | Used in ending scene |
| `graphic_data_19` | 5 | $a31b | $1631b | Left Pattern table | [$0680-$08c0) | |
| `graphic_data_1a` | 5 | $a500 | $16500 | Left Pattern table | [$0a80-$0dc0) | |
## Background Collision Data
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 one of the 4
points in the super-tile. The first 8 bytes are for the top rows of collision
points for a single nametable row. The next 8 bytes are the middle collision
points. 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
entry, 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.
Note that this is similar in structure to the attribute table.
* Each byte contains data for a #$0f (16) nametable tiles
* Each 2 bits within the byte specifies data for 4 nametable table tiles (2x2
area).
However, the offsets within the structure refer to different areas of the
nametable. Unlike the attribute table, where each byte specifies a 4x4 area of
pattern table tiles, the background collision data byte specifies data for a 2x8
area of pattern table tiles. The table below shows how the background collision
data is arranged on the nametable.
```
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`.
## Super-Tiles
_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
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`.
## Sprite CPU Buffer
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.
## Sprite Number Encoding
The sprite data in bank 1 is encoded. Except when #$fe, the first byte
specifies the number of entries in the sprite. There 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](https://wiki.nesdev.org/w/index.php?title=PPU_OAM) 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
* 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 set to #$fc (-4) and their Y position set to #$f8
(-8).
* 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).
# Palette
The NES supports 4 palettes for the nametables (background) and 4 palettes for
sprites. A palette is a set of 3 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.
## Fade-In Effect
_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.
## Cycling
_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
# _Contra_ Compression
The graphic data for _Contra_ is largely compressed with a relatively basic
compression algorithm known as
[Run-length encoding](https://en.wikipedia.org/wiki/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. Essentially,
instead of
* #$00 #$00 #$00 #$00 #$00 #$00 #$00 #$00 #$00
It can be represented like
* #$89 #$00
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`).
## Pattern Table Decompression
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 3 command byte types
* #$ff - specifies the end of graphic data
* #$7f - tells the algorithm to change PPU write address to the next 2 bytes
specified.
* bit 7 set - any byte larger than #$7f, or alternatively, any byte that has
its most significant bit set to 1 is treated as a RLE command to write the
next byte of data multiple times to the PPU. This excludes #$ff, which
always means end of graphic data
Level section data not encoded exactly the same way
`level_1_supertiles_screen_ptr_table`
# Quick Reference
## Graphic Locations
* `level_graphic_data_tbl` (ROM: $1c8e3, MEM: Bank 7 $c8e3) specifies which
pattern table data is loaded for a level, indexes into `graphic_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.
## Important Labels
* `load_level_graphic_data` (ROM: $1c8c6, MEM: Bank 7 $c8c6) decompresses and
loads the "Graphic data" for the current level
* `load_current_supertiles_screen_indexes` (ROM: $1e169, MEM: Bank 7 $e169)
decompresses and loads the super-tile indexes into memory
* `load_level_supertile_data` (ROM: $1df48, MEM: Bank 7 $df48) decompresses
and loads the super-tiles from the pattern table
* `write_graphic_data_to_ppu` (ROM: $1c9a1, MEM: Bank 7 $c9a1) loads and
decompresses the entire graphic data specified by the A register as offset
into `graphic_data_ptr_tbl`
* `@flush_cpu_graphics_buffer` (ROM: $1cc3f, MEM: $cc3f) writes all the data in
the CPU graphics buffer to the PPU
* `draw_sprites` (ROM: $6e97, MEM: Bank 1 $ae97) populates $0200-$02ff with
sprite data for OAMDMA.

View File

@ -0,0 +1,178 @@
# How Contra Prints the High Score
## Overview
There are 3 scores stored in CPU memory for _Contra_: high score, player 1
score, and player 2 score. Each score is 2 bytes and stored in addresses
0x07e0-0x07e1, 0x07e2-0x07e3, and 0x07e4-0x07e5 respectively. These 2-byte
scores are little-endian, i.e. the least significant byte is the lower-addressed
byte. All scores have 00 appended when displayed, for example a score of 100
will show as 10000 on the screen. The default initial high score stored in
0x07e2 is 0xc8, or 200 in decimal. This will show as 20000. The exception to
this is a score of 0. 0 points will show as 0 and not 000.
The logic to display the score on the screen is non-trivial and worth properly
documenting. To display the score on the screen, the code needs to convert an
in-memory hexadecimal number to a decimal number.
At a high level, _Contra_ recursively calculates each decimal digit to display
one at a time from right to left. If the player's score is 123 in decimal, or
0111 1011 in binary. _Contra_ will call the same routine 3 times, each time
producing the next digit in the score from right to left: 3, then 2, then 1.
|Call # | Rest | Right Digit |
|-------|------|-------------|
| 1 | 12 | 3 |
| 2 | 1 | 2 |
| 3 | 0 | 1 |
As you can see, each row can be read as Score = (10 * Rest) + Right_Digit
The Rest column can be conceptually though of as 10 times larger than what is
stored in memory. So 12 really means 120, and 1 really means 10. This will make
conceptualizing the algorithm easier.
## Examples
This document's examples are simplified slightly in that these examples are
using 1-byte scores and not 2-byte scores like _Contra_. The source code shown
works against 2-byte scores.
### Example of Routine
As shown above, the routine is called a number of times, each time retrieving
the next digit of the score to display. Below is a step-by-step walk through of
calculating a single digit of the score to display.
Score (0x01): 0x7b 0111 1011 (123 in decimal)
Right Digit (0x02): 0x00 0000 0000 (0)
|Shift # | Rest | Right Digit | Input | Explanation |
|--------|-----------|-------------|----------|------------------------------|
| 1 | | 0 | 111 1011 | left shift |
| 2 | | 01 | 111 011 | left shift |
| 3 | | 011 | 11 011 | left shift |
| 4 | | 0111 | 1 011 | left shift |
| 5 | | 1111 | 011 | left shift |
| | | 0101 | 011 | subtract 10 from right digit |
| 6 | 1 | 1010 | 11 | shift left, mark 1 into Rest |
| | 1 | 0000 | 11 | subtract 10 |
| 7 | 11 | 0001 | 1 | shift left, mark 1 into Rest |
| 8 | 110 | 0011 | | shift left |
| 9 | 1100 | 0011 | | shift left Rest only |
Rest (0x01): 0000 1100 (12 decimal)
Right Digit (0x02): 0000 0011 (3 decimal)
The way _Contra_ determines the right-most digit recursively is very
interesting. Every time the algorithm shifts left, the input is passed through
the "Right Digit" column which will track when the single-digit is more than 10.
Every time the right digit's column is more than 10, 10 is subtracted. On the
subsequent left shift, 1 is placed on the right-most bit of the Rest column.
That 1 signifies that a subtraction of 10 was performed. Remember that the Rest
column's value is actually 10 times larger, e.g. 12 really means 120. So when
the 1 is carried to the "Rest" column, it is really a 10 being carried, which
matches what was subtracted from the "Right Digit" column.
### Memory-accurate Example With a 1-byte Score
While the previous example helps understand at a conceptual level, it hides some
of the technical ingenuity. _Contra_ doesn't split the score ("Input") into
"Right Digit" and "Rest". _Contra_ actually shifts the "Rest" back into the
"Input". This is useful because it allows the routine to be called
consecutively until there are no more digits to display.
Again, _Contra_ uses 2-byte scores, but if _Contra_ had 1-byte scores, this is
how the algorithm would work, while being memory accurate. 0x01 is shifted into
0x02. Right Digit (0x02) is always initialized to zero.
Score (0x01): 0x7b 0111 1011 (123)
Right Digit (0x02): 0x00 0000 0000 (0)
|Shift # | 0x01 | 0x02 | Explanation |
|--------|-------------|-------------|------------------------------|
| 1 | 1111 0110 | 0000 0000 | left shift 0x01 only |
| 2 | 1110 1100 | 0000 0001 | left shift 0x02, 0x01 |
| 3 | 1101 1000 | 0000 0011 | left shift 0x02, 0x01 |
| 4 | 1011 0000 | 0000 0111 | left shift 0x02, 0x01 |
| 5 | 0110 0000 | 0000 1111 | left shift 0x02, 0x01 |
| | 0110 0000 | 0000 0101 | subtract 10 |
| 6 | 1100 0001 | 0000 1010 | shift left, mark 1 into Rest |
| | 1100 0001 | 0000 0000 | subtract 10 |
| 7 | 1000 0011 | 0000 0001 | shift left, mark 1 into Rest |
| 8 | 0000 0110 | 0000 0011 | shift left 0x02, 0x01 |
| 9 | 0000 1100 | 0000 0011 | shift left 0x01 only |
Rest (0x01): 0000 1100 (12 decimal)
Right Digit (0x02): 0000 0011 (3 decimal)
You can see that after this routine is called, it can be called again
immediately to determine the next right-most digit since the score address
(0x01) is overwritten with the new score. 0x02 is never more than 4-bits, and no
additional memory locations are needed. I believe this is the reason this
algorithm was used over other algorithms that can convert from hexadecimal to
decimal with a fixed number of steps.
## _Contra_'s Score Display Algorithm in Assembly from Source
The code for converting the scores for display is in the last (8th) PRG ROM
bank, bank 7. The logic begins at address $caf8 in CPU memory. _Contra_ will
recursively determine the value of the "Right Digit" place, which is then
displayed on the screen to the player.
CPU memory addresses used
* 0x00 - low byte of the current score being calculated
* 0x01 - high byte of the current score being calculated
* 0x02 - the next decimal digit to display
* 0x03 - hard-coded 10 in decimal
```
calculate_score_digit:
lda #$00 ; set the accumulator register A to zero (#$00)
sta $02 ; zero out any previously calculated digit
ldy #$10 ; set the left-shift loop counter back to #$10 (16 decimal)
rol $00 ; shift the score low byte to the left by one bit
; push the most significant bit (msb) to the carry flag
rol $01 ; shift the score high byte to the left by one byte
; push the msb to the carry flag
; pull in carry flag to least significant bit (lsb)
shift_and_check_digit_carry:
rol $02 ; shift score high byte to the left by one bit
; if the msb of the score high byte was 1, then carry into lsb
lda $02 ; load current digit into the accumulator register A
cmp $03 ; compare #$0a (10 decimal) to the current digit
; branch if current digit is less than #$0a (10 decimal)
; - this means no subtraction and carry is needed
; if greater than #$0a (10 decimal), don't jump
; - subtract 10 and carry
bcc continue_shift_score
sbc $03 ; the current digit is greater than 10, subtract 10
; this also sets the carry flag, which will be moved to the
; low byte of the score, which is the "Rest" of the number
; this carry represents adding 10 to the "Rest"
sta $02 ; store the difference (new current digit) back in $02
; $02 (current digit) is less than #$0a, or has just been subtracted
; continue algorithm by shifting score left
continue_shift_score:
rol $00 ; if just set $02 to digit by subtraction, this will put 1
; in $00's lsb, signifying adding 10 to "Rest"
rol $01 ; if $00's msb is 1, then it'll carry to the lsb of $01
dey ; Y goes from $10 to $00, once Y is $00, the algorithm is done
bne shift_and_check_digit_carry
rts
```
### See Also
Converting a hexadecimal number to decimal can be done in multiple ways. A
common algorithm for doing this is
[double dabble algorithm](https://en.wikipedia.org/wiki/Double_dabble). _Contra_
does not use this method. This is probably because the double dabble algorithm
requires more CPU memory space, whereas _Contra_'s algorithm uses the same,
fixed 2 bytes of CPU memory (0x01 and 0x02) for all score calculation.
Consequently, instead of a fixed number of operations like double dabble,
_Contra_ uses recursion, which requires a variable number of CPU cycle at the
benefit of fixed memory usage.
Another interesting and related concept is the
[binary-coded decimal](https://en.wikipedia.org/wiki/Binary-coded_decimal).

178
docs/Level Headers.md Normal file
View File

@ -0,0 +1,178 @@
# Overview
_Contra_ loads various level-specific configuration data from in bank 2. These
are called "level headers". These headers are #$20 (32) bytes for each level
and contain information about the level, for example whether the level is and
indoor level, or an outdoor level, vertical level or horizontal level, etc.
In the code, the level headers are labeled `level_headers` and each level is
labeled with an appropriate `level_x_header` label.
# Byte Offset 0 - Location Type
* Offset: 0
* Memory Location: $40
* Variable Name: `LEVEL_LOCATION_TYPE`
The first byte is the level's "location type". There are 3 location types
* Outdoor - #$00
* Indoor/Base - #$01
* Indoor Boss - #$80
Outdoor levels are typical platformer side-scrolling levels and scrolls
horizontally or vertically. Indoor levels are also known as base levels and are
the levels where Bill and Lance are facing away from the player in a pseudo 3D
experience (Levels 2 and 4).
Although the value is never #$80 in the level headers table, for the boss fights
on the indoor levels (2 and 4), the location type will be set to $80. It is
similar to the indoor location type (#$01), except the player cannot go prone
and shooting up looks like shooting forward (z-plane). When animating the
players walking into the screen between 3D screens, the value is also set to
#$80.
# Byte Offset 1 - Outdoor Scrolling Type
* Offset: 1
* Memory Location: $41
* Variable Name: `LEVEL_SCROLLING_TYPE`
The second byte is the level's scrolling type. It is only used in outdoor
levels.
* Horizontal - #$00
* Vertical - #$01
# Byte Offsets 2 and 3 - Screen Super-Tile Pointer
* Offset: 2 and 3
* Memory Location: 2-byte address $42 and $43
* Variable Name: `LEVEL_SCREEN_SUPERTILES_PTR`
The 3rd and 4th bytes make up the pointer that point to the data that specifies
which super-tiles are on each screen. This is an address into Bank 2. The
address is 2 bytes and stored in little-endian, i.e. the least significant byte
first.
For example, on level 3, the screen super tile pointer points to
`level_3_supertiles_screen_ptr_table` (ROM: $84ce, MEM: Bank 2 $84ce). This
address contains a list of pointers for each "screen" of the level. A screen is
a set of super-tiles for a level that is 256 pixels wide. Since level 3 is a
vertical level, each screen is 64 super-tiles (8x8). Looking at the data
specified at the address of a specific screen pointer, e.g.
`level_3_supertiles_screen_01` (ROM: $8635, MEM: Bank 2 $8635), you will find an
RLE-encoded set of bytes that specify the super-tile indexes used. After
decompressing, each byte is the offset into the
`level_3_supertile_data` (ROM: $cef8, MEM: Bank 3 $8ef8).
# Byte Offsets 4 and 5 - Super-Tile Data
* Offset: 4 and 5
* Memory Location: 2-byte address $44 and $45
* Variable Name: `LEVEL_SUPERTILE_DATA_PTR`
The 5th and 6th bytes of the level headers are a pointer to the level's
super-tiles. Each entry in table at the specified address is #$10 bytes long
and is a single super-tile. This data is not RLE-encoded. Each byte in each
super-tile represent a pattern table tile. An example pointer is
`level_3_supertile_data` (ROM: $cef8, MEM: Bank 3 $8ef8)
# Byte Offsets 6 and 7 - Super-Tile Palette Data
* Offset: 6 and 7
* Memory Location: 2-byte address $46 and $47
* Variable Name: `LEVEL_SUPERTILE_PALETTE_DATA`
Address to byte table that specifies the palette used for each super-tile in the
level. Each byte describes the 4 palettes for a single super-tile. An example
is `level_2_palette_data`
# Byte Offset 8 - Alternate Graphics Loading Location
* Offset: 8
* Location: $48
* Variable Name: `LEVEL_ALT_GRAPHICS_POS`
This byte specifies the alternate graphics loading location. This is the screen
where the game has the ability to change out the pattern table tiles for a
level.
# Byte Offsets 9, 10, and 11 - Tile Collision Limits
* Offset: 9, 10, and 11
* Location: $49, $4a, $4b
* Variable Name: `COLLISION_CODE_1_TILE_INDEX`, `COLLISION_CODE_0_TILE_INDEX`,
and `COLLISION_CODE_2_TILE_INDEX` (in that order)
These three bytes are used to specify which pattern table tiles are which
collision codes.
* Pattern table tiles below the value in $49 (excluding #$00) are considered
Collision Code 1 (floor).
* Pattern table tiles >= the value in $49, but less than the value in $4a are
considered Collision Code 0 (empty)
* Pattern table tiles >= $4a but less than this tile index are considered
Collision Code 2 (water)
Pattern table tiles above the last entry, are considered Collision Code 3
(Solid).
# Byte Offset 12, 13, 14, and 15 - Cycling Palette Colors
* Offset: 12, 13, 14, and 15
* Location: $4c, $4d, $4e, and $4f
* Variable Name: `LEVEL_PALETTE_CYCLE_INDEXES`
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. This header section
contains those specific indexes to cycle through.
For more details see `Graphics Documentation.md`. There is a section titled
`Palette -> Cycling`.
# Byte Offset 16, 17, 18, and 19 - Nametable Palette Initial Colors
* Offset: 16, 17, 18, and 19
* Location: $50, $51, $52, and $53
* Variable Name: `LEVEL_PALETTE_INDEX`
Initial background tile palette colors for the level. Indexes into
`game_palettes`.
# Byte Offset 20, 21, 22, and 23 - Nametable Palette Initial Colors
* Offset: 20, 21, 22, and 23
* Location: $54, $55, $56, and $57
* Variable Name: `LEVEL_PALETTE_INDEX`
Initial sprite tile palette colors for the level. Indexes into `game_palettes`.
# Byte Offset 24 - Scroll Stop Screen
* Offset: 24
* Location: $58
* Variable Name: `LEVEL_STOP_SCROLL`
The screen of the level to stop scrolling. The value in memory is set to #$ff
when boss auto scroll starts
# Byte Offset 25 - Solid Background Collision
* Offset: 25
* Location: $59
* Variable Name: `LEVEL_SOLID_BG_COLLISION_CHECK`
Specifies whether to check for bullet and weapon item solid bg collisions.
1. When non-zero, specifies that the weapon item should check for solid bg
collisions (see `weapon_item_check_bg_collision`)
2. When negative (bit 8 set), specifies to let bullets for both players and
enemies to check for bullet-solid background collisions. This is used on
levels 6 - energy zone and 7 - hangar. (see
`check_bullet_solid_bg_collision` and `enemy_bullet_routine_01`)
# Byte Offset 26 through 32 - Unused
The last 6 bytes in each level's level header are unused and set to #$00.

306
docs/Sound Documentation.md Normal file
View File

@ -0,0 +1,306 @@
# Overview
A song or sound can be composed of multiple instruments or channels. The NES
has 5 channels: 2 pulse (square wave) channels, 1 triangle channel, 1 noise
channel, and 1 delta modulation channel. _Contra_ maintains 6 slots of data in
memory that are used in priority order to play sounds. Higher slots are played
before the lower slots. Each slot is linked to an NES sound channel.
* #$00 = pulse 1 channel
* #$01 = pulse 2 channel
* #$02 = triangle channel
* #$03 = noise and dmc channel
* #$04 = pulse 1
* #$05 = noise channel
For example, if a sound is loaded in slot #$00 and slot #$04 (both pulse 1
channel), then the sound in slot #$04 will be played since it is higher. The
code that converts from sound slot to channel is `@load_sound_channel_offset`.
When the game loads a sound to play, it first determines which slots are needed
by loading data from `sound_table_00`. This table specifies the number of slots
needed to play the sound and where the instructions to play the sound exists,
i.e. the 2 byte cpu address where the sound channel instructions are.
# Sound Codes
Below is a table of all the sounds that exist in _Contra_, including unused
sounds. The Japanese names were obtained from the "sound mode" feature in the
Famicom version of the game. Each sound is related to one or more sound codes.
For example, the level 3 waterfall music uses 4 sound codes, which means that
sound uses 4 sound channels.
At the bottom of the table are the DPCM samples that are used throughout the
game by various other sounds.
| Sound | Japanese Name | Description | sound_code(s) | Slot | Command Type | Channel |
|-------|---------------|------------------------------------------------------|---------------|------|--------------|------------------|
| #$01 | | empty/silence, used to initialize channel | `sound_01` | | | |
| #$02 | | percussive tick (bass drum/tom drum) | `sound_02` | 5 | low | noise |
| #$03 | FOOT | player landing on ground or water | `sound_03` | 4 | low | pulse 1 |
| | | | `sound_04` | 5 | low | noise |
| #$05 | ROCK | waterfall rock landing on ground | `sound_05` | 4 | low | pulse 1 |
| #$06 | TYPE 1 | unused, keyboard typing in Japanese version of game | `sound_06` | 4 | low | pulse 1 |
| | | | `sound_07` | 5 | low | noise |
| #$08 | | unused, rumbling | `sound_08` | 5 | low | noise |
| #$09 | FIRE | energy zone fire beam | `sound_09` | 5 | low | noise |
| #$0a | SHOTGUN1 | default weapon | `sound_0a` | 4 | low | pulse 1 |
| | | | `sound_0b` | 5 | low | noise |
| #$0c | SHOTGUN2 | M weapon, turret man | `sound_0c` | 4 | low | pulse 1 |
| | | | `sound_0d` | 5 | low | noise |
| #$0e | LASER | L weapon | `sound_0e` | 4 | low | pulse 1 |
| | | | `sound_0f` | 5 | low | noise |
| #$10 | PL FIRE | F weapon | `sound_10` | 4 | low | pulse 1 |
| | | | `sound_11` | 5 | low | noise |
| #$12 | SPREAD | S weapon | `sound_12` | 4 | low | pulse 1 |
| | | | `sound_13` | 5 | low | noise |
| #$14 | HIBIWARE | bullet shielded wall plating ting | `sound_14` | 4 | low | pulse 1 |
| #$15 | CHAKUCHI | energy zone boss landing | `sound_15` | 4 | low | pulse 1 |
| #$16 | DAMEGE 1 | bullet to metal collision ting | `sound_16` | 4 | low | pulse 1 |
| | | | `sound_17` | 5 | low | noise |
| #$18 | DAMEGE 2 | alien heart boss hit | `sound_18` | 4 | low | pulse 1 |
| #$19 | TEKI OUT | enemy destroyed | `sound_19` | 4 | low | pulse 1 |
| #$1a | HIRAI 1 | ice grenade whistling noise | `sound_1a` | 4 | low | pulse 1 |
| #$1b | SENSOR | level 1 jungle boss siren | `sound_1b` | 4 | low | pulse 1 |
| #$1c | KANDEN | electrocution sound | `sound_1c` | 4 | low | pulse 1 |
| | | | `sound_1d` | 5 | low | noise |
| #$1e | CAR | tank advancing | `sound_1e` | 4 | low | noise |
| #$1f | POWER UP | pick up weapon item | `sound_1f` | 4 | low | pulse 1 |
| #$20 | 1UP | extra life | `sound_20` | 4 | low | pulse 1 |
| #$21 | HERI | helicopter rotors | `sound_21` | 4 | low | pulse 1 |
| | | | `sound_21` | 1 | low | pulse 2 |
| | | | `sound_23` | 5 | low | noise |
| #$24 | BAKUHA 1 | explosion | `sound_24` | 5 | low | noise |
| #$25 | BAKUHA 2 | game intro, indoor wall, and island explosion | `sound_25` | 5 | low | noise |
| #$26 | TITLE | game intro tune | `sound_26` | 0 | high | pulse 1 |
| | | | `sound_27` | 1 | high | pulse 2 |
| | | | `sound_28` | 2 | high | triangle |
| | | | `sound_29` | 3 | high | noise |
| #$2a | BGM 1 | level 1 jungle and level 7 hangar music | `sound_2a` | 0 | high | pulse 1 |
| | | | `sound_2b` | 1 | high | pulse 2 |
| | | | `sound_2c` | 2 | high | triangle |
| | | | `sound_2d` | 3 | high | noise |
| #$2e | BGM 2 | level 3 waterfall music | `sound_2e` | 0 | high | pulse 1 |
| | | | `sound_2f` | 1 | high | pulse 2 |
| | | | `sound_30` | 2 | high | triangle |
| | | | `sound_31` | 3 | high | noise |
| #$32 | BGM 3 | level 5 snow field music | `sound_32` | 0 | high | pulse 1 |
| | | | `sound_33` | 1 | high | pulse 2 |
| | | | `sound_34` | 2 | high | triangle |
| | | | `sound_35` | 3 | high | noise |
| #$36 | BGM 4 | level 6 energy zone | `sound_36` | 0 | high | pulse 1 |
| | | | `sound_37` | 1 | high | pulse 2 |
| | | | `sound_38` | 2 | high | triangle |
| | | | `sound_39` | 3 | high | noise |
| #$3a | BGM 5 | level 8 alien's lair music | `sound_3a` | 0 | high | pulse 1 |
| | | | `sound_3b` | 1 | high | pulse 2 |
| | | | `sound_3c` | 2 | high | triangle |
| | | | `sound_3d` | 3 | high | noise |
| #$3e | 3D BGM | indoor/base level music | `sound_3e` | 0 | high | pulse 1 |
| | | | `sound_3f` | 1 | high | pulse 2 |
| | | | `sound_40` | 2 | high | triangle |
| | | | `sound_41` | 3 | high | noise |
| #$42 | BOSS | indoor/base boss screen music | `sound_42` | 0 | high | pulse 1 |
| | | | `sound_43` | 1 | high | pulse 2 |
| | | | `sound_44` | 2 | high | triangle |
| | | | `sound_45` | 3 | high | noise |
| #$46 | PCLR | end of level tune | `sound_46` | 0 | high | pulse 1 |
| | | | `sound_47` | 1 | high | pulse 2 |
| | | | `sound_48` | 2 | high | triangle |
| | | | `sound_49` | 3 | high | noise |
| #$4a | ENDING | end credits | `sound_4a` | 0 | high | pulse 1 |
| | | | `sound_4b` | 1 | high | pulse 2 |
| | | | `sound_4c` | 2 | high | triangle |
| | | | `sound_4d` | 3 | high | noise |
| #$4e | OVER | game over/after end credits, presented by Konami | `sound_4e` | 0 | high | pulse 1 |
| | | | `sound_4f` | 1 | high | pulse 2 |
| | | | `sound_50` | 2 | high | triangle |
| | | | `sound_51` | 3 | high | noise |
| #$52 | PL OUT | player death | `sound_52` | 4 | low | pulse 1 |
| | | | `sound_53` | 5 | low | noise |
| #$54 | | game pausing | `sound_54` | 4 | low | pulse 1 |
| #$55 | BOSS BK | tank, boss ufo, boss giant, alien guardian destroyed | `sound_55` | 4 | low | pulse 1 |
| | | | `sound_56` | 5 | low | noise |
| #$57 | BOSS OUT | boss destroyed | `sound_57` | 4 | low | pulse 1 |
| | | | `sound_58` | 1 | low | pulse 2 |
| | | | `sound_59` | 5 | low | noise |
| #$5a | n/a | high hat | n/a | n/a | low | delta modulation |
| #$5b | n/a | snare | n/a | n/a | low | delta modulation |
| #$5c | n/a | high hat | n/a | n/a | low | delta modulation |
| #$ff | n/a | snowfield boss defeated door open (bug) | n/a | n/a | low | delta modulation |
The sound for pausing the game is not in the sound mode menu, presumably to not
confuse players into thinking the game is paused. As for names, I can guess at
some of the abbreviations and name meanings.
* BAKUHA - ばくはつ (爆発) - Japanese for explosion
* BGM - background music
* BK - BAKUHA, i.e. explosion
* CHAKUCHI - ちゃくち (着地) - Japanese for landing/touching the ground
* HIBIWARE - ひびわれ (罅割れ, ひび割れ) - Japanese for crack; crevice; fissure
* PCLR - player clear, or pattern clear
* PL - player
* TYPE - keyboard typing
# sound_code Parsing
Every video frame, the game loops through each sound slot to see if a sound is
currently playing, see `@sound_slot_loop`. If a sound slot is populated, i.e.
a sound is playing, then `handle_sound_code` will be called on that slot.
`handle_sound_code` will first check if the game is paused, as the music and
sound effects are paused when the game is paused. If the game isn't paused, the
`handle_sound_code` will decrement the current sound slot's sound length
(`SOUND_CMD_LENGTH`) and if the sound is finished, move to read the next command
(`read_sound_command_00`). If the sound isn't finished, then
`@pulse_vol_and_vibrato` is called to possibly adjust the volume and frequency
of the current playing sound. Note frequency adjustments, i.e. vibrato isn't
used by _Contra_.
A `sound_code` is composed of 1 or more sound commands. Each sound command will
configure variables or set APU registers. Not every sound command will make a
sound. Some just configure variables for the subsequent sound code, for example
setting `SOUND_LENGTH_MULTIPLIER` for use by a subsequent sound command. The
sound commands are parsed according to the following logic.
The first byte of the entire sound code determines how the rest of the commands
for the sound code will be parsed. When the byte is less than #$30, the the
sound code is considered a 'low sound' code. Otherwise, the code will be parsed
as a 'high sound' code.
## sound_code Sharing
All sound command types can reference addresses to other sound commands. When a
sound command moves to another command, once that command is finished executing,
then the sound read pointer goes back to the next bytes in the original command.
* #$fd - move to child sound command for playing shared sound data across
different `sound_xx` commands, or shared parts within the same sound code.
Move to execute sound command at address specified by next 2 bytes.
* #$fe - repeat the next sound command at address `a` `n` times, where `a` is
the next byte and `n` is the 2nd and 3rd byte.
* #$ff - finished reading sound command, exit to previous command if child
sound command, otherwise, finished entire sound code
## Low Sound Command
In _Contra_, low sound commands are used by sound slots #$01 (pulse 1), #$04
(pulse 2) and #$05 (noise). Low sound commands are used for sound effects.
The method in code for parsing low sound commands is `read_low_sound_cmd`. In
general, low sound commands set the length, decrescendo start, pitch, and duty.
The first byte of the sound command dictates how the subsequent bytes are
interpreted. The command is read recursively until the note period is set, then
the parsing exits.
* **Case 1** - #$2x - sets the number of video frames to wait before reading
the next sound command (`SOUND_LENGTH_MULTIPLIER`) as well as the high
nibble of the APU configuration register for the sound channel
(`SOUND_CFG_HIGH`).
* when low nibble is not #$f, then `SOUND_LENGTH_MULTIPLIER` is set to low
nibble.
* when low nibble is #$f, then the next full byte is used to set
`SOUND_LENGTH_MULTIPLIER`
* the following byte is then used to set high nibble of the APU
configuration register for the sound channel (`SOUND_CFG_HIGH`)
* **Case 2** - #$10 - enable/disable sweep and set volume decrescendo. What
is set is based on the next byte. Additionally, if the sound slot is #$04,
then the pulse 1 channel `PULSE_VOL_DURATION` will also be set to the sweep
value.
* non-zero - sweep will be enabled and set to the value of the byte.
* #$00 - if the byte after #$10 is #$00, then sweep will be disabled by
setting the APU register $4001 to #$7f.
be set to #$7f.
* **Case 3** - #$1x - slightly flatten the note that will be played by less
than 1Hz by setting bit 4 of `SOUND_FLAGS`. The low nibble is not used.
This case is not used in _Contra_. However, notes are flattened in another
flow, see `@flip_flatten_note_adv`.
* **Case 4** - #$xx - if not #2x, or #$1x, then byte high nibble is used as
the low nibble (volume) for APU channel config. The high nibble and low
nibble from memory are merged (`SOUND_CFG_HIGH` and `SOUND_CFG_LOW`
respectively), unless volume is constant, then only the high nibble is used
when setting the sound channel configuration ($4000). Also, the sound length
(`SOUND_CMD_LENGTH`), value is set based on `SOUND_LENGTH_MULTIPLIER`.
Finally, the note frequency/counter/pitch is set based off the next two
bytes and then the parsing is complete.
After case 4 is parsed, the read low sound command method exits. Then the game
logic will continue. The next frame, a standard `@sound_slot_loop` will pick up
the sound slot is populated and play possibly modify the sound's volume and
frequency for vibrato (not used in _Contra_). Every video frame, the game logic
repeats this until the sound is completed, the game logic will then continue
by reading the next byte of the next low sound code. This entire process
repeats until a #$ff is read.
## High Sound Command
In _Contra_, high sound commands are used by sound slots #$00 (pulse 1), #$01
(pulse 2), #$02 (triangle) and #$03 (noise & dmc channel). High sound commands
are used for the 'music' of the game: the level background music, the intro
tune, end credits song, and after credits/game over music. The method in code
for parsing low sound commands is `read_high_sound_cmd`.
The first byte of the sound command dictates how the subsequent bytes are
interpreted.
* **Case 1** - if the sound slot is #$03 (noise and dmc channel), then
`parse_percussion_cmd` is called to handle the percussion. See notes below
in section titled `Percussion Command`.
* **Case 2** - if byte 0 high nibble is less than #$c, then `simple_sound_cmd`
is used. This plays a single note with a specified length change from
previous note with a volume envelope specified by `lvl_config_pulse`.
* **Case 3** - if byte 0 high nibble is greater than or equal to #$0c, then
`@regular_sound_cmd` is used. `@regular_sound_cmd` looks at bits 4 and 5 to
know which entry in `sound_cmd_ptr_tbl` to use to handle the sound command.
For details of what each method does, see section `sound_cmd_routine_xx`.
### sound_cmd_routine_xx
* `sound_cmd_routine_00` - sets sound channel config to mute, marks channel
as muted by setting bit 6 of `SOUND_FLAGS`.
* `sound_cmd_routine_01` - sets sound length multiplier
(`SOUND_LENGTH_MULTIPLIER`) to low nibble. Sets in memory low nibble of
sound channel. Can initialize channel by calling
`exe_channel_init_ptr_tbl_routine`. Otherwise, recursively calls back to
`read_high_sound_cmd` to handle next byte.
* `sound_cmd_routine_02`
* If the low nibble is less than #$5, then sets note adjustment flag
(`SOUND_PERIOD_ROTATE`) and recursively calls back to `read_high_sound_cmd`.
* If the low nibble is #$8, set bit 4 of `SOUND_FLAGS` to mark note as
slightly flattened from original value.
* If the low nibble is #$b, set vibrato variables and recursively call
`read_high_sound_cmd`.
* If the low nibble is #$c, set the pitch based on next sound byte, which
(when doubled) is an offset into `note_period_tbl`.
* If the low nibble isn't any known value, just ignore it and recursively
call `read_high_sound_cmd`.
* `sound_cmd_routine_03` - this function handles the end of a sound command
and determines where to go next based on the byte value. See section
above titled `sound_code Sharing`.
### Percussion Command
Percussion commands are recursively read until Case 1 or Case 3 is reached.
* **Case 1** - The byte's high nibble is #$f. This is an end of sound command,
the sound command will either end, repeat, or move back to parent sound
command. See the section titled `sound_code Sharing`.
* **Case 2** - The byte's high nibble is #$d. The low nibble is used to set
the sound length multiplier (`SOUND_LENGTH_MULTIPLIER`).
* **Case 3** - The byte high nibble isn't #$f, nor #$d. In this case, call
`calc_cmd_len_play_percussion` to determine sound command length
(`SOUND_CMD_LENGTH`) based on the low nibble and the value determined from
Case 2. Then call `play_percussive_sound`. This method will use the high
nibble (shifted into low nibble) of the byte value to get offset into
`percussion_tbl`, which specifies which DMC sound sample code to play. This
value is passed to `play_sound` to play the sound code. Then, if the value
was greater than or equal to #$3, `sound_02` is also played with other sound
code. The offsets are defined and which sound(s) is/are played are below.
Note that in offset 5, `sound_02` is not played because there is a check in
`load_sound_code_entry` and `sound_25` is already playing in slot #$05.
* 0 - `sound_02`
* 1 - `sound_5a`
* 2 - `sound_5b`
* 3 - `sound_5a` and `sound_02`
* 4 - `sound_5b` and `sound_02`
* 5 - `sound_25`
* 6 - `sound_5c` and `sound_02`
* 7 - `sound_5d` and `sound_02`

BIN
docs/attachments/pw_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
docs/attachments/pw_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
docs/attachments/pw_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
docs/attachments/pw_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

View File

@ -0,0 +1,15 @@
%% This is a mermaid file (https://github.com/mermaid-js/mermaid)
%% This shows the very high level of the game engine
graph TD
nmi_start --> handle_sound_slots
handle_sound_slots --> load_controller_state
load_controller_state --> exe_game_routine
exe_game_routine --> game_routine_01 --> draw_sprites
exe_game_routine --> game_routine_02 --> draw_sprites
exe_game_routine --> game_routine_03 --> draw_sprites
exe_game_routine --> game_routine_04 --> draw_sprites
exe_game_routine --> game_routine_05
game_routine_05 --> run_level_routine --> draw_sprites
exe_game_routine --> game_routine_06 --> draw_sprites
draw_sprites --> remove_registers_from_stack_and_rti

View File

@ -0,0 +1,42 @@
%% This is a mermaid file (https://github.com/mermaid-js/mermaid)
%% This shows the paths from which the graphics are updated
flowchart TD
level_routine_01:::entry
level_routine_01 --> draw_stage_and_level_name
draw_stage_and_level_name --> load_bank_6_write_text_palette_to_mem
level_routine_01 --> draw_player_num_lives
draw_player_num_lives --> load_bank_6_write_text_palette_to_mem
level_routine_00:::entry
level_routine_00 --> |load transition_screen_palettes|load_bank_6_write_text_palette_to_mem
load_bank_6_write_text_palette_to_mem --> write_text_palette_to_mem
level_routine_00 --> zero_out_nametables
zero_out_nametables --> |graphic_data_0|write_graphic_data_to_ppu
level_routine_04:::entry
level_routine_04 --> handle_scroll
handle_scroll --> load_column_of_tiles_to_cpu_buffer
load_column_of_tiles_to_cpu_buffer --> load_level_supertile_data
level_routine_05:::entry
level_routine_05 --> load_level_intro
load_level_intro --> load_A_offset_graphic_data
load_A_offset_graphic_data --> load_level_graphic_data
load_level_graphic_data --> write_graphic_data_to_ppu
level_routine_03:::entry
level_routine_03 --> init_lvl_nametable_animation
init_lvl_nametable_animation --> load_column_of_tiles_to_cpu_buffer
init_lvl_nametable_animation --> write_col_attribute_to_cpu_memory
level_routine_02:::entry
level_routine_02 --> zero_out_nametables
level_routine_02 --> load_level_graphics
load_level_graphics --> load_current_level_graphic_data
load_current_level_graphic_data --> write_graphic_data_to_ppu
level_routine_02 --> load_palettes_color_to_cpu
nmi_start:::entry
nmi_start --> write_cpu_graphics_buffer_to_ppu
classDef entry fill:#f96

View File

@ -0,0 +1,18 @@
%% This is a mermaid file (https://github.com/mermaid-js/mermaid)
%% This shows the ordering of the states among the level routines
graph TD
level_routine_00 --> level_routine_01
level_routine_01 --> level_routine_02
level_routine_02 --> level_routine_03
level_routine_03 --> level_routine_04
level_routine_04 --> |Level Complete|level_routine_08
level_routine_04 --> |Game Over|level_routine_0a
level_routine_05 --> |Level Complete|level_routine_00
level_routine_05 --> |Complete Last Level/Game Over|level_routine_06
level_routine_05 --> |Game Over No Continues|level_routine_07
level_routine_06 --> |Continue/End Selected|level_routine_00
level_routine_08 --> |Game Over|level_routine_0a
level_routine_08 --> |Level Complete|level_routine_09
level_routine_0a --> |Game Over Timer Expired|level_routine_05
level_routine_09 --> |Level Complete|level_routine_05

View File

@ -0,0 +1,25 @@
%% This is a mermaid file (https://github.com/mermaid-js/mermaid)
%% This shows the simplified overview of the audio engine
flowchart TD
6(read_sound_command_00) -->|< #$30|8(read_low_sound_cmd)
6 --> |>= #$30|9(read_high_sound_cmd)
9 --> |sound slot 3|11(parse_percussion_cmd)
11 --> 16
9 --> 13("@regular_sound_cmd")
9 --> 14(simple_sound_cmd)
14 --> |set note|42[rts]
8 --> 16(sound_cmd_routine_03)
16 --> 43[rts]
16 -.-> |next sound command|6
8 --> 17(interpret_sound_byte)
17 --> |set note|46[rts]
17 -.-> 8
13 --> 39(sound_cmd_routine_00)
39 --> 44[rts]
13 --> 40(sound_cmd_routine_01)
13 --> 41(sound_cmd_routine_02)
13 --> 16
41 -.-> 9
40 --> 45[rts]
40 -.-> 9

View File

@ -0,0 +1,49 @@
%% This is a mermaid file (https://github.com/mermaid-js/mermaid)
%% This shows in detail the logic flow for the audio engine
flowchart TD
5["@check_sound_command"] --> |read next command|6(read_sound_command_00)
6 -->|command byte < #$30|8(read_low_sound_cmd)
6 -->|command byte >= #$30|9(read_high_sound_cmd)
9 --> |noise channel|11(parse_percussion_cmd)
9 --> |"not noise regular command (>=#$c0)"|13("@regular_sound_cmd")
9 --> |"not noise simple command (< #$c0)"|14(simple_sound_cmd)
8 --> |not control byte|17(interpret_sound_byte)
8 --> |control byte\n#$fd, #$fe, #$ff|16(sound_cmd_routine_03)
11 --> |high nibble #$f|16
11 --> |high nibble #$d|20(control_nibble_d)
11 --> |high nibble not #$d not #$f|21(sound_byte_calc_cmd_delay)
17 --> |high nibble not #$2|24("@high_nibble_not_2")
17 --> |high nibble #$2|52[set length multiplier and config]
52 -.-> 8
24 --> |high nibble #$1 low nibble not #$0|54[flatten note]
54 -.-> 8
24 --> |high nibble #$1 low nibble #$0|26[set sweep if specified]
24 --> |high nibble not #$1|27("@high_nibble_not_1")
26 -.-> 8
27 --> 55[set sound multiplier]
55 --> 56(set_note)
56 -.-> 8
20 --> 28["SOUND_LENGTH_MULTIPLIER = low nibble"]
28 -.-> |read next byte|11
16 --> |low nibble < #$0e|33[set NEW_SOUND_CODE_LOW_ADDR, and sound flag bit 3]
16 --> |low nibble #$f|32(low_nibble_f)
16 --> |low nibble #$e|38(read_sound_command_01)
33 --> 34(load_sound_code_addr)
32 --> |SOUND_FLAGS bit 3 set|36(move_new_sound_code)
32 --> |SOUND_FLAGS bit 3 clear|37(exe_channel_init_ptr_tbl_routine)
38 -.-> 6
13 --> 16
13 --> 39(sound_cmd_routine_00)
13 --> 40(sound_cmd_routine_01)
13 --> 41(sound_cmd_routine_02)
41 --> |low nibble >= #$05|43("@high_val")
41 --> |low nibble < #$05|44(set SOUND_PERIOD_ROTATE)
44 -.-> |advance sound byte|9
43 --> |low nibble #$08|46("@flip_flatten_note_adv")
43 --> |low nibble #$0b|48("@set_vibrato_vars_adv")
43 --> |low nibble #$0c|50("@set_pitch_adj_adv")
43 -.-> |unsupported value, advance sound byte|9
50 --> |advance sound byte|51(set SOUND_PITCH_ADJ)
51 -.-> |advance sound byte|9

View File

@ -0,0 +1,9 @@
while (true) do
memory.writebyte(0x00b0, 0xff) -- player 1
memory.writebyte(0x00b1, 0xff) -- player 2
-- 0xb0/b1 in memory store the amount of time the B weapon (barrier) lasts.
-- By continually overwriting the timer to the max value (FF), the players
-- will always be invincible
emu.frameadvance();
end;

View File

@ -0,0 +1,24 @@
while (true) do
local p1score1 = memory.readbyte(0x07e2)
local p1score2 = memory.readbyte(0x07e3)
p1score = (bit.lshift(p1score2, 8) + p1score1) * 100
local weapon_hex = memory.readbyte(0x00aa)
local rapid_fire = ""
if bit.band(weapon_hex, 0x10) == 0x10 then
rapid_fire = " Rapid "
else
rapid_fire = " "
end
weapon_hex = bit.band(weapon_hex, 0x0f)
local weapon_name = ""
if weapon_hex == 0 then weapon_name = "Default Gun"
elseif weapon_hex == 1 then weapon_name = "Machine Gun"
elseif weapon_hex == 2 then weapon_name = "Flame Thrower"
elseif weapon_hex == 3 then weapon_name = "Spray"
elseif weapon_hex == 4 then weapon_name = "Laser" end
gui.text(5, 40, "P1: " .. p1score .. rapid_fire .. weapon_name);
emu.frameadvance();
end;

View File

@ -0,0 +1,9 @@
while (true) do
memory.writebyte(0x00b0, 0xff) -- player 1
memory.writebyte(0x00b1, 0xff) -- player 2
-- 0xb0/b1 in memory store the amount of time the B weapon (barrier) lasts.
-- By continually overwriting the timer to the max value (FF), the players
-- will always be invincible
emu.frameadvance();
end;

View File

@ -0,0 +1,37 @@
-- used for testing to show enemy data
function display_enemy_data()
for i = 0,0xf do
local ENEMY_X_POS = emu.read(0x033e + i, emu.memType.cpu)
local ENEMY_Y_POS = emu.read(0x0324 + i, emu.memType.cpu)
local ENEMY_ANIMATION_DELAY = emu.read(0x0538 + i, emu.memType.cpu)
local ENEMY_ATTRIBUTES = emu.read(0x05a8 + i, emu.memType.cpu)
local ENEMY_FRAME = emu.read(0x0568 + i, emu.memType.cpu)
local ENEMY_HP = emu.read(0x0578 + i, emu.memType.cpu)
local ENEMY_STATE_WIDTH = emu.read(0x0598 + i, emu.memType.cpu)
local ENEMY_VAR_A = emu.read(0x0548 + i, emu.memType.cpu)
local ENEMY_VAR_B = emu.read(0x0558 + i, emu.memType.cpu)
local ENEMY_VAR_1 = emu.read(0x05b8 + i, emu.memType.cpu)
local ENEMY_VAR_2 = emu.read(0x05c8 + i, emu.memType.cpu)
local ENEMY_VAR_3 = emu.read(0x05d8 + i, emu.memType.cpu)
local ENEMY_VAR_4 = emu.read(0x05e8 + i, emu.memType.cpu)
local ENEMY_SPRITES = emu.read(0x030a + i, emu.memType.cpu)
local ENEMY_ROUTINE = emu.read(0x04b8 + i, emu.memType.cpu)
local ENEMY_ATTACK_DELAY = ENEMY_VAR_B
local ENEMY_X_VELOCITY_FAST = emu.read(0x0508 + i, emu.memType.cpu)
local ENEMY_X_VELOCITY_FRACT = emu.read(0x0518 + i, emu.memType.cpu)
local ENEMY_X_VEL_ACCUM = emu.read(0x04d8 + i, emu.memType.cpu)
local ENEMY_Y_VELOCITY_FAST = emu.read(0x04e8 + i, emu.memType.cpu)
local ENEMY_Y_VELOCITY_FRACT = emu.read(0x04f8 + i, emu.memType.cpu)
local ENEMY_Y_VEL_ACCUM = emu.read(0x04c8 + i, emu.memType.cpu)
-- change variable to interested variable for studying
emu.drawString(ENEMY_X_POS, ENEMY_Y_POS, string.format("%x", ENEMY_HP))
end
end
function Main()
display_enemy_data()
end
emu.addEventCallback(Main, emu.eventType.endFrame)

View File

@ -0,0 +1,72 @@
-- shows the background collision codes
-- floor, water, solid, or empty
function get_bg_collision(x, y)
local VERTICAL_SCROLL = emu.read(0xfc, emu.memType.cpu)
local HORIZONTAL_SCROLL = emu.read(0xfd, emu.memType.cpu)
local PPUCTRL_SETTINGS = emu.read(0xff, emu.memType.cpu)
local adjusted_y = y + VERTICAL_SCROLL
local adjusted_x = x + HORIZONTAL_SCROLL
if adjusted_y >= 0xf0 then
adjusted_y = adjusted_y + 0x0f
adjusted_y = adjusted_y - 255
end
-- $10 is always #$00, except when moving cart is calling get_bg_collision
local nametable_number = (PPUCTRL_SETTINGS ~ 0x00) & 0x01
if adjusted_x > 255 then
nametable_number = nametable_number ~ 1
adjusted_x = adjusted_x - 255
end
adjusted_y = (adjusted_y >> 2) & 0x3c
adjusted_x = adjusted_x >> 4
local bg_collision_offset = (adjusted_x >> 2) | adjusted_y
if nametable_number == 1 then
bg_collision_offset = bg_collision_offset | 0x40;
end
local collisionCodeByte = emu.read(0x680 + bg_collision_offset, emu.memType.cpu)
adjusted_x = adjusted_x & 0x03;
local collisionCode = 0
if adjusted_x == 0 then
collisionCode = collisionCodeByte >> 6
elseif adjusted_x == 1 then
collisionCode = collisionCodeByte >> 4
elseif adjusted_x == 2 then
collisionCode = collisionCodeByte >> 2
else
collisionCode = collisionCodeByte
end
collisionCode = collisionCode & 0x03;
local floorColor = 0x508fbc8f
local waterColor = 0x500096FF
local solidColor = 0x50A9A9A9
local tileColor = 0x0
if collisionCode == 0x01 then
tileColor = floorColor
elseif collisionCode == 0x02 then
tileColor = waterColor
elseif collisionCode == 0x03 then
tileColor = solidColor
end
if collisionCode ~= 0 then
emu.drawRectangle(x, y, 16, 16, tileColor, true)
end
end
function Main()
local VERTICAL_SCROLL = emu.read(0xfc, emu.memType.cpu)
local HORIZONTAL_SCROLL = emu.read(0xfd, emu.memType.cpu)
for i = 0,300,16 do
for j = 0,300,16 do
get_bg_collision(i - math.fmod(HORIZONTAL_SCROLL, 16), j - math.fmod(VERTICAL_SCROLL, 16))
end
end
end
emu.addEventCallback(Main, emu.eventType.endFrame)

View File

@ -0,0 +1,106 @@
-- shows the player - enemy collision boxes
-- does not show the player bullet - enemy collision boxes
-- doesn't currently handle collision code f (rising spiked walls and fire beams)
function check_player_x_collision(player_index)
local PLAYER_STATE = emu.read(0x90 + player_index, emu.memType.cpu)
if PLAYER_STATE ~= 0x01 then
-- exit if player not in normal state
return nil
end
-- @check_in_water_crouched
local collision_box = 0x00
local LEVEL_LOCATION_TYPE = emu.read(0x40 + player_index, emu.memType.cpu)
local PLAYER_WATER_STATE = emu.read(0xb2 + player_index, emu.memType.cpu)
if LEVEL_LOCATION_TYPE & 0xfe == 0x00 then
-- outdoor
local PLAYER_SPRITE_SEQUENCE = emu.read(0xbc + player_index, emu.memType.cpu)
if PLAYER_WATER_STATE ~= 0x00 and PLAYER_SPRITE_SEQUENCE == 0x02 then
-- exit if player crouched in water, no collision can happen with player
return nil
end
end
if PLAYER_WATER_STATE == 0x00 then
collision_box = collision_box + 1
end
-- not checking for crouched while on indoor level, because non-bullets can
-- collide when crouching player on indoor levels
local PLAYER_JUMP_STATUS = emu.read(0xa0 + player_index, emu.memType.cpu)
local PLAYER_SPRITE_CODE = emu.read(0xd6 + player_index, emu.memType.cpu)
if PLAYER_JUMP_STATUS == 0x00 then
-- player not jumping
collision_box = collision_box + 1
if PLAYER_SPRITE_CODE ~= 0x17 then
collision_box = collision_box + 1
end
end
-- draw player collision point
local SPRITE_Y_POS = emu.read(0x031a + player_index, emu.memType.cpu)
local SPRITE_X_POS = emu.read(0x0334 + player_index, emu.memType.cpu)
emu.drawRectangle(SPRITE_X_POS, SPRITE_Y_POS, 1, 1, 0x0000ff, false)
for i = 0,0xf do
local ENEMY_ROUTINE = emu.read(0x04b8 + i, emu.memType.cpu)
local ENEMY_ROUTINE = emu.read(0x04b8 + i, emu.memType.cpu)
local ENEMY_STATE_WIDTH = emu.read(0x0598 + i, emu.memType.cpu)
local should_test_collision = ENEMY_STATE_WIDTH & 0x01 == 0x00
if ENEMY_ROUTINE ~= 0x00 and should_test_collision then
set_enemy_collision_box(player_index, i, collision_box)
end
end
end
function set_enemy_collision_box(player_index, i, collision_box)
local ENEMY_SCORE_COLLISION = emu.read(0x0588 + i, emu.memType.cpu) & 0x0f
if ENEMY_SCORE_COLLISION == 0x0f then
-- todo handle (fire beam and rising spiked wall)
return
end
local collision_box_addr = emu.readWord(0xe4e8 + (collision_box * 2), emu.memType.cpu) -- collision_box_codes_tbl
local offset = ENEMY_SCORE_COLLISION * 4
local y0 = emu.read(collision_box_addr + offset, emu.memType.cpu)
local x0 = emu.read(collision_box_addr + offset + 1, emu.memType.cpu)
local height = emu.read(collision_box_addr + offset + 2, emu.memType.cpu)
local width = emu.read(collision_box_addr + offset + 3, emu.memType.cpu)
local ENEMY_Y_POS = emu.read(0x0324 + i, emu.memType.cpu)
local ENEMY_X_POS = emu.read(0x033e + i, emu.memType.cpu)
local topY = ENEMY_Y_POS
if y0 > 0x80 then
y0 = negateInt(y0)
topY = ENEMY_Y_POS - y0
else
topY = ENEMY_Y_POS + y0
end
local topX = ENEMY_X_POS
if x0 > 0x80 then
x0 = negateInt(x0)
topX = ENEMY_X_POS - x0
else
topX = ENEMY_X_POS + x0
end
emu.drawRectangle(topX, topY, width, height, 0x0000ff, false)
end
function negateInt(num)
if(num > 0x80) then
num = (~num + 1) & 0xff
end
return num
end
function Main()
check_player_x_collision(0)
end
emu.addEventCallback(Main, emu.eventType.endFrame)

View File

@ -0,0 +1,27 @@
-- shows information about weapon on head-up-display area
function Main()
local p1score1 = emu.read(0x07e2, emu.memType.cpu)
local p1score2 = emu.read(0x07e3, emu.memType.cpu)
p1score = ((p1score2 << 8) + p1score1) * 100
local weapon_hex = emu.read(0x00aa, emu.memType.cpu)
local rapid_fire = ""
if weapon_hex & 0x10 == 0x10 then
rapid_fire = " Rapid "
else
rapid_fire = " "
end
weapon_hex = weapon_hex & 0x0f
local weapon_name = ""
if weapon_hex == 0 then weapon_name = "Default Gun"
elseif weapon_hex == 1 then weapon_name = "Machine Gun"
elseif weapon_hex == 2 then weapon_name = "Flame Thrower"
elseif weapon_hex == 3 then weapon_name = "Spray"
elseif weapon_hex == 4 then weapon_name = "Laser" end
emu.drawString(5,40, "P1: " .. p1score .. rapid_fire .. weapon_name)
end
emu.addEventCallback(Main, emu.eventType.endFrame)

View File

@ -0,0 +1,45 @@
This folder contains every sprite that can be loaded in Contra. The sprites
here are not used by the game code, they were extracted from the ROM for ease of
viewing.
The name of each sprite is the same as the label in bank1.asm in the 2 large
sprite tables sprite_ptr_tbl_0 and sprite_ptr_tbl_1.
The player lives medals in the heads up display (HUD) are not in the sprite
tables, but the tiles are written directly to the OAMDMA via the CPU buffer.
# Sprite Table
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
|---|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|
| 0 | | | ![sprite_02](sprite_02.png?raw=true "sprite_02") ![sprite_02_p2](sprite_02_p2.png?raw=true "sprite_02_p2") | ![sprite_03](sprite_03.png?raw=true "sprite_03") ![sprite_03_p2](sprite_03_p2.png?raw=true "sprite_03_p2") | ![sprite_04](sprite_04.png?raw=true "sprite_04") ![sprite_04_p2](sprite_04_p2.png?raw=true "sprite_04_p2") | ![sprite_05](sprite_05.png?raw=true "sprite_05") ![sprite_05_p2](sprite_05_p2.png?raw=true "sprite_05_p2") | ![sprite_06](sprite_06.png?raw=true "sprite_06") ![sprite_06_p2](sprite_06_p2.png?raw=true "sprite_06_p2") | ![sprite_07](sprite_07.png?raw=true "sprite_07") | ![sprite_08](sprite_08.png?raw=true "sprite_08") ![sprite_08_p2](sprite_08_p2.png?raw=true "sprite_08_p2") | ![sprite_09](sprite_09.png?raw=true "sprite_09") ![sprite_09_p2](sprite_09_p2.png?raw=true "sprite_09_p2") | ![sprite_0a](sprite_0a.png?raw=true "sprite_0a") ![sprite_0a_p2](sprite_0a_p2.png?raw=true "sprite_0a_p2") | ![sprite_0b](sprite_0b.png?raw=true "sprite_0b") ![sprite_0b_p2](sprite_0b_p2.png?raw=true "sprite_0b_p2") | ![sprite_0c](sprite_0c.png?raw=true "sprite_0c") ![sprite_0c_p2](sprite_0c_p2.png?raw=true "sprite_0c_p2") | ![sprite_0d](sprite_0d.png?raw=true "sprite_0d") ![sprite_0d_p2](sprite_0d_p2.png?raw=true "sprite_0d_p2") | ![sprite_0e](sprite_0e.png?raw=true "sprite_0e") ![sprite_0e_p2](sprite_0e_p2.png?raw=true "sprite_0e_p2") | ![sprite_0f](sprite_0f.png?raw=true "sprite_0f") ![sprite_0f_p2](sprite_0f_p2.png?raw=true "sprite_0f_p2") |
| 1 | ![sprite_10](sprite_10.png?raw=true "sprite_10") ![sprite_10_p2](sprite_10_p2.png?raw=true "sprite_10_p2") | ![sprite_11](sprite_11.png?raw=true "sprite_11") ![sprite_11_p2](sprite_11_p2.png?raw=true "sprite_11_p2") | ![sprite_12](sprite_12.png?raw=true "sprite_12") ![sprite_12_p2](sprite_12_p2.png?raw=true "sprite_12_p2") | ![sprite_13](sprite_13.png?raw=true "sprite_13") ![sprite_13_p2](sprite_13_p2.png?raw=true "sprite_13_p2") | ![sprite_14](sprite_14.png?raw=true "sprite_14") ![sprite_14_p2](sprite_14_p2.png?raw=true "sprite_14_p2") | ![sprite_15](sprite_15.png?raw=true "sprite_15") ![sprite_15_p2](sprite_15_p2.png?raw=true "sprite_15_p2") | ![sprite_16](sprite_16.png?raw=true "sprite_16") ![sprite_16_p2](sprite_16_p2.png?raw=true "sprite_16_p2") | ![sprite_17](sprite_17.png?raw=true "sprite_17") ![sprite_17_p2](sprite_17_p2.png?raw=true "sprite_17_p2") | ![sprite_18](sprite_18.png?raw=true "sprite_18") | ![sprite_19](sprite_19.png?raw=true "sprite_19") | ![sprite_1a](sprite_1a.png?raw=true "sprite_1a") | ![sprite_1b](sprite_1b.png?raw=true "sprite_1b") | ![sprite_1c](sprite_1c.png?raw=true "sprite_1c") | ![sprite_1d](sprite_1d.png?raw=true "sprite_1d") | ![sprite_1e](sprite_1e.png?raw=true "sprite_1e") | ![sprite_1f](sprite_1f.png?raw=true "sprite_1f") |
| 2 | ![sprite_20](sprite_20.png?raw=true "sprite_20") | ![sprite_21](sprite_21.png?raw=true "sprite_21") | ![sprite_22](sprite_22.png?raw=true "sprite_22") | ![sprite_23](sprite_23.png?raw=true "sprite_23") | ![sprite_24](sprite_24.png?raw=true "sprite_24") | ![sprite_25](sprite_25.png?raw=true "sprite_25") | ![sprite_26](sprite_26.png?raw=true "sprite_26") | ![sprite_27](sprite_27.png?raw=true "sprite_27") | ![sprite_28](sprite_28.png?raw=true "sprite_28") | ![sprite_29](sprite_29.png?raw=true "sprite_29") | ![sprite_2a](sprite_2a.png?raw=true "sprite_2a") | ![sprite_2b](sprite_2b.png?raw=true "sprite_2b") | ![sprite_2c](sprite_2c.png?raw=true "sprite_2c") | ![sprite_2d](sprite_2d.png?raw=true "sprite_2d") | | ![sprite_2f](sprite_2f.png?raw=true "sprite_2f") |
| 3 | ![sprite_30](sprite_30.png?raw=true "sprite_30") | ![sprite_31](sprite_31.png?raw=true "sprite_31") | ![sprite_32](sprite_32.png?raw=true "sprite_32") | ![sprite_33](sprite_33.png?raw=true "sprite_33") | ![sprite_34](sprite_34.png?raw=true "sprite_34") | ![sprite_35](sprite_35.png?raw=true "sprite_35") | ![sprite_36](sprite_36.png?raw=true "sprite_36") | ![sprite_37](sprite_37.png?raw=true "sprite_37") | ![sprite_38](sprite_38.png?raw=true "sprite_38") | ![sprite_39](sprite_39.png?raw=true "sprite_39") | ![sprite_3a](sprite_3a.png?raw=true "sprite_3a") | ![sprite_3b](sprite_3b.png?raw=true "sprite_3b") | ![sprite_3c](sprite_3c.png?raw=true "sprite_3c") | ![sprite_3d](sprite_3d.png?raw=true "sprite_3d") | ![sprite_3e](sprite_3e.png?raw=true "sprite_3e") | ![sprite_3f](sprite_3f.png?raw=true "sprite_3f") |
| 4 | ![sprite_40](sprite_40.png?raw=true "sprite_40") | ![sprite_41](sprite_41.png?raw=true "sprite_41") | ![sprite_42](sprite_42.png?raw=true "sprite_42") | ![sprite_43](sprite_43.png?raw=true "sprite_43") | ![sprite_44](sprite_44.png?raw=true "sprite_44") | ![sprite_45](sprite_45.png?raw=true "sprite_45") | ![sprite_46](sprite_46.png?raw=true "sprite_46") | ![sprite_47](sprite_47.png?raw=true "sprite_47") | ![sprite_48](sprite_48.png?raw=true "sprite_48") | ![sprite_49](sprite_49.png?raw=true "sprite_49") | ![sprite_4a](sprite_4a.png?raw=true "sprite_4a") | ![sprite_4b](sprite_4b.png?raw=true "sprite_4b") | ![sprite_4c](sprite_4c.png?raw=true "sprite_4c") | ![sprite_4d](sprite_4d.png?raw=true "sprite_4d") | ![sprite_4e](sprite_4e.gif?raw=true "sprite_4e") | |
| 5 | ![sprite_50](sprite_50.png?raw=true "sprite_50") ![sprite_50_p2](sprite_50_p2.png?raw=true "sprite_50_p2") | ![sprite_51](sprite_51.png?raw=true "sprite_51") ![sprite_51_p2](sprite_51_p2.png?raw=true "sprite_51_p2") | ![sprite_52](sprite_52.png?raw=true "sprite_52") ![sprite_52_p2](sprite_52_p2.png?raw=true "sprite_52_p2") | ![sprite_53](sprite_53.png?raw=true "sprite_53") ![sprite_53_p2](sprite_53_p2.png?raw=true "sprite_53_p2") | ![sprite_54](sprite_54.png?raw=true "sprite_54") ![sprite_54_p2](sprite_54_p2.png?raw=true "sprite_54_p2") | ![sprite_55](sprite_55.png?raw=true "sprite_55") ![sprite_55_p2](sprite_55_p2.png?raw=true "sprite_55_p2") | ![sprite_56](sprite_56.png?raw=true "sprite_56") ![sprite_56_p2](sprite_56_p2.png?raw=true "sprite_56_p2") | ![sprite_57](sprite_57.png?raw=true "sprite_57") ![sprite_57_p2](sprite_57_p2.png?raw=true "sprite_57_p2") | ![sprite_58](sprite_58.png?raw=true "sprite_58") ![sprite_58_p2](sprite_58_p2.png?raw=true "sprite_58_p2") | | | | | ![sprite_5d](sprite_5d.png?raw=true "sprite_5d") | ![sprite_5e](sprite_5e.png?raw=true "sprite_5e") | ![sprite_5f](sprite_5f.png?raw=true "sprite_5f") |
| 6 | ![sprite_60](sprite_60.png?raw=true "sprite_60") | ![sprite_61](sprite_61.png?raw=true "sprite_61") | ![sprite_62](sprite_62.png?raw=true "sprite_62") | ![sprite_63](sprite_63.png?raw=true "sprite_63") | ![sprite_64](sprite_64.png?raw=true "sprite_64") | | | | ![sprite_68](sprite_68.png?raw=true "sprite_68") | ![sprite_69](sprite_69.png?raw=true "sprite_69") | ![sprite_6a](sprite_6a.png?raw=true "sprite_6a") | ![sprite_6b](sprite_6b.png?raw=true "sprite_6b") | ![sprite_6c](sprite_6c.png?raw=true "sprite_6c") | ![sprite_6d](sprite_6d.png?raw=true "sprite_6d") | ![sprite_6e](sprite_6e.png?raw=true "sprite_6e") | ![sprite_6f](sprite_6f.png?raw=true "sprite_6f") |
| 7 | ![sprite_70](sprite_70.png?raw=true "sprite_70") | ![sprite_71](sprite_71.png?raw=true "sprite_71") | ![sprite_72](sprite_72.png?raw=true "sprite_72") | ![sprite_73](sprite_73.png?raw=true "sprite_73") | ![sprite_74](sprite_74.png?raw=true "sprite_74") | ![sprite_75](sprite_75.png?raw=true "sprite_75") | ![sprite_76](sprite_76.png?raw=true "sprite_76") | ![sprite_77](sprite_77.png?raw=true "sprite_77") | ![sprite_78](sprite_78.png?raw=true "sprite_78") | ![sprite_79](sprite_79.png?raw=true "sprite_79") | ![sprite_7a](sprite_7a.png?raw=true "sprite_7a") | ![sprite_7b](sprite_7b.png?raw=true "sprite_7b") | ![sprite_7c](sprite_7c.png?raw=true "sprite_7c") | ![sprite_7d](sprite_7d.png?raw=true "sprite_7d") | ![sprite_7e](sprite_7e.png?raw=true "sprite_7e") | |
| 8 | | | ![sprite_82](sprite_82.png?raw=true "sprite_82") | ![sprite_83](sprite_83.png?raw=true "sprite_83") | ![sprite_84](sprite_84.png?raw=true "sprite_84") | ![sprite_85](sprite_85.png?raw=true "sprite_85") | ![sprite_86](sprite_86.png?raw=true "sprite_86") | ![sprite_87](sprite_87.png?raw=true "sprite_87") | ![sprite_88](sprite_88.png?raw=true "sprite_88") | ![sprite_89](sprite_89.png?raw=true "sprite_89") | ![sprite_8a](sprite_8a.png?raw=true "sprite_8a") | ![sprite_8b](sprite_8b.png?raw=true "sprite_8b") | ![sprite_8c](sprite_8c.png?raw=true "sprite_8c") | ![sprite_8d](sprite_8d.png?raw=true "sprite_8d") | ![sprite_8e](sprite_8e.png?raw=true "sprite_8e") | ![sprite_8f](sprite_8f.png?raw=true "sprite_8f") |
| 9 | ![sprite_90](sprite_90.png?raw=true "sprite_90") | ![sprite_91](sprite_91.png?raw=true "sprite_91") ![sprite_91_p2](sprite_91_p2.png?raw=true "sprite_91_p2") | ![sprite_92](sprite_92.png?raw=true "sprite_92") | ![sprite_93](sprite_93.png?raw=true "sprite_93") | ![sprite_94](sprite_94.png?raw=true "sprite_94") | ![sprite_95](sprite_95.png?raw=true "sprite_95") | ![sprite_96](sprite_96.png?raw=true "sprite_96") | ![sprite_97](sprite_97.png?raw=true "sprite_97") | ![sprite_98](sprite_98.png?raw=true "sprite_98") | ![sprite_99](sprite_99.png?raw=true "sprite_99") | ![sprite_9a](sprite_9a.png?raw=true "sprite_9a") | ![sprite_9b](sprite_9b.png?raw=true "sprite_9b") | ![sprite_9c](sprite_9c.png?raw=true "sprite_9c") | ![sprite_9d](sprite_9d.png?raw=true "sprite_9d") | ![sprite_9e](sprite_9e.png?raw=true "sprite_9e") | ![sprite_9f](sprite_9f.png?raw=true "sprite_9f") |
| a | ![sprite_a0](sprite_a0.png?raw=true "sprite_a0") | ![sprite_a1](sprite_a1.png?raw=true "sprite_a1") | ![sprite_a2](sprite_a2.png?raw=true "sprite_a2") | ![sprite_a3](sprite_a3.png?raw=true "sprite_a3") | ![sprite_a4](sprite_a4.png?raw=true "sprite_a4") | ![sprite_a5](sprite_a5.png?raw=true "sprite_a5") | ![sprite_a6](sprite_a6.png?raw=true "sprite_a6") | ![sprite_a7](sprite_a7.png?raw=true "sprite_a7") | ![sprite_a8](sprite_a8.png?raw=true "sprite_a8") | ![sprite_a9](sprite_a9.png?raw=true "sprite_a9") | ![sprite_aa](sprite_aa.png?raw=true "sprite_aa") | ![sprite_ab](sprite_ab.png?raw=true "sprite_ab") | ![sprite_ac](sprite_ac.png?raw=true "sprite_ac") | ![sprite_ad](sprite_ad.png?raw=true "sprite_ad") | ![sprite_ae](sprite_ae.png?raw=true "sprite_ae") | ![sprite_af](sprite_af.png?raw=true "sprite_af") |
| b | ![sprite_b0](sprite_b0.png?raw=true "sprite_b0") | ![sprite_b1](sprite_b1.png?raw=true "sprite_b1") | ![sprite_b2](sprite_b2.png?raw=true "sprite_b2") | ![sprite_b3](sprite_b3.png?raw=true "sprite_b3") | ![sprite_b4](sprite_b4.png?raw=true "sprite_b4") | ![sprite_b5](sprite_b5.png?raw=true "sprite_b5") | ![sprite_b6](sprite_b6.png?raw=true "sprite_b6") | ![sprite_b7](sprite_b7.png?raw=true "sprite_b7") | ![sprite_b8](sprite_b8.png?raw=true "sprite_b8") | ![sprite_b9](sprite_b9.png?raw=true "sprite_b9") | ![sprite_ba](sprite_ba.png?raw=true "sprite_ba") | ![sprite_bb](sprite_bb.png?raw=true "sprite_bb") | ![sprite_bc](sprite_bc.png?raw=true "sprite_bc") | ![sprite_bd](sprite_bd.png?raw=true "sprite_bd") | ![sprite_be](sprite_be.png?raw=true "sprite_be") | ![sprite_bf](sprite_bf.png?raw=true "sprite_bf") |
| c | ![sprite_c0](sprite_c0.png?raw=true "sprite_c0") | ![sprite_c1](sprite_c1.png?raw=true "sprite_c1") | ![sprite_c2](sprite_c2.png?raw=true "sprite_c2") | ![sprite_c3](sprite_c3.png?raw=true "sprite_c3") | ![sprite_c4](sprite_c4.png?raw=true "sprite_c4") | ![sprite_c5](sprite_c5.png?raw=true "sprite_c5") | ![sprite_c6](sprite_c6.png?raw=true "sprite_c6") | ![sprite_c7](sprite_c7.png?raw=true "sprite_c7") | ![sprite_c8](sprite_c8.png?raw=true "sprite_c8") | ![sprite_c9](sprite_c9.png?raw=true "sprite_c9") | ![sprite_ca](sprite_ca.png?raw=true "sprite_ca") | ![sprite_cb](sprite_cb.png?raw=true "sprite_cb") | ![sprite_cc](sprite_cc.png?raw=true "sprite_cc") | ![sprite_cd](sprite_cd.png?raw=true "sprite_cd") | ![sprite_ce](sprite_ce.png?raw=true "sprite_ce") | ![sprite_cf](sprite_cf.png?raw=true "sprite_cf") |
# Hud Sprites
* Player 1
* ![player_1_lives_medal](player_1_lives_medal.png?raw=true "player_1_lives_medal")
* ![player_1_game_over](player_1_game_over.png?raw=true "player_1_game_over")
* Player 2
* ![player_2_lives_medal](player_2_lives_medal.png?raw=true "player_2_lives_medal")
* ![player_2_game_over](player_2_game_over.png?raw=true "player_2_game_over")
# Sprites Not Used In Game
* ![sprite_78](sprite_78.png?raw=true "sprite_78") (`sprite_78`) exists in the
game code, but isn't used. It is identical to `sprite_74` and that is what is
used insead.
* `sprite_59`, `sprite_5a`, `sprite_5b`, `sprite_5c`, `sprite_65`, `sprite_66`,
and `sprite_67` are all empty and not used in the game.
* `sprite_80` and `sprite_81` are defined in the game, but never used so I can't
tell which pattern tiles they are supposed to utilize.

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Some files were not shown because too many files have changed in this diff Show More