Difference between revisions of "SNES format"
(3 intermediate revisions by one other user not shown) | |||
Line 1: | Line 1: | ||
− | This file describes the format of data in | + | This file describes the format of data in Prince of Persia 1 for the SNES. |
This document is far from complete. I will continuously add information to it. | This document is far from complete. I will continuously add information to it. | ||
+ | |||
+ | Documentation about the SNES hardware can be found on this page: https://problemkaputt.de/fullsnes.htm | ||
== General concepts == | == General concepts == | ||
Line 14: | Line 16: | ||
{| class="wikitable" | {| class="wikitable" | ||
− | |||
! Address !! Count !! Contents | ! Address !! Count !! Contents | ||
|- | |- | ||
Line 21: | Line 22: | ||
| 0x78000 || 1485 || Sprites | | 0x78000 || 1485 || Sprites | ||
|- | |- | ||
− | | 0xE8000 || 36 or 37 || Sounds, | + | | 0xE8000 || 36 or 37 || Sounds, Music |
|} | |} | ||
A resource table is an array of addresses. Each address is in the following form: | A resource table is an array of addresses. Each address is in the following form: | ||
− | A word telling the offset and a byte telling the | + | A word telling the offset and a byte telling the bank (also called "segment"). |
− | + | Banks are in the range 0x00..0x1F, offsets are in the range 0x8000..0xFFFF. | |
− | address= | + | address=bank*0x8000+offset-0x8000 |
− | e.g. 0x14 0x81 0x04 means offset=0x8114, | + | e.g. 0x14 0x81 0x04 means offset=0x8114, bank=0x04, address=0x04*0x8000+0x0114=0x20114 |
Note that some ROM images have a 0x200 byte header. | Note that some ROM images have a 0x200 byte header. | ||
Line 61: | Line 62: | ||
71: princess room screen | 71: princess room screen | ||
72: princess room arches screen | 72: princess room arches screen | ||
− | 73: torch tiles (16 uncompressed 4bpp tiles) | + | 73: princess room torch tiles (16 uncompressed 4bpp tiles) |
74: ending font (stored in the same format as level background graphics) | 74: ending font (stored in the same format as level background graphics) | ||
75: Masaya logo screen | 75: Masaya logo screen | ||
− | 76: stars screen (big) | + | 76: Masaya stars screen (big) |
77: torture screen (big) | 77: torture screen (big) | ||
78: garden screen (big) | 78: garden screen (big) | ||
Line 84: | Line 85: | ||
=== Contents of the second resource table: (0x78000) === | === Contents of the second resource table: (0x78000) === | ||
+ | <pre> | ||
0000..1248: regular sprites | 0000..1248: regular sprites | ||
− | |||
1249..1394: sprites that are put into the background (traps, buttons, doors), 48*64 pixels | 1249..1394: sprites that are put into the background (traps, buttons, doors), 48*64 pixels | ||
− | |||
1395..1484: masks, 48*64 pixels | 1395..1484: masks, 48*64 pixels | ||
+ | </pre> | ||
=== Contents of the third resource table: (0xE8000) === | === Contents of the third resource table: (0xE8000) === | ||
− | ( | + | <pre> |
+ | 00: sound samples (for sound effects) | ||
+ | 01: sound effects (in the same format as music) | ||
+ | 02: sound samples (for music) | ||
+ | 03..36: music | ||
+ | </pre> | ||
+ | |||
+ | Note: In the Japanese version, resource 03 (Konami jingle) is missing, and resources 04..36 are 03..35 instead. | ||
== Resource formats == | == Resource formats == | ||
Line 101: | Line 109: | ||
0bbbbbgggggrrrrr | 0bbbbbgggggrrrrr | ||
+ | |||
+ | ==== Palette entries ==== | ||
+ | |||
+ | The SNES has 256 palette entries, numbered from 0x00 to 0xFF. | ||
+ | |||
+ | It is useful to think of them as a 16x16 matrix. Row 0 is 0x00..0x0F, row 1 is 0x10..0x1F, and so on. | ||
+ | |||
+ | Usage of the palette entries when playing: | ||
+ | <pre> | ||
+ | Background: | ||
+ | 0x00..0x1B: Loaded from the palette of the level type. | ||
+ | 0x00..0x03: palette 0 of layer 1 | ||
+ | 0x04..0x07: palette 1 of layer 1 | ||
+ | 0x08..0x0B: palette 2 of layer 1 | ||
+ | 0x0C..0x0F: palette 3 of layer 1 | ||
+ | 0x10..0x13: palette 4 of layer 1 | ||
+ | 0x14..0x17: palette 5 of layer 1 | ||
+ | 0x18..0x1B: palette 6 of layer 1 | ||
+ | 0x1C..0x1F: Palette of texts. | ||
+ | 0x20..0x6F: Loaded from the palette of the level type. | ||
+ | 0x20..0x2F: palette 0 of layer 3 | ||
+ | 0x30..0x3F: palette 1 of layer 3 | ||
+ | 0x40..0x4F: palette 2 of layer 3 | ||
+ | 0x50..0x5F: palette 3 of layer 3 (not used) | ||
+ | 0x60..0x6F: palette of layer 2 and background sprites | ||
+ | 0x70..0x7F: Hitpoints. | ||
+ | Sprites: | ||
+ | 0x80..0x8F: kid | ||
+ | 0x90..0x9F: sword | ||
+ | 0xA0..0xAF: flame | ||
+ | 0xB0..0xBF: potion | ||
+ | 0xC0..0xCF: green kid (slow fall) | ||
+ | 0xD0..0xDF: guard/shadow | ||
+ | 0xE0..0xEF: not used | ||
+ | 0xF0..0xFF: not used | ||
+ | </pre> | ||
+ | Colors 0xC0..0xFF can be made transparent by setting bit 4 of port 2131h. | ||
+ | This is used to make the shadow transparent. | ||
+ | |||
+ | ==== Contents of the palette resources ==== | ||
+ | The numbers in [brackets] mean which row of palette entries are they loaded to. | ||
+ | <pre> | ||
+ | res 68: | ||
+ | 0: kid [8] | ||
+ | 1: red kid | ||
+ | 2: slowfall kid [12] | ||
+ | 3: princess [9] | ||
+ | 4: skeleton | ||
+ | 5: brown skeleton | ||
+ | 6: gold skeleton | ||
+ | 7: skeleton on level 10 | ||
+ | 8: amazon | ||
+ | 9: fat | ||
+ | 10: shadow | ||
+ | 11: yellow guard (torture [9]) | ||
+ | 12: green guard | ||
+ | 13: blue guard | ||
+ | 14: red guard | ||
+ | 15: purple guard | ||
+ | 16: blueface guard | ||
+ | 17: red knight | ||
+ | 18: blue knight | ||
+ | 19: monster | ||
+ | 20: Jaffar (walk [8]) (princess's room [10]) | ||
+ | 21: mouse (princess's room [11]) | ||
+ | 22: sword [9] | ||
+ | 23: prince fire, flame, hardhit [10] (walk [9]) (princess's room [12]) (torture [10]) | ||
+ | 24: potion [11] | ||
+ | 25: elephant? / skull | ||
+ | 26: shooting star | ||
+ | |||
+ | res 69: | ||
+ | 0: hitpoint [7] | ||
+ | 1: princess room [4] top part | ||
+ | 2: princess room [5] front arches | ||
+ | 3: princess room [6] where the wall meets the floor | ||
+ | 4: princess room [7] floor | ||
+ | 5: princess room hourglass [13] | ||
+ | 6: Masaya logo, pegasus [4,8] | ||
+ | 7: Masaya logo stars? [0] | ||
+ | 8: title [4] | ||
+ | 9: game name [5] | ||
+ | 10 | ||
+ | 11 | ||
+ | 12: main menu background [4] | ||
+ | 13: where Jaffar walks [1] front curtain | ||
+ | 14: where Jaffar walks [2] floor/column/statue/flag gradient | ||
+ | 15: where Jaffar walks [3] back layer | ||
+ | 16: ending brown [4] | ||
+ | 17: ending sundown [4] | ||
+ | 18 | ||
+ | 19: torture [1] front layer | ||
+ | 20: torture [2] back layer | ||
+ | 21: torture [3] guards | ||
+ | 22: garden [1] back and front layer + fountain water [11 before guards, 10 with guards] | ||
+ | 23: garden [2] back layer guard | ||
+ | 24: garden [3] front layer guard | ||
+ | 25: garden [10 before guards] | ||
+ | (26..32: Europe/USA versions only) | ||
+ | 26: Konami logo [0] letters | ||
+ | 27: Konami logo [1] logo top left | ||
+ | 28: Konami logo [2] logo bottom right | ||
+ | 29: Konami logo [3] line and stars | ||
+ | 30: Konami logo [4] grey letters | ||
+ | 31: Konami logo [5] yellow logo top left | ||
+ | 32: Konami logo [6] orange logo bottom right | ||
+ | </pre> | ||
=== Tiles === | === Tiles === | ||
+ | |||
+ | (See also the section "SNES VRAM DATA" in the SNES Hardware specs) | ||
The SNES uses planar tiles, which can be 2- or 4-bit. | The SNES uses planar tiles, which can be 2- or 4-bit. | ||
Line 362: | Line 479: | ||
else: | else: | ||
read 8 bytes into plane P | read 8 bytes into plane P | ||
− | + | ||
0x18, 0x19= | 0x18, 0x19= | ||
Line 379: | Line 496: | ||
(needs better description) | (needs better description) | ||
+ | |||
+ | === Maps === | ||
+ | |||
+ | (See also the section "SNES VRAM DATA" in the SNES Hardware specs) | ||
+ | |||
+ | Screens and environments contain maps. These tell how should tiles be arranged. | ||
+ | |||
+ | Each entry in a map is a 2-byte integer. | ||
+ | |||
+ | {| class="wikitable" | ||
+ | ! bits !! meaning | ||
+ | |- | ||
+ | | 0..9 || Index of tile. | ||
+ | |- | ||
+ | | 10..12 || Index of palette. Multiply it with the number of colors in a tile to get a palette offset. | ||
+ | |- | ||
+ | | 13 || Priority: 0 means that the tile appears behind the sprites, 1 means in front of the sprites. | ||
+ | |- | ||
+ | | 14 || Xflip: Flips the tile so that left and right switches place. | ||
+ | |- | ||
+ | | 15 || Yflip: Flips the tile so that top and bottom switches place. | ||
+ | |} | ||
=== Environments === | === Environments === | ||
{| class="wikitable" | {| class="wikitable" | ||
− | ! type !! block width !! block height !! bits per pixel !! number of blocks !! number of tiles !! B in block mapping !! B in tile mapping | + | ! type !! block width !! block height !! bits per pixel !! number of blocks !! number of tiles !! B in block mapping !! B in tile mapping !! palette offset |
|- | |- | ||
− | | 1. background || 3 || 8 || 2 || 256 || 512 || 8 || 9 | + | | 1. background || 3 || 8 || 2 || 256 || 512 || 8 || 9 || 0x00 |
|- | |- | ||
− | | 2. blocks background || 6 || 8 || 4 || 256 || 256 || 8 || 8 | + | | 2. blocks background || 6 || 8 || 4 || 256 || 256 || 8 || 8 || 0x60 |
|- | |- | ||
− | | 3. blocks foreground || 3 || 8 || 4 || 256 || 256 || 8 || 8 | + | | 3. blocks foreground || 3 || 8 || 4 || 256 || 256 || 8 || 8 || 0x20 |
|} | |} | ||
Line 472: | Line 611: | ||
{| class="wikitable" | {| class="wikitable" | ||
− | |||
! Address !! Size !! Contents | ! Address !! Size !! Contents | ||
|- | |- | ||
Line 670: | Line 808: | ||
255 means that no room is in the given direction. | 255 means that no room is in the given direction. | ||
− | For the "above" link, 254 means the same, plus that the 4-pixel part of the room above (which would have walls) is not shown. | + | For the "above" link, 254 means the same as 255, plus that the 4-pixel part of the room above (which would have walls) is not shown. |
This is used for special "outdoor" rooms. | This is used for special "outdoor" rooms. | ||
Line 682: | Line 820: | ||
'''Guard position in room''': row*10+column (0..29), or 0xFF if there is no guard in this room. | '''Guard position in room''': row*10+column (0..29), or 0xFF if there is no guard in this room. | ||
− | '''Guard type and direction''': The bottom 7 bits specify the guard type, the top bit | + | '''Guard type and direction''': The bottom 7 bits specify the guard type, the top bit specifies the direction: 1=left, 0=right. |
{| class="wikitable" | {| class="wikitable" | ||
Line 695: | Line 833: | ||
| 0x05 || golden skeleton | | 0x05 || golden skeleton | ||
|- | |- | ||
− | | 0x06 || | + | | 0x06 || amazon |
|- | |- | ||
| 0x07 || fat | | 0x07 || fat | ||
Line 829: | Line 967: | ||
(needs better description) | (needs better description) | ||
− | === | + | === Sound samples === |
+ | |||
+ | The format of sound sample resources: | ||
+ | <pre> | ||
+ | 1 byte: number of sounds | ||
+ | for each sample: | ||
+ | 2 byte: length of part 1 in bytes | ||
+ | 2 byte: length of part 2 in bytes | ||
+ | for each sample: | ||
+ | x byte: part 1 | ||
+ | y byte: part 2 | ||
+ | </pre> | ||
+ | Part 2 will be looped. | ||
+ | |||
+ | Samples are compressed with BRR (Bit Rate Reduction). | ||
− | ( | + | Information about BRR (and the SPC in general) can be found here: http://emureview.ztnet.com/developerscorner/SoundCPU/spc.htm |
+ | |||
+ | === Music === | ||
+ | |||
+ | Music resources begin with the following '''header''': | ||
+ | |||
+ | <pre> | ||
+ | 2 bytes: Length of data. | ||
+ | Note: This length is excluded from offsets, i.e. all offsets are counted from the next byte. | ||
+ | 2 bytes: Offset to instrument data. | ||
+ | 1 byte: Flags to enable channels. (Bits 0..7 correspond to channels 1..8.) Should be 0xFF to enable all channels. | ||
+ | For each channel (1..8): | ||
+ | 2 bytes: Offset to the sequence of this channel. | ||
+ | </pre> | ||
+ | |||
+ | Structure of each channel's '''sequence''': | ||
+ | <pre> | ||
+ | For each item: | ||
+ | 1 byte: Repeat count (0x01..0xFD). | ||
+ | 2 byte: Offset of command string. | ||
+ | (The items are played in sequence.) | ||
+ | 1 byte: end marker | ||
+ | 0xFF = stop (for "event" music, for example potions) (0x00 seems to mean the same?) | ||
+ | 0xFE = loop (for background music) | ||
+ | |||
+ | 0xFE is followed by a 2 byte offset (into the sequence) that tells where to restart when looping. | ||
+ | It usually points to the *second* item of the sequence, since the first item contains only settings and no notes. | ||
+ | (So 0xFE is basically a GOTO.) | ||
+ | |||
+ | In the "sound effects" resource this works a bit differently: | ||
+ | * The sequences of channels 1-7 are empty, they contain only a "stop". | ||
+ | * The sequence of channel 8 contains one item for each sound effect. | ||
+ | * It seems that the repeat counts are ignored, or maybe they are used for something else? | ||
+ | </pre> | ||
+ | |||
+ | '''Command strings''' are ASCII strings terminated with a 0x00 byte. | ||
+ | |||
+ | The possible commands are: | ||
+ | {| class="wikitable" | ||
+ | ! rowspan=2 | command !! colspan=4 | parameter !! rowspan=2 | scope !! rowspan=2 | meaning | ||
+ | |- | ||
+ | ! type !! range !! initial<br>(on startup) !! default<br>(if omitted) | ||
+ | |- | ||
+ | | T || n || 0-255 || 0 || 0 || global || set tempo, i.e. set unit of note length to n * 0.0002 seconds | ||
+ | |- | ||
+ | | V || n || 0-15 || 13 || 0 || channel || set volume | ||
+ | |- | ||
+ | | ^ || || || || || channel || increase volume (wraps around from 15 to 0) | ||
+ | |- | ||
+ | | _ || || || || || channel || decrease volume (stops at 0) | ||
+ | |- | ||
+ | | @ || n || 0-255 || 128 || 0 || channel || set instrument | ||
+ | |- | ||
+ | | O || n || 0-7 || 4 || 0 || channel || set octave | ||
+ | |- | ||
+ | | + || || || || || channel || up one octave (wraps around from 7 to 0) | ||
+ | |- | ||
+ | | - || || || || || channel || down one octave (wraps around from 0 to 7) | ||
+ | |- | ||
+ | | C,D,E,F,G,A,B || n || 0-255 || 16 || previous || channel || play note (n = set length of this note and next ones, relative to Tn) | ||
+ | |- | ||
+ | | # || || || || || channel || make next note sharp | ||
+ | |- | ||
+ | | R || n || 0-255 || 16 || previous || channel || play rest (n = same as for CDEFGAB) | ||
+ | |- | ||
+ | | = || n || 0-127 || 64 || 64 || channel || set balance | ||
+ | |- | ||
+ | | < || || || || || channel || same as =127 (balance to left) | ||
+ | |- | ||
+ | | > || || || || || channel || same as =0 (balance to right) | ||
+ | |- | ||
+ | | [ || || || || || channel || start merge? (disable Decay?) | ||
+ | |- | ||
+ | | & || || || || || channel || separate merge? (disable Attack?) | ||
+ | |- | ||
+ | | ] || || || || || channel || end merge? (enable Attack and Decay?) | ||
+ | |- | ||
+ | | ) || n || 0-255 || 0 || || channel || set detune? (signed) | ||
+ | |- | ||
+ | | $ || n || 0-3 || || 0 || global || set all echo coefficients to a preset | ||
+ | |- | ||
+ | | $ || i,n || 0-7, 0-255 || || 0, 0 || global || set an echo coefficient (signed) | ||
+ | |- | ||
+ | | Q || n || 0-15 || 0 || 0 || channel || set fill ratio: note will fill n/16 part of the time given for it (0 means 16) | ||
+ | |- | ||
+ | | P || n || 0-255 || 0 || 0 || channel || ? | ||
+ | |- | ||
+ | | H || n || 0-255 || 0 || 0 || global || echo volume | ||
+ | |- | ||
+ | | I || n || 0-255 || 0 || 0 || global || echo feedback volume | ||
+ | |- | ||
+ | | J || n || 0-255 || 4 || 0 || global || echo delay | ||
+ | |- | ||
+ | | K || n || 0-127 || 64 || 0 || global || echo volume | ||
+ | |- | ||
+ | | L || n || 0-255 || 0 || 0 || channel || move the balance left-right in a wave | ||
+ | |- | ||
+ | | ! || || || || || global || ? | ||
+ | |- | ||
+ | | % || || || || || channel || skip to next % (i.e. comment?) | ||
+ | |- | ||
+ | | M || n || 0 or 1 || 0 || 0 || global || pitch modulation enable? | ||
+ | |- | ||
+ | | N || n || 0-31 or 255 || || 255 || global || noise? (255=off) | ||
+ | |} | ||
+ | |||
+ | '''Instruments''' are described using 11 bytes each: | ||
+ | <pre> | ||
+ | 1 byte: index of sound sample (written to register x4h = VxSRCN) | ||
+ | 1 byte: ADSR lower 8 bits (written to register x5h = VxADSR1, OR'ed with 0x80 to select ADSR instead of GAIN) | ||
+ | 1 byte: ADSR upper 8 bits (written to register x6h = VxADSR2) | ||
+ | 1 byte: A numerator or 0 | ||
+ | 1 byte: A denominator or 0 | ||
+ | 1 byte: B numerator or 0 | ||
+ | 1 byte: B denominator or 0 | ||
+ | 1 byte: flags? (written to RAM $27) | ||
+ | 1 byte: Vibrato Speed (?) (written to RAM $A0+X) | ||
+ | 2 byte: pitch correction? | ||
+ | </pre> | ||
+ | For details about ADSR see: https://problemkaputt.de/fullsnes.htm#snesapudspadsrgainenvelope |
Latest revision as of 11:45, 3 March 2019
This file describes the format of data in Prince of Persia 1 for the SNES.
This document is far from complete. I will continuously add information to it.
Documentation about the SNES hardware can be found on this page: https://problemkaputt.de/fullsnes.htm
General concepts
Byte order: Little-endian byte order is used, unless noted otherwise.
Numbering of bits: The least significant bit of a byte is bit 0, the most significant bit of a byte is bit 7.
The resource tables
There are 3 resource tables in the ROM, at the following addresses:
Address | Count | Contents |
---|---|---|
0x20000 | 91 or 92 | Levels, Graphics |
0x78000 | 1485 | Sprites |
0xE8000 | 36 or 37 | Sounds, Music |
A resource table is an array of addresses. Each address is in the following form: A word telling the offset and a byte telling the bank (also called "segment"). Banks are in the range 0x00..0x1F, offsets are in the range 0x8000..0xFFFF. address=bank*0x8000+offset-0x8000
e.g. 0x14 0x81 0x04 means offset=0x8114, bank=0x04, address=0x04*0x8000+0x0114=0x20114
Note that some ROM images have a 0x200 byte header.
The size or type of the resource is not stored.
Contents of the first resource table: (0x20000)
00: menu/intro font tiles (256 uncompressed 2bpp tiles) 01..40: the 10 environments (graphics for level types) (each type has four resources: background, blocks background, blocks foreground, palette) 0: 01,02,03,04: blue dungeon 1: 05,06,07,08: pink palace 2: 09,10,11,12: grey palace 3: 13,14,15,16: red dungeon 4: 17,18,19,20: green palace 5: 21,22,23,24: brown dungeon 6: 25,26,27,28: red palace 7: 29,30,31,32: level 20 8: 33,34,35,36: Jaffar 9: 37,38,39,40: demo red palace 41..67: the 27 levels: 41..60: level 1-20 61: Jaffar's level 62: demo level 63..67: training 1-5 68: sprite palettes (27*16 colors) 69: screen palettes (33*16 colors) 70: hitpoint tiles (4 uncompressed 4bpp tiles) 71: princess room screen 72: princess room arches screen 73: princess room torch tiles (16 uncompressed 4bpp tiles) 74: ending font (stored in the same format as level background graphics) 75: Masaya logo screen 76: Masaya stars screen (big) 77: torture screen (big) 78: garden screen (big) 79: title screen 80: title things screen 81: pattern screen 82: game name screen 83: main menu background screen 84: where Jaffar walks screen (big) 85: ending: prince at sultan screen 86: ending: prince and princess at window with people screen 87: ending: prince and princess at window with sundown screen 88: ending: sundown screen 89: ending: sundown 2 screen 90: Japanese characters screen 91: Konami logo screen (Europe/USA versions only)
Contents of the second resource table: (0x78000)
0000..1248: regular sprites 1249..1394: sprites that are put into the background (traps, buttons, doors), 48*64 pixels 1395..1484: masks, 48*64 pixels
Contents of the third resource table: (0xE8000)
00: sound samples (for sound effects) 01: sound effects (in the same format as music) 02: sound samples (for music) 03..36: music
Note: In the Japanese version, resource 03 (Konami jingle) is missing, and resources 04..36 are 03..35 instead.
Resource formats
Palettes
The SNES uses 5:5:5 RGB palettes, each entry is a word in the following format:
0bbbbbgggggrrrrr
Palette entries
The SNES has 256 palette entries, numbered from 0x00 to 0xFF.
It is useful to think of them as a 16x16 matrix. Row 0 is 0x00..0x0F, row 1 is 0x10..0x1F, and so on.
Usage of the palette entries when playing:
Background: 0x00..0x1B: Loaded from the palette of the level type. 0x00..0x03: palette 0 of layer 1 0x04..0x07: palette 1 of layer 1 0x08..0x0B: palette 2 of layer 1 0x0C..0x0F: palette 3 of layer 1 0x10..0x13: palette 4 of layer 1 0x14..0x17: palette 5 of layer 1 0x18..0x1B: palette 6 of layer 1 0x1C..0x1F: Palette of texts. 0x20..0x6F: Loaded from the palette of the level type. 0x20..0x2F: palette 0 of layer 3 0x30..0x3F: palette 1 of layer 3 0x40..0x4F: palette 2 of layer 3 0x50..0x5F: palette 3 of layer 3 (not used) 0x60..0x6F: palette of layer 2 and background sprites 0x70..0x7F: Hitpoints. Sprites: 0x80..0x8F: kid 0x90..0x9F: sword 0xA0..0xAF: flame 0xB0..0xBF: potion 0xC0..0xCF: green kid (slow fall) 0xD0..0xDF: guard/shadow 0xE0..0xEF: not used 0xF0..0xFF: not used
Colors 0xC0..0xFF can be made transparent by setting bit 4 of port 2131h. This is used to make the shadow transparent.
Contents of the palette resources
The numbers in [brackets] mean which row of palette entries are they loaded to.
res 68: 0: kid [8] 1: red kid 2: slowfall kid [12] 3: princess [9] 4: skeleton 5: brown skeleton 6: gold skeleton 7: skeleton on level 10 8: amazon 9: fat 10: shadow 11: yellow guard (torture [9]) 12: green guard 13: blue guard 14: red guard 15: purple guard 16: blueface guard 17: red knight 18: blue knight 19: monster 20: Jaffar (walk [8]) (princess's room [10]) 21: mouse (princess's room [11]) 22: sword [9] 23: prince fire, flame, hardhit [10] (walk [9]) (princess's room [12]) (torture [10]) 24: potion [11] 25: elephant? / skull 26: shooting star res 69: 0: hitpoint [7] 1: princess room [4] top part 2: princess room [5] front arches 3: princess room [6] where the wall meets the floor 4: princess room [7] floor 5: princess room hourglass [13] 6: Masaya logo, pegasus [4,8] 7: Masaya logo stars? [0] 8: title [4] 9: game name [5] 10 11 12: main menu background [4] 13: where Jaffar walks [1] front curtain 14: where Jaffar walks [2] floor/column/statue/flag gradient 15: where Jaffar walks [3] back layer 16: ending brown [4] 17: ending sundown [4] 18 19: torture [1] front layer 20: torture [2] back layer 21: torture [3] guards 22: garden [1] back and front layer + fountain water [11 before guards, 10 with guards] 23: garden [2] back layer guard 24: garden [3] front layer guard 25: garden [10 before guards] (26..32: Europe/USA versions only) 26: Konami logo [0] letters 27: Konami logo [1] logo top left 28: Konami logo [2] logo bottom right 29: Konami logo [3] line and stars 30: Konami logo [4] grey letters 31: Konami logo [5] yellow logo top left 32: Konami logo [6] orange logo bottom right
Tiles
(See also the section "SNES VRAM DATA" in the SNES Hardware specs)
The SNES uses planar tiles, which can be 2- or 4-bit.
Tiles are stored in this format:
for B=0..7: byte B of plane 0 byte B of plane 1 if tile is 4bpp: for B=0..7: byte B of plane 2 byte B of plane 3
To get the color of a pixel, from X and Y coordinates:
color = 0 for P=0..bpp-1: read bit 7-X of byte Y of plane P of the tile, and write it into bit P of color
(needs further description)
Compressions used for tiles
2 bit-per-pixel tiles
First, some helper procedures:
procedure fill_masked(P:plane) read 1 byte: mask read 1 byte: X for B=0..7: if bit B of mask is 1 then: put X into byte B of plane P procedure read_remainder(P:plane) for B=0..7: if byte B of plane P is not yet set: read 1 byte into byte B of plane P procedure fill_masked_both_same() read 1 byte: mask read 1 byte: X for B=0..7: if bit B of mask is 1 then: put X into byte B of planes 0 and 1 procedure fill_masked_both_diff() read 1 byte: mask read 2*1 byte: X0,X1 for B=0..7: if bit B of mask is 1 then: put X0 into byte B of plane 0 put X1 into byte B of plane 1 procedure read_remainder_both_same() for B=0..7: if byte B of planes 0 and 1 is not yet set: read 1 byte: X put X into byte B of planes 0 and 1 procedure read_remainder_both_diff() for B=0..7: if byte B of planes 0 and 1 is not yet set: read 1 byte into byte B of plane 0 read 1 byte into byte B of plane 1 procedure fill_masked_flat(D:byte) read 2 byte: mask for B=0..7: for P=0..1: if bit B*2+P of mask is 1 then: put D into byte B of plane P else: read 1 byte into byte B of plane P
The decompression methods:
1 byte: code 0x00= read_remainder_both_diff() * 0x01= read 1 byte: X fill planes 0 and 1 with X 0x02= read 2*1 byte: X0,X1 fill plane 0 with X0 fill plane 1 with X1 0x03= read_remainder_both_same() * 0x04= fill_masked_both_same() read_remainder_both_same() 0x05= fill_masked_both_same() fill_masked_both_same() read_remainder_both_same() 0x06= fill_masked_both_diff() read_remainder_both_diff() 0x07= fill_masked_both_diff() fill_masked_both_diff() read_remainder_both_diff() 0x08= fill_masked_both_diff() fill_masked_both_diff() fill_masked_both_diff() read_remainder_both_diff() 0x09= fill_masked_both_diff() fill_masked_both_diff() fill_masked_both_diff() fill_masked_both_diff() 0x0A= read_remainder(plane 0) * fill_masked(plane 1) fill_masked(plane 1) read_remainder(plane 1) 0x0B= fill_masked(plane 0) read_remainder(plane 0) fill_masked(plane 1) fill_masked(plane 1) read_remainder(plane 1) 0x0C= fill_masked(plane 0) fill_masked(plane 0) read_remainder(plane 0) read_remainder(plane 1) * 0x0D= fill_masked(plane 0) fill_masked(plane 0) read_remainder(plane 0) fill_masked(plane 1) read_remainder(plane 1) 0x0E= fill_masked(plane 0) fill_masked(plane 0) read_remainder(plane 0) fill_masked(plane 1) fill_masked(plane 1) read_remainder(plane 1) 0x0F= (not used) 0x10= fill_masked_flat(0x00) 0x11= fill_masked_flat(0x55) 0x12= fill_masked_flat(0xAA) 0x13= fill_masked_flat(0xFF) 0x14= 2 byte: T 1 byte: mask for B=0..7: if bit B of mask is 1 then: copy bytes B of planes 0,1 from tile T into the current tile else: read byte B of plane 0 read byte B of plane 1 *: in these cases, the "remainder" is the whole tile or plane.
4 bit-per-pixel tiles
First, some helper procedures:
procedure fill_masked_four_diff() read 1 byte: mask read 4*1 byte: X0,X1,X2,X3 for B=0..7: if bit B of mask is 1 then: put X0 into byte B of plane 0 put X1 into byte B of plane 1 put X2 into byte B of plane 2 put X3 into byte B of plane 3 procedure read_remainder_four_diff() for B=0..7: if byte B of planes 0,1,2,3 is not set yet then: read 4*1 byte: X0,X1,X2,X3 put X0 into byte B of plane 0 put X1 into byte B of plane 1 put X2 into byte B of plane 2 put X3 into byte B of plane 3
The decompression methods:
1 byte: code 0x00= for B=0..7: read byte B of plane 0 read byte B of plane 1 for B=0..7: read byte B of plane 2 read byte B of plane 3 0x01= read 1 byte: X fill planes 0,1,2,3 with X 0x02= read 4*1 byte: X0,X1,X2,X3 fill plane 0 with X0 fill plane 1 with X1 fill plane 2 with X2 fill plane 3 with X3 0x03= fill_masked_four_diff() read_remainder_four_diff() 0x04= fill_masked_four_diff() fill_masked_four_diff() read_remainder_four_diff() 0x05= fill_masked_four_diff() fill_masked_four_diff() fill_masked_four_diff() read_remainder_four_diff() 0x06= fill_masked_four_diff() fill_masked_four_diff() fill_masked_four_diff() fill_masked_four_diff() read_remainder_four_diff() 0x07= 1 byte: color for P=0..3: fill plane P with bit P of color (fills the tile with the given color) 0x08= read 1 byte: mask for B=0..7: if bit B of mask is 1: for P=0..3: 1 byte: X fill byte B of plane P with bits P*2..P*2+1 of X else: for P=0..3: read byte B of plane P 0x09..0x17= for P=0..3: if bit P of code-8 is 1: 1 byte: x0 1 byte: mask for B=0..7: if bit B of mask is 1: put X0 into byte B of plane P else: read 1 byte into byte B of plane P else: read 8 bytes into plane P 0x18, 0x19= if code=0x18: 1 byte: T if code=0x19: 2 byte: T 1 byte: mask for B=0..7: if bit B of mask is 1 then: copy bytes B of planes 0,1,2,3 from tile T into the current tile else: read byte B of plane 0 read byte B of plane 1 read byte B of plane 2 read byte B of plane 3
(needs better description)
Maps
(See also the section "SNES VRAM DATA" in the SNES Hardware specs)
Screens and environments contain maps. These tell how should tiles be arranged.
Each entry in a map is a 2-byte integer.
bits | meaning |
---|---|
0..9 | Index of tile. |
10..12 | Index of palette. Multiply it with the number of colors in a tile to get a palette offset. |
13 | Priority: 0 means that the tile appears behind the sprites, 1 means in front of the sprites. |
14 | Xflip: Flips the tile so that left and right switches place. |
15 | Yflip: Flips the tile so that top and bottom switches place. |
Environments
type | block width | block height | bits per pixel | number of blocks | number of tiles | B in block mapping | B in tile mapping | palette offset |
---|---|---|---|---|---|---|---|---|
1. background | 3 | 8 | 2 | 256 | 512 | 8 | 9 | 0x00 |
2. blocks background | 6 | 8 | 4 | 256 | 256 | 8 | 8 | 0x60 |
3. blocks foreground | 3 | 8 | 4 | 256 | 256 | 8 | 8 | 0x20 |
2 byte: number of stored blocks ?*2 byte: block mapping bit 15: 1->incrementing sequence , 0->repeat 1-> middle (15-B) bits: length (0 means 2^(15-B)) bottom B bits: starting value 0-> bit 14: 0->length=1 , 1->length=given middle (14-B) bits: length (0 means 2^(14-B)) bottom B bits: value to repeat repeat this until you don't have enough values in the output compressed blocks for each tile 1 or 2 byte: data if layer 1 or 3: (big endian) ##0##### ######## -> raw copy (swap the bytes) 001##### -> repeat next two bytes, # times, MUST NOT cross block boundary 011##### -> repeat next two bytes, # times, with increase, MUST NOT cross block boundary 1#1##### -> repeat tile # of current block if layer 2: (big endian) ##0##### ######## -> raw copy (swap the bytes) 001##### ######## -> repeat next two bytes, # times, MUST NOT cross block boundary 011##### ######## -> repeat next two bytes, # times, with increase, MUST NOT cross block boundary 1#1##### -> repeat tile # of current block if screen: (little endian) ######## ##0##### -> raw copy ######## 001##### -> repeat next two bytes, # times ######## 011##### -> repeat next two bytes, # times, with increase 2 byte: number of stored tiles ?*2 byte: tile mapping similar format to block mapping compressed tiles for each tile 1 byte: compression mode ? byte: data if layer 2: in-front-of masks for each tile: 1 byte: mask mode 01->use default mask (all zeroes) 00->use the following mask 8 bytes: mask
(needs better description)
Levels
Levels are compressed.
I'll try to explain the algorithm:
procedure decompress_a_part(input:stream of bytes,output:stream of bytes,LEN:integer) clear output repeat while you don't have LEN bytes in the output: read a byte to X if X>=128 then copy X-128 bytes from input to output else read a byte to Y, if Y=0 then Y:=256 if X=1: read a byte to D, write D to output Y times if X=2: read a byte to D, write D,D+1,D+2,... to output, Y bytes total if X=3: read a byte to D, write D,D-1,D-2,... to output, Y bytes total if X=4: read a byte to A, copy Y bytes from the A-th byte of the output to after the end of the output if X=5: read a 2-byte integer to A, copy Y bytes from the A-th byte of the output to after the end of the output // note that the source and destination may overlap in the above two cases otherwise: error end if end repeat end procedure You have to call the above procedure with these values of LEN, and concatenate the outputs: 720,720,720,256,256,256,256,244,256,256 Each input part begins directly after the end of the previous. Calling with LEN=3940 at once won't work, because at the X=4 and X=5 cases, A is counted from the beginning of the current part, not the beginning of the whole level data.
Parts of the level:
Address | Size | Contents |
---|---|---|
0x000 | 720 | background layer |
0x2D0 | 720 | blocks layer |
0x5A0 | 720 | modifiers layer |
0x870 | 256 | block layer 1 graphics |
0x970 | 256 | block layer 2 graphics |
0xA70 | 256 | block objects |
0xB70 | 256 | block flags and background animation info |
0xC70 | 244 | room links, starting position, guards |
0xD64 | 256 | door events 1 |
0xE64 | 256 | door events 2 |
Total size = 3940 bytes = 0xF64 bytes
Background layer
One byte for each tile. (24 rooms * 30 tiles) Selects which background graphics should be used for this tile.
Blocks layer
One byte for each tile. (24 rooms * 30 tiles) Selects which block should be used for this tile.
Informations about blocks are found in the four parts whose names begin with "block".
Some blocks are used for special purposes:
Block 255 is used for nonexistent rooms. (Room link=255) It is usually a wall.
Block 254 is used for buttons that are permanently triggered by a dead guard or loose floor. It is usually a floor.
Reason: Button blocks don't have floor graphics, because the button object draws it for them.
If a button is triggered permanently, it needs to be changed into a regular floor, which does need floor graphics.
The only way this can be done is placing a different block into the place of the button.
Modifiers layer
One byte for each tile. Selects which modifier should be used for this tile.
Its meaning depends on the object used. (See the comments in the table below.)
Block layer 1 graphics
One byte for each block type. Selects which background graphics should be used for this block type.
Block layer 2 graphics
One byte for each block type. Selects which foreground graphics should be used for this block type.
Block objects
One byte for each block type. Selects which object should be used for this block type.
value | object type | notes |
---|---|---|
0x00 | (none) | |
0x01 | (unused) | |
0x02 | loose floor | The modifier selects the animation frame to be displayed. It should be zero. |
0x03 | (unused) | |
0x04 | open button | The modifier selects the door event to be triggered. |
0x05 | close button | The modifier selects the door event to be triggered. |
0x06 | door | The modifier specifies how much the door is open, in 1/4 pixels. |
0x07 | level door end | |
0x08 | mirror | |
0x09 | skeleton | |
0x0A | sword | |
0x0B | torch | There are four torch objects. They differ in the position of the flames. |
0x0C | chomper | The modifier selects the animation frame to be displayed. It should be zero. |
0x0D | guillotine | The modifier selects the animation frame to be displayed. It should be zero. |
0x0E | spike | The modifier selects the animation frame to be displayed. It should be zero. |
0x0F | (unused) | |
0x10 | crusher | The modifier selects the animation frame to be displayed. It should be zero. |
0x11 | (unused) | |
0x12 | heal potion | |
0x13 | life potion | |
0x14 | hurt potion | |
0x15 | upside down potion | |
0x16 | slow fall potion | |
0x17 | debris | |
0x18 | door top | |
0x19 | fire | |
0x1A | potion of warp | The potion of warp will take the player to room 17, tile 11, turned left. |
0x1B | down only | If added to a block that has floor, the player can fall through it, but can't jump/climb up through it. The modifier selects the animation frame to be displayed. It should be zero. If the modifier is in the 1..4 range, the transparent floor (that you can see after the shadow fight) will appear. |
0x1C | kill potion | |
0x1D | torch | There are four torch objects. They differ in the position of the flames. |
0x1E | spinning log | The modifier selects the animation frame to be displayed. It should be zero. |
0x1F | (unused) | |
0x20 | stars | The modifier selects the animation frame to be displayed. It should be zero. |
0x21 | torch | There are four torch objects. They differ in the position of the flames. |
0x22 | torch | There are four torch objects. They differ in the position of the flames. |
0x23 | teleport | Entering a teleport will take to the other teleport with the same modifier. |
0x24 | level door begin | |
0x25 | (unused) | |
0x26 | (unused) | |
0x27 | lava | Lava: falling onto this tile from any height will kill the prince. |
0x28 | skeleton continue | If a skeleton falls into this room then it will appear here. |
0x29 | skull? | Found on Level 19, in those rooms that have a skull. |
0x2A | conveyor belt, going right >>> | |
0x2B | conveyor belt, going left <<< |
Block flags and background animation info
One byte for each block type / background block.
Bits 0-1 determine if the current block type is a floor or a wall. If bit 0 is set then this block type is a floor. If bit 1 is set then this block type is a wall.
Bits 2-7 determine if the current background block is part of an animation group, and if yes, then which one. The animation groups are hard-coded. (needs further description)
Room links, starting position, guards
Address | Size | Contents |
---|---|---|
0xC70 | 24*4 | Room links |
0xCD0 | 1 | Starting room |
0xCD1 | 1 | Starting position in room |
0xCD2 | 1 | Starting direction |
0xCD3 | 1 | unused |
0xCD4 | 24 | Guard position in room |
0xCEC | 24 | Guard type and direction |
0xD04 | 4*24 | unknown |
Room links: For every room, the room to the left, right, above and below the current room is given, in this order.
The rooms are numbered 0..23, which is different from the DOS version.
255 means that no room is in the given direction.
For the "above" link, 254 means the same as 255, plus that the 4-pixel part of the room above (which would have walls) is not shown.
This is used for special "outdoor" rooms.
Starting room: The room the player starts in. Can be 0..23.
Starting position in room: row*10+column. (0..29)
Starting direction: 0xFF=left, 0x00=right.
Guard position in room: row*10+column (0..29), or 0xFF if there is no guard in this room.
Guard type and direction: The bottom 7 bits specify the guard type, the top bit specifies the direction: 1=left, 0=right.
value | guard type |
---|---|
0x02 | dead purple |
0x03 | skeleton |
0x04 | brown skeleton |
0x05 | golden skeleton |
0x06 | amazon |
0x07 | fat |
0x08 | shadow |
0x09 | green |
0x0A | greenish blue |
0x0B | blue |
0x0C | red |
0x0D | purple |
0x0E | blueface |
0x0F | red knight |
0x10 | blue knight |
0x11 | monster |
0x12 | Jaffar |
0x13 | dead |
Other values may display garbage or may even hang the game.
Door events 1 and 2
(same format as in DOS version, except that rooms are numbered differently) ------------------------- 7 6 5 4 3 2 1 0 <-bits byte from door events 1 | NX R1 R0 L4 L3 L2 L1 L0 byte from door events 2 | R4 R3 R2 0 0 0 0 0
R4 R3 R2 R1 R0: The number of the room. (0..23)
L4 L3 L2 L1 L0: The location in the room. (0..29)
NX: If zero then trigger next door event, if one then don't.
Screens
Screens are compressed.
(needs further description)
screen=32*28 tiles
big screen=64*56 tiles
compressed map similar format to compressed blocks in environments 2 byte: number of stored tiles compressed tiles (4bpp) same format as compressed tiles in environments
The map is stored in left-to-right, top-to-bottom order.
(needs better description)
Sprites
Sprites are compressed.
read 1 byte: width in pixels - 1 read 1 byte: height in pixels - 1 read 1 byte: size in tiles top 4 bits: height in 16 pixel units bottom 4 bits: width in 16 pixel units for each two 8*8 tiles: read 1 byte: flags for P = 0 to 7: (plane) (the 8 planes are actually 4 planes of 2 tiles) create a 64-byte array, representing two tiles; fill it with zeroes if bit P is set in flags: read 1 byte: mask if mask = 255 then: read 1 byte: mask read 1 byte: value for B = 0 to 7: (byte) if bit B of mask is zero: put value into byte B of plane P (in the array) end if end for else: for B = 0 to 7: (byte) if bit B of mask is zero: read 1 byte: value put value into byte B of plane P (in the array) end if end for endif end if append the array to the output end for end for
The 16*16 tiles are stored in left-to-right, top-to-bottom order.
The 8*8 tiles within the 16*16 tiles are also stored in left-to-right, top-to-bottom order.
(needs better description)
Background sprites and masks
Background sprites are always 48 pixels wide and 64 pixels high.
if it's a mask then W=1 else W=5 6*2 byte: start address of each 8-pixel wide column, or 0 if it's empty for each non-empty column: while the output is shorter than 64: 1 byte: n if n>=128 then: append n-128 empty rows to the output: else: copy n*W bytes from the input to the output for sprites, the first byte is a mask, the next four bytes are the bytes of each color plane endif
The data of each 8-pixel wide column is stored in top-to bottom order.
(needs better description)
Sound samples
The format of sound sample resources:
1 byte: number of sounds for each sample: 2 byte: length of part 1 in bytes 2 byte: length of part 2 in bytes for each sample: x byte: part 1 y byte: part 2
Part 2 will be looped.
Samples are compressed with BRR (Bit Rate Reduction).
Information about BRR (and the SPC in general) can be found here: http://emureview.ztnet.com/developerscorner/SoundCPU/spc.htm
Music
Music resources begin with the following header:
2 bytes: Length of data. Note: This length is excluded from offsets, i.e. all offsets are counted from the next byte. 2 bytes: Offset to instrument data. 1 byte: Flags to enable channels. (Bits 0..7 correspond to channels 1..8.) Should be 0xFF to enable all channels. For each channel (1..8): 2 bytes: Offset to the sequence of this channel.
Structure of each channel's sequence:
For each item: 1 byte: Repeat count (0x01..0xFD). 2 byte: Offset of command string. (The items are played in sequence.) 1 byte: end marker 0xFF = stop (for "event" music, for example potions) (0x00 seems to mean the same?) 0xFE = loop (for background music) 0xFE is followed by a 2 byte offset (into the sequence) that tells where to restart when looping. It usually points to the *second* item of the sequence, since the first item contains only settings and no notes. (So 0xFE is basically a GOTO.) In the "sound effects" resource this works a bit differently: * The sequences of channels 1-7 are empty, they contain only a "stop". * The sequence of channel 8 contains one item for each sound effect. * It seems that the repeat counts are ignored, or maybe they are used for something else?
Command strings are ASCII strings terminated with a 0x00 byte.
The possible commands are:
command | parameter | scope | meaning | |||
---|---|---|---|---|---|---|
type | range | initial (on startup) |
default (if omitted) | |||
T | n | 0-255 | 0 | 0 | global | set tempo, i.e. set unit of note length to n * 0.0002 seconds |
V | n | 0-15 | 13 | 0 | channel | set volume |
^ | channel | increase volume (wraps around from 15 to 0) | ||||
_ | channel | decrease volume (stops at 0) | ||||
@ | n | 0-255 | 128 | 0 | channel | set instrument |
O | n | 0-7 | 4 | 0 | channel | set octave |
+ | channel | up one octave (wraps around from 7 to 0) | ||||
- | channel | down one octave (wraps around from 0 to 7) | ||||
C,D,E,F,G,A,B | n | 0-255 | 16 | previous | channel | play note (n = set length of this note and next ones, relative to Tn) |
# | channel | make next note sharp | ||||
R | n | 0-255 | 16 | previous | channel | play rest (n = same as for CDEFGAB) |
= | n | 0-127 | 64 | 64 | channel | set balance |
< | channel | same as =127 (balance to left) | ||||
> | channel | same as =0 (balance to right) | ||||
[ | channel | start merge? (disable Decay?) | ||||
& | channel | separate merge? (disable Attack?) | ||||
] | channel | end merge? (enable Attack and Decay?) | ||||
) | n | 0-255 | 0 | channel | set detune? (signed) | |
$ | n | 0-3 | 0 | global | set all echo coefficients to a preset | |
$ | i,n | 0-7, 0-255 | 0, 0 | global | set an echo coefficient (signed) | |
Q | n | 0-15 | 0 | 0 | channel | set fill ratio: note will fill n/16 part of the time given for it (0 means 16) |
P | n | 0-255 | 0 | 0 | channel | ? |
H | n | 0-255 | 0 | 0 | global | echo volume |
I | n | 0-255 | 0 | 0 | global | echo feedback volume |
J | n | 0-255 | 4 | 0 | global | echo delay |
K | n | 0-127 | 64 | 0 | global | echo volume |
L | n | 0-255 | 0 | 0 | channel | move the balance left-right in a wave |
! | global | ? | ||||
% | channel | skip to next % (i.e. comment?) | ||||
M | n | 0 or 1 | 0 | 0 | global | pitch modulation enable? |
N | n | 0-31 or 255 | 255 | global | noise? (255=off) |
Instruments are described using 11 bytes each:
1 byte: index of sound sample (written to register x4h = VxSRCN) 1 byte: ADSR lower 8 bits (written to register x5h = VxADSR1, OR'ed with 0x80 to select ADSR instead of GAIN) 1 byte: ADSR upper 8 bits (written to register x6h = VxADSR2) 1 byte: A numerator or 0 1 byte: A denominator or 0 1 byte: B numerator or 0 1 byte: B denominator or 0 1 byte: flags? (written to RAM $27) 1 byte: Vibrato Speed (?) (written to RAM $A0+X) 2 byte: pitch correction?
For details about ADSR see: https://problemkaputt.de/fullsnes.htm#snesapudspadsrgainenvelope