Kid Icarus Revealed: 1-2
NES Hardware fundamentals
I’ll discuss only the key facts on the NES hardware, that are relevant and needed, for Kid Icarus disassembling process.
NES memory maps, or valid CPU addresses, are in the range of $0000-$FFFF, because it uses a 16 bits address bus CPU.
$C000-$FFFF ----> ROM high bank
$8000-$BFFF ----> ROM low bank
$6000-$7FFF ----> Expansion RAM
$2000-$4FFF ----> I/O ports
$0000-$07FF ----> First 2Kb are dedicated to system main RAM
Last 32Kb are dedicated to ROM mapping. The 128Kb Kid Icarus ROM should be seen as eight consecutive 16Kb banks.
Bank 0 Bank 1 Bank 2 Bank 3 Bank 4 Bank 5 Bank 6 Bank 7
Kid Icarus bank 7 is always mapped in ROM high bank area. This means it’s always accesible to NES, from address $C000 to $FFFF. And so, it is said to be hardwired. Any of the other banks can be mapped in ROM low bank area.
Ok, now we have to distinguish between ROM addresses and NES addresses. It is very important to have a clear idea about this in order to fully understand the disassembling process, and work on NES ROM hacking.
Have a look at the following table, where we can again see the eight ROM banks, but with its associated address ranges.
ROM addresses table
ROM Bank 0 ----> $00000-$03FFF
ROM Bank 1 ----> $04000-$07FFF
ROM Bank 2 ----> $08000-$0BFFF
ROM Bank 3 ----> $0C000-$0FFFF
ROM Bank 4 ----> $10000-$13FFF
ROM Bank 5 ----> $14000-$17FFF
ROM Bank 6 ----> $18000-$1BFFF
ROM Bank 7 ----> $1C000-$1FFFF
Notice that ROM addresses are 1 bit longer than NES addresses, so, I will always write ROM addresses using five digits, while NES addresses will always be 4 digits.
ROM, Bank 7 starts at ROM offset $1C000.
So, the first byte of this bank is at ROM offset $1C000. But if you want to access to this byte, you cannot use the ROM offset. You always need to use real NES addresses. These are the only ones that have a meaning for the console. Ok, how we do this?
Well, Bank 7 is mapped in NES address range $C000-$FFFF. So, real address for this first byte will be $C000. Now imagine that we map bank 3 in ROM low bank area ($8000-$BFFF) Just to ilustrate it, let’s have a look at real addresses for the first 4 bytes of each bank.
ROM Bank 3 == ROM address ===== NES real address
Byte 0 ------- $0C000 ---------- $8000
Byte 1 ------- $0C001 ---------- $8001
Byte 2 ------- $0C002 ---------- $8002
Byte 3 ------- $0C003 ---------- $8003
ROM Bank 7 == ROM address ===== NES real address
Byte 0 ------- $1C000 ---------- $C000
Byte 1 ------- $1C001 ---------- $C001
Byte 2 ------- $1C002 ---------- $C002
Byte 3 ------- $1C003 ---------- $C003
This is too easy, so if you don’t get it clear go over it again, till you do :-)
ROM address to real address translation
Now on with the interesting thing. Imagine you want to know which real address corresponds to ROM offset $1DFB2. First of all you need to find which bank is it in. Having the ROM addresses table, it is immediate, so I recommend you have one at hand. But it is as easy as dividing the address by 16Kb, and ignoring the remainder:
For address $1DFB2 we get 7. Now that we know which bank we are in, we need to know the offset from bank start address, called bank base address. In this case, bank 7 base address is $1C000.
To get the offset just substract. $1DFB2 - $1C000 = $1FB2. Now we add this offset to NES base address. As bank 7 is mapped in high bank area, it’s base address is $C000.
NES address = NES bank base address + ROM bank offset
In this example: $DFB2 = $C000 + $1FB2
An so, if we want to access byte at ROM address $1DFB2, we can find it in NES address $DFB2.
For a ROM address $14201, we follow all previous steps.
- It is a bank 5 ROM address
- It ROM bank offset is $201
- Assuming bank 5 is mapped, it’s NES bank base address is $8000.
- Real address is $8201
Keep in mind that we assumed bank 5 is mapped. If it is not mapped, then it is not accessible at that time, and there’s no real address for it. You need to tell MMC1 mapper to map that bank first.
Making this kind of address translation is vital. Very handy when you’re working on the ROM itselves. For example;
* If you’re disassembling ROM code, you will likely want to have a look at some code running, using a debugger. To do so, you need a breakpoint address. When an instruction is fetched from there, execution flow stops, and you can then execute step-by-step. You’ll need to translate the ROM address, in order to set the debugger’s breakpoint, at the desired real address.
* Or you might have found some interesting data in the ROM, or maybe you don’t really know if it is data or code. And you want to go into that. You could again use a debugger, and set it to do a breakpoint at that address. If execution flow stops at that breakpoint, then you know it is code. If the address is just accessed to get an operand, that would mean is data. You can make sure of this, using some debuggers, that let you set breakpoints for data access too (execution flow stops whenever the breakpoint address is being read or write). Again, you’d need to do the translation.
Real address to ROM address translation
Of course, if you understand this process, you can go back from a real address to a ROM address. This is needed if you get a real address that is of your interest, for example;
* Using an emulator with debugger.
* Disassembling code. All memory references in the code will be real addresses. If you want to find the data in the ROM file, you need to translate them. This is the most common case
You might find out that an assembler routine at address $CF74 is doing something cool. And you want to locate that routine in the ROM, to modify it, or trying to corrupt it.
- $CF74 is a real address inside the ROM high bank area. So, it must be from ROM bank 7.
- As high bank area starts at base address $C000, our address is $0F74 offset.
- We add the offset to the ROM bank 7 base address $1C000. We get $1CF74.
If the real address corresponds to low bank area (addresses $8000-$BFFF), we would first need to know which bank it is mapped there. For Kid Icarus, I have a definitive, “always-work” way to find it out. Look at address $B6. It is a RAM address where the game stores the actual bank mapped in low bank area. So cool.
By the way, the assembler routine starting at $CF74 is responsible of recovering Pit’s health when in yellow water :-)
ROM MAP (levels data)
| Location | Level | Description |Size |
Bank 0 and 1
| 00000-07FFF | x | Tiles |7E3 |
| 0B04A-0B82C | 2 | Screen data |7E3 |
| 0B82D-0B93F | 2 | Structure data |113 |
| 0B940-0BB03 | 2 | Macros |1C4 |
| 0BB04-0BB13 | 2 | 5th FFFF line | |
| 0BB14-0BBF1 | 2 | Screen pointers |DE |
| 0BBF2-0BC39 | 2 | Structure pointers |48 |
| 0BC3A-0BC4E | 2 | Items table |15 |
| 0BC4F-0BDC2 | 2 | Enemy tables | |
| 0BDC3-0BE42 | 2 | Platforms (4x2 lines)| |
| 0BE43-0BE45 | 2 | Palettes info? | |
| 0BE46-0BEA5 | 2 | Palettes | |
| 0BEA6-0BED2 | 2 | Enemy property table | |
| 0FA8C-0FBFC | 4-1 | Screen data |171 |
| 0FBFD-0FCB7 | 4-1 | Structure data |BB |
| 0FCB8-0FE7B | 4-1 | Macros |1C4 |
| 0FF34-0FF53 | 4-1 | Palettes | |
| 15BF5-1626C | x-4 | Screen data |678 |
| 1626D-1631D | x-4 | Structure data |B1 |
| 1631E-164E1 | x-4 | Macros |1C4 |
| 19950-1A1B9 | 1 | Screen data |86A |
| 1A1BA-1A2EB | 1 | Structure data |132 |
| 1A2EC-1A4AF | 1 | Macros |1C4 |
| 1A4B0-1A52F | 1 | Platforms (4x2 lines)| |
| 1A530-1A53F | 1 | 5th “FFFF” unknown | |
| 1A540-1A5CB | 1 | Screen pointers |8C |
| 1A5CC-1A61F | 1 | Structure pointers |54 |
| 1A620-1A6F3 | 1 | Enemy tables | |
| 1A6F4-1A6F6 | 1 | Palettes info? | |
| 1A6F7-1A756 | 1 | Palettes | |
| 1A757-1A76F | 1 | Items table |19 |
| 1A770-1A79F | 1 | Enemy property table | |
| 1A7A0-1ABC5 | 3 | Screen data |426 |
| 1ABC6-1AC76 | 3 | Structure data |B1 |
| 1AC77-1AE3A | 3 | Macros |1C4 |
| 1AE3B-1AEBA | 3 | Platforms | |
| 1AEBB-1AECA | 3 | 5th FFFF line | |
| 1AECB-1AF4C | 3 | Screen Pointers |82 |
| 1AF4D-1AF80 | 3 | Structure pointers |34 |
| 1AF81-1B068 | 3 | Enemy tables | |
| 1B069-1B06B | 3 | Palette info? | |
| 1B06C-1B0CB | 3 | Palettes | |
| 1B0CC-1B0D8 | 3 | Item table |D |
| 1B0D9-1B108 | 3 | Enemy prop table | |
| 1B109-1B12F | 1-4 | Header block |27 |
| 1B130-1B183 | 1-4 | Screen pointers |54 |
| 1B184-1B1B7 | 1-4 | Structure pointers |34 |
| 1B1B8-1B237 | 1-4 | Rooms map |80 |
| 1B238-1B277 | 1-4 | Room enemy table |40 |
| 1B278-1B2C3 | 1-4 | Enemy position table |4C |
| 1B2C4-1B306 | 1-4 | Map+Centurion table |43 |
| 1B307-1B309 | 1-4 | Palettes info? |3 |
| 1B30A-1B329 | 1-4 | Palettes (32 bytes) |20 |
| 1B30A-1B35B | 1-4 | Enemy prop table |30 |
| 1B35C-1B3F9 | 1-4 | Enemy animations |9E |
| 1B3FD-1B423 | 2-4 | Header block |27 |
| 1B424-1B477 | 2-4 | Screen pointers |54 |
| 1B478-1B4AB | 2-4 | Structure pointers |34 |
| 1B4AC-1B52B | 2-4 | Rooms map |80 |
| 1B52C-1B56B | 2-4 | Room enemy table |40 |
| 1B56C-1B5B7 | 2-4 | Enemy position table |4C |
| 1B5B8-1B5FA | 2-4 | Map+Centurion table |43 |
| 1B5FB-1B5FD | 2-4 | Palettes info? |3 |
| 1B5FE-1B61D | 2-4 | Palettes (32 bytes) |20 |
| 1B61E-1B64D | 2-4 | Enemy prop table |30 |
| 1B64E-1B6D0 | 2-4 | Enemy animations |83 |
| 1B6D1-1B6F7 | 3-4 | Header block |27 |
| 1B6F8-1B74B | 3-4 | Screen pointers |54 |
| 1B74C-1B7A7 | 3-4 | Structure pointers |34 |
| 1B780-1B7FF | 3-4 | Rooms map |80 |
| 1B800-1B83F | 3-4 | Room enemy table |40 |
| 1B840-1B88B | 3-4 | Enemy position table |4C |
| 1B88C-1B8CE | 3-4 | Map+Centurion table |43* |
| 1B8CF-1B8D1 | 3-4 | Palettes info? |3 |
| 1B8D2-1B8FE | 3-4 | Palettes (32 bytes) |20 |
| 1B8FF-1B91E | 3-4 | Enemy prop table |30 |
| 1B91F-1B9E4 | 3-4 | Enemy animations |C6 |
| 1EFB9-xxxxx | x-x | Door Data | |
* 26 bytes after FF are not used.
At the beginning of level 1-1, banks 2, 4 and 7 are used.
While in World 1, bank 2 is usually mapped.
While in World 2, bank 2 is usually mapped.
While in World 3, bank 3 is usually mapped.
While in fortresses, bank 5 is usually mapped.
Not all Worlds follow the exact level storage and use scheme. I’ll call level data to the set of Macros, Structures, Screen and Level data for each world, along with palettes, and enemies, mobile platforms, and items location. I’ll call level code to the primary chunck of code used while in gameplay for that world.
Worlds 1 and 3
Level data is stored in bank 6.
This data is loaded into the cartridge’s expansion RAM, at offset $7000, so bank 6 does not need to be mapped while playing. This is probably because most code routines for these worlds are located within bank 2, which is the one mapped during gameplay. Levels are decompressed from expansion RAM.
World 2 and 4
Both, level’s data and code, are stored in bank 2, so no level data is copied to expansion RAM, and bank 2 is kept mapped. Levels are decompresses from bank 2.
Same goes for single level World 4, but now level’s data and code are stored in bank 3.
Fortresses are fun thing. They use a different encoding format for level data, which was foreseeable, as the structure of a fortress differs from an ordinary level. Instead of a plain linear level, we have a labyrinth of rooms, so we need to keep information about things such as room layout, map and centurion locations, etc.
What I did not predict was that fortresses also used a different level data storage layout. Fortresses level data is split down.
Macros, Structures and Screen data is stored in bank 5. I call this area the “fortress shared ROM area”, because all fortress use it. In these things there are not differences with ordinal levels.
The rest of level data, is stored in bank 6, after world 1 and 3 level data.
Remember Worlds 1 and 3 load its level data into expansion RAM? Well, same applies to fortresses. But, not all data is loaded. Only the level data which is stored in bank 6 is copied to expansion RAM, at offset $7000. Since bank 5 is usually mapped while in fortress gameplay, all level data is accessible at same time. Clever design. I think bank 5 is usually mapped probably because level code is there.
Kid Icarus has 2 pattern tables. Here, I will discuss the one used for backgrounds. As I already said, macros, which are the basic building blocks for the game, are made out of 2x2 tiles, stored in the background pattern table.
This pattern table changes between Worlds, so new tile set is loaded each time Pit goes from one World to another, making it possible to show different graphics. For example, World 2 has ice platforms. World 3 has cloud platforms. Why should we have this cloud tiles in VRAM while we don’t need them?? As there’s very limited VRAM in the NES, we have to save as much space as possible.
Kid Icarus background tile set changes very little though. Actually, 192 tiles remain constant throughout the game. Only last 48 positions change. Remember that the pattern table can hold 256 tiles. First World pattern table was shown at the beginning of this document. Below, there’re World 2’s and World 3’s pattern table, for you to see that the only changes made to them, only affects 48 last tiles (3 rows in the image).
This layout was carefully designed. The first 192 tiles are commonly used in the game, and they are always available. World’s exclusive tiles are a little fraction, but very well choosen. Fortresses share the same macros, structures and rooms. So, all rooms have an equivalent in the other 2 fortresses. But they seem to have different structures. For example, look at the room where you fight Tweenbelows. It has an equivalent in the second fortress (though, never shown in real game).
It is obvious that platforms are different structures. But I can guarantee you that structure data is shared by all fortresses. Well, this is one reason I said pattern tables where carefully designed. Some macros use the exclusive world’s tiles. Those macros are builded by grouping 2x2 tiles from a certain position of the pattern table. So, when World changes, those tiles change too. And so, macro’s appearance too! And so, structures made with those macros, also change.
If at this moment you find difficult to understand Kid Icarus’s graphic system scheme, then you are right. It is quite complex, and you should train yourself in understanding and diferenciating pointer than actual data. As macros are 4 pointers to tiles, if you change the actual data (tiles), macros, who where just pointing the data, also changes.
Structure pointers table
If you have a look at the ROM map, you’ll see “Structure data” area, where graphical structures are stored, following the format described. Then, there’s a “Structure pointers” area. These are variable size tables, that hold 16 bit memory pointers to each structure data in the “Structure data” area. This way, you can directly find a certain structure data address by looking at this table. For example, imagine you want to find World 2’s fourth structure. First, go to the Structure pointer table, found at ROM offset $0BBF2
0000bbf2h: 1D B8 22 B8 28 B8 3D B8 46 B8 56 B8 5A B8 5F B8
0000bc02h: 64 B8 6A B8 6F B8 76 B8 7D B8 86 B8 8D B8 9A B8
0000bc12h: A0 B8 A5 B8 AA B8 BA B8 BD B8 C0 B8 C4 B8 D4 B8
0000bc22h: DE B8 F3 B8 00 B9 09 B9 0D B9 12 B9 15 B9 18 B9
0000bc32h: 1E B9 23 B9 26 B9 2B B9
As each entry is 16 bits (2bytes), fourth structure’s pointer would be located at 3*2 = 6 offset, BBF8. The memory pointer points NES address $B83D.
This way we can direct access any structure we want. Anyway, this is not always necessary. If you want to make a level editor, you can just go through all structure data parsing each structure. This is actually how I made my Kid Icarus editor.
Level decompression is the process of generating and showing real level on screen. To so so, the game has to go trough level data, that points to screen data, which holds a list of constitutive structures, each of which is made of macros, consisting in 4 tiles.
This task is split up in several stages. A) level and screen data is processed, to generate 2 real screens, and storing them in screen buffers. These buffers are $500-$5DF and $600-$6DF in RAM. Each of them stores one full screen, so two full screens are always avaiable there. When level begins, screen buffers contain the first two screens. When Pit goes on, and data from third screen is needed, one of the screen buffers is replaced, so new screen can be decompressed there.
If you open an emulator with memory viewer, and go to $500 and $600 offsets, you’ll be able to see the level appearance Each byte represents one macro. And the byte value is the macro value itself.
B) Data from screen buffers are retrieved when needed. In vertical levels, 2 macro rows are processed at once. As screen buffers contains the macro values, this stage requieres processing macros to generate tiles that would go on screen. This tile values are stored in a RAM table located at $483.
C) The table at $483 is finally copied to Name Table in VRAM, to refresh screen.
Level decompression routines I discovered are stored in banks 2 and 3. But inside each bank, there are similar routines that seem to do nearly the same thing. This is because they are adapted for specific world’s needs. For example, for bank 2, some routines apply to World 1 (vertical levels), and the others apply to World 2 (horizontal levels).
I will have to illustrate this or no one else in the world will understand it, I think, and so, I’d be loosing some great time here :-) Nearly half of bank 2 contains routines used in World1, while the other half, in World2.
Bank 2 layout
World 1 code
World 2 code
*The above addresses are bank 2 offset where code starts.
Each of the above blocks contain their own decompression routines. Basically, there’re a 3 key routines in each block, primarily constituted by a decompression routine to screen buffers ($500 and $600), decompression routines from screen buffers to RAM table (offset $483), and another one that copies decompressed data to Name Table.
A 34 bytes long RAM area at offset $0481 is used for decompression tasks. Some more RAM locations are used too, as temporary storage, from $0-$6, for example, as well as $62, and probably many others.
Level data from screen buffers is decompressed into $0483-$04C2.
The previous bytes hold the VRAM address where decompressed data will be copied:
High byte of VRAM address is stored $0482.
Low byte of VRAM address is stored in $0481.
Full decompression is not simple process at all. All routines involved are quite hard to understand. So I’ll not list them here, with some exceptions. One of the main reasons I won’t list them all is that I don’t understand them all :-)
The decompression process consists, as I said before, of:
- Phase 1: Screen decompression .
Decompress screen data to screen buffers.
- Phase 2: Tile decompression.
Decompress screen buffers to $483 RAM table
- Phase 3: Name Table refresh.
Copy decompressed data from $483 to Name Table in VRAM.
Phase 3 routine is, by far, the most easy to understand. Phase 2 routines are more complex, but phase 1 routines, which I’ve been recently working on, are driving me crazy. Just to much chaos. I get the idea, but so far I don’t have all code detected and analysed.
Phase 3 routine for World 1 is:
lda $482 ; high byte
sta $2006 ; VRAM address
lda $481 ; low byte
sta $2006 ; VRAM addr
lda $483,y ; write to VRAM
sta $2007 ; #64 bytes
Main phase 2 routine is:
;This routine at adress $95D4 loads bytes to be copied
;to VRAM, so they are correctly displayed on screen
;VRAM is not written yet
;This routine loads these bytes on a special memory area,
;which starts at address $483
;VRAM will be written on a later phase (phase 3)
ldy #$f ;we are going to iterate 16 times
;each iteration, a 2x2 tile macro is decompressed and written
pha ;save Y on stack, holding iterations left
;Memory addresses $06-$15 hold 16 bytes, which will be
;decompressed into 4 tiles (2x2 tile macro)
;The data at $06-$15 was loaded in phase 1.
lda ($6),y ;A = byte pointed by address (Y+6)
;21 for first iteration (15+6)
;20 for second iteration (14+6)
;and so on... until 6 (0+6)
;The next routine at address $9602 returns after setting
;address bytes $02-$05 to those to write to table at $483.
;Each of these 4 bytes match one of the 2x2 tiles which make
;the current macro
jsr lbl_9602 ;this routine extract the tiles based upon
;byte loaded in A by previous "lda ($6),y"
pla ;restore Y value from the stack
pha ;and save it again in the stack
asl a ;offset to write in table $483 = A * 2
;30 for first iteration (15*2)
;28 for second iteration (14*2)
;and so on... until 0
tay ;save the offset in register Y
;now transfer bytes at $2 and $3 to table, at the right place
;which is pointed by the offset stored in Y
lda $2 ;load byte at address $2 (set by lbl_9602)
sta $483,y ;store decompressed byte in table
iny ;point to next byte inside the table
lda $3 ;load byte at $3 (also set by lbl_9602)
sta $483,y ;store it too
;add 31 bytes to the table offset, so next bytes are stored
;in the next line (each line is 32 bytes width)
;this way, bytes at $4 and $5 are written so they appear
;(on the screen) right under those where $2-$3 were written
;for example, imagine [X] is a tile on screen:
;begin of line - [X]...[X][X][X]...[X] - end of line
;begin of line - [X]...[X][X][X]...[X] - end of line
;data is loaded in square blocks, 2x2 tile macros
tya ;move offset to A, so we can add
clc ;clear carry
adc #$1f ;add 31 bytes
tay ;and put it back in register Y
;Now we are ready to write bytes corresponding to second row
;of the 2x2 tile macro.
;Restore Y value, holding the iterations left to be done.
;As the original value was 15, this loop will repeat 16 times.
dey ;substract one, and check weather
;to loop again or we are finished
bpl lbl_95d6 ;repeat (16 times) until Y >= 0
;or Y=0 before "dey" instruction
; When Y = -1 ($FF) the branch will not be taken: we're done
rts ;return from subroutine
This routine is at NES address $951D, when bank 2 is mapped.
The routine for phase 2 starts at NES address $95D4, when bank 2 is mapped.
One of the routines for phase 3 starts at $9722, when bank 2 is mapped.
Some notes on fortresses...
There's another decompression routine in bank 5.
I suppose it must be used in fortresses, as there's only one block of routines.
And it's likely adapted for levels where no scroll exists, as all fortress action develops in static rooms with no scroll.
Items are stored in a variable length table.
For World 1, it starts at ROM offset $1A757.
The table’s format is the following:
First byte hold number of (items - 1) in the World.
Then come 4 bytes blocks, each one representing one item.
Byte 0: (Level – 1).
Byte 1: (Screen – 1).
Byte 2: Y/X Position (high nibble holds Y, low nibble holds X).
01: Holy water.
World’s 1 Item table is:
0001a757h: 05 00 03 5C 00 00 07 A8 01 01 01 57 01 01 04 5B 01
0001a768h: 02 0B AA 00 02 05 09 00
As you can see, first byte tells us that there will be 6 items (5 + 1).
First item to come is at level is in level 1, located in screen 4, at position $5C, and it is a harp.
As usually done in Kid Icarus, position $5C is at coordinated:
X = $C = 13
Y = $5 = 5
And they refer to 16x16 pixels locations (same size as macros).
There’re 16 locations width, and 15 locations height.
Introduction to Kid Icarus enemies
Enemies are stored in a quite unobvious way, much like all the rest of the game’s implementation. God bless assembler programmers. Usually I think how the hell can something be done?? Well, anyway....
There’re 4 types of enemies.
Types 2 and 4 let you give initial positions for the enemy, and there can only be one enemy of the same type on the screen.
Types 1 and 3 don’t let you set an initial position for the enemies. You just bound the enemies to a certain screen. When Pit enters that screen, enemies of these types are generated.
It seems weird at the beginning, but types 2 and 4 are reserved exclusively for Reapers. For some time I thought Snowman was also a member of these types, but it isn’t.
There’s no way to know if the rest of enemies are type 1 or type 3, BUT they are always the same type, and I found out by inspecting the code, as well as doing some ROM corruptions. Most of enemies, though, are type 1 enemies.
For World 1:
Type 1 enemies are Monoeye, Commyloose and Nettler.
Type 3 enemies are Shemun and McGoo.
All Reapers that appear in the game can be type 1 or type 3. There’s no difference really. But there’re cannot be more than one Reaper of same type in the screen. So, this system just lets you show 2 Reapers at the same moment.
Enemy storage format
The way enemies are stores, once you understand enemy types, is not very complicated.
The Enemy tables for each World, store all information needed to place enemies.
There’re 4 enemy tables, sequentially stored.
First table is for type 1 enemies.
Second table is for type 2 enemies.
Third table is for type 3 enemies.
Fourth table is for type 4 enemies.
Each table hold info for the 3 levels of the World. And level 1 data is not separated in anyway from level 2 data. The starting position of each level data is needed.
That’s why each Enemy table starts with 3 memory pointers (6 bytes).
First pointer points to NES memory address where level 1 data starts.
Second pointer points to NES memory address where level 2 data starts.
Third pointer points to NES memory address where level 3 data starts.
Once we are at the beginning of the level data, consecutive byte, holds enemy to be placed in each consecutive level screen.
For this to become clearer, let’s have a look at World 1, Enemy table 1. this table will tell us where to place type 1 enemies throughout the 3 levels of the World.
0001a620h: D6 7C E2 7C F0 7C 00 00 00 08 08 00 08 00 00 0D
0001a630h: 05 00 00 08 08 08 00 00 0D 00 08 08 00 00 0A 0A
0001a640h: 00 05 00 00 08 00 08 08 00 0A 0A 0A 0A 0A 0A 0A
0001a650h: 00 0A 00 0D 08
First we have the 3 memory pointers. Bolded bytes represent the first byte of each level data. Each pointer points to NES address where these three bytes are. The byte after each bold byte is the content of second screen of that level. And so on. Byte 00 means no enemy at that screen. The other bytes are enemy code bytes, telling the game to place that particular enemy in that screen.
Enemy codes for World 1 type 1 enemies are:
Monoeye (08h), Commyloose (0Ah) and Nettler (05h).
The 0Dh byte tells the game that there’ll be a reaper in that screen. But it does not seem responsible to create the Reaper itself. Just somekind of reservation in NES sprite RAM (more on this later).
This way we see:
In level 1, there’re Monoeyes at screen 4 and 5. This is where the harp is.
In level 2, there’re Monoeyes in screen 2, 3 and 4.
In level 3, there’re Netlers in screen 2.
Table 3 works the same way, for type 3 enemies. Just need to know the codes (don’t worry, I’ll list them later).
Table 2 and 4 are different format. The don’t hold an enemy code. As they always store Reapers, each byte sets the starting position of the Repaer inside the screen. This byte should be interpreted as position byte in Item tables, previously seen.
Fortress’ specific enemies, like Ganawmedes and Kobils in World 1, are not treated the same way, so they’re not in one of these types. They are stored, generated and placed in a different fashion, that will be discussed when I go in detail into Fortresses.
These are stores in the enemy property tables. This table is 48 bytes long, and it’s divided in 3 subtables of 16 bytes, each containing a specific information. All enemies have an code that is used in game engine to identify them. We’ve already discussed this in previous section. This code will serve as an offset in these property tables.
World 1’s table is:
0001a770h: 01 01 01 01 01 02 01 02 01 04 02 0A 08 0A 02 00
0001a780h: 01 01 01 03 05 03 01 02 03 04 03 0A 08 05 02 00
0001a790h: 01 02 01 01 02 01 01 02 01 04 01 0A 08 02 02 00
Offset 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Each line is a subtable.
First subtable is used to store enemy’s health points, that is, how many arrows are needed to kill it. Just remember that is your strength is 3, each arrow counts as 3.
Second subtable is used to store enemy’s score/heart reward. The meaning of these bytes seem to be:
00-02: 100 points. 1 heart.
03-04: 300 points. 5 hearts.
05-0F: 500 points. 10 hearts.
10-1F: 1000 points. 10 hearts.
20-3F: 2000 points. 10 hearts.
40-7F: 4000 points. 10 hearts.
80-FF: 8000 points. 10 hearts.
Third subtable is used to store enemy’s hit power, that is, how many hit points does Pit get when colliding with the enemy.
Consider Monoeye. Its code is 08h. So, we use an 8 offset to look up subtables. First subtable tells us that Monoeye has one single health point, so a single arrow is needed to overcome it. Second subtable tells us that it will grant 300 points and half big hearth when killed. Third subtable tells us that Pit will suffer from one hit point when touched by it.
Certain enemies like Shemun seem to be slightly different. It always gives one hearth no matter what second subtable holds, and it always has 1 health point. The rest reamins the same.
Mobile platforms are stored in a fixed area, with FF bytes to fill unused positions. The format in which they’re stored is:
Then come 4 bytes blocks, each one representing one item.
Byte 0: (Level – 1).
Byte 1: (Screen – 1).
Byte 2: Starting position (high nibble holds Y, low nibble holds initial direction?)
Byte 3: 0x00
They go into Object Table at offset $7C0,...,...,...
I wrote this section very fast, so there could be some mistakes. My very last discovery was about door positioning information. For long time, I was able to change level appearance, and later, even screens or enemies, but how door position was stored in the ROM was a mystery for me, and so, even being able to change a whole level, not being able to change door position was a very limiting factor.
Some time ago, I finished up with all level data , meaning that I already knew what ALL bytes from level data sections of the ROM meant. And door info was not located there. I felt a bit disappointed. I had no idea of where this data could be located.
On 12th of July, after some days trying to find out a possible strategy to look for the missing data, I found a memory location that seemed really interesting. ROM offset $1EFBD. How? Well, my strategy was to use a debugger, to see when the game updated the screen buffers, at locations where I knew there was a door. This way, I discovered room codes (more on this next). From this point, disassembling code for some hours, I got the ROM offset, along with huge chunks of data, that could lead me to the answer, but it was overwhelming. It was very hard to disassemble all of it. So much code, so much jumps, so much temporary RAM locations used... Fortunately, quickly enough, I realized the ROM offset was part of game’s first level, first door data. Great!
By the way, I did this by ROM corruption techniques. Direct disassembling way would have been a job for many days, being optimistic.
In the ROM map I presented before, at the end, you can see a “Door Data” section. Well, there it is. The meaning of this table is:
Byte 0: Level - 1
Byte 1: Screen - 1
Byte 2: Y/X Position
Byte 3: Room code
Finally, here it is. Different worlds are separated by FF FF.
0001efd9h: 00 01 AE 24 00 06 93 22 00 07 AB 20 01 01 71 22
0001efe9h: 01 03 D8 26 01 04 99 22 01 06 B1 25 01 0C 2C 24
0001eff9h: 02 02 8E 22 02 04 43 25 02 06 27 20 02 0D 01 26
0001f009h: 02 11 46 23 FF FF 00 01 AF 25 00 03 AA 27 00 05
0001f019h: AD 22 00 0A 8C 26 00 0D 76 20 00 11 76 24 00 14
0001f029h: A8 25 00 17 1E 23 00 19 AA 24 01 00 A5 20 01 01
0001f039h: A4 22 01 02 A4 22 01 03 A4 24 01 04 6A 27 01 07
0001f049h: 89 22 01 10 9A 26 01 17 9C 22 01 1A 9C 26 02 07
0001f059h: A8 20 02 0A AB 23 02 0F AE 25 02 13 9F 22 02 15
0001f069h: AE 26 FF FF 00 00 BE 27 00 09 03 24 01 03 16 23
0001f079h: 01 05 DC 25 01 09 88 26 01 0C 49 27 02 05 5E 26
0001f089h: 02 0F B2 25 FF FF FF FF
At the beginning you see a door at level 1, screen 2, at macro position X=$E and Y=$A, and leading to room $24. As I’ll just say, this code correspond to an empty room (maybe the one where god grants you with a new arrow?) After the first FF FF, come second world (overworld) door data. The first 4 bytes place a door at level 1, screen 2, with code 25 (a shop)
I’m not sure of all info about this, as I was not going to add a section about doors in this document, as it was a very recent discovery, and had no much time to work on it. But here it goes. My most up to date info for room codes is:
$22: Nose enemies room
$23: Training room
$24: Empty room
$25: Shop room
$26: Black Market room
$27: Empty room
David Senabre Albujer. 2006