Analysis of "King's Bounty - The Conqueror's Quest" for the Genesis/MegaDrive
King's Bounty was a videogame by New World Computing. It was originally released for Apple II and MS-DOS in 1990. Later versions included ports to Commodore 64 and Macintosh, then Amiga. The Genesis/MegaDrive port was released in 1991 with the subtitle The Conqueror's Quest. The port brought some changes: it featured real-time player movement, some different graphics, and even a few changes to game rules.
King's Bounty was a predecessor to the wildly successful "Heroes of Might & Magic" series of games.
About this document
This document describes how the graphics and sounds were stored in the ROM of King's Bounty - The Conqueror's Quest. It is the result of exploratory analysis for my own education and entertainment.
The details in this document could be used for producing a third-party patch to replace graphics or sounds, or perhaps it could be used to add King's Bounty support to a resource viewing application.
The offsets in this writeup are based on the ROM for the USA/Europe release.
- Length in bytes: 524288
- CRC32 checksum:
aa68a92e
- MD5 sum:
1bb8e1e5d41bbeff2e452935bddd9883
- SHA1 sum:
32f90806f44a0bd1d65d84ceeb644681b9cee967
- SHA256 sum:
5fd1494a6a47e33d85b22ebbc26a1156f31bb539cf54d4e109e1bcbddef8b4eb
Hexadecimal numbers such as ROM offsets will usually be written
in the standard 0x123abc format. The abbreviation
int16be
means a 16-bit (two-byte) integer stored in
big-endian order (most significant byte first). Likewise,
int32le
means a 32-bit (four-byte) integer stored in
little-endian order (least significant byte first).
ROM layout
offset | contents |
---|---|
0x00000 | ROM header |
0x00200 | Entry code |
0x00312 | "Jump table" for data and code addresses (272 entries, 570 bytes) |
0x00882 | Jump table padding (room for an additional 23 entries) |
0x0090c | Code 1 (math, intro support) |
0x00b6e | Game data 1 (font) |
0x010a4 | Code 2 (C code - most of the game logic) |
0x18b0c | Code 3 (decompression, copying, text printing) |
0x19062 | Game data 2 (map, text) |
0x25174 | Code 4 (publisher intro, graphics) |
0x285fe | Sound driver (Z80 code) |
0x29b86 | Sound driver support |
0x2a060 | Code 5 (VBLANK support) |
0x2a4b4 | Bigger data section (graphics, music) |
0x7fa2c | Code for checksum |
Code 2: Game logic
These functions are the higher-level game code. They follow C
calling conventions (args on stack in reverse order, return value
in register d0
) and even have the name of the function
at the end (see elsewhere in this document for the exact format of
these names).
A selection of the functions and their C-style calling signature:
offset | function name | signature |
---|---|---|
0x047c8 | Viewarmy | void Viewarmy() |
0x05d48 | Viewchar | void Viewchar() |
0x09f9a | Music | void Music(void *music_sequencing_data) |
0x1249e | Damage | int32_t Damage(uint8_t which_army_slot, uint8_t other_army_slot) |
0x164d6 | Subtractgold | bool Subtractgold(int32_t how_much_gold) |
0x167d8 | PrintAmount | int32_t PrintAmount(int8_t x, int8_t y, int16_t how_many_troops) |
Code 3: lower level memory/video routines for game
offset | signature | |
---|---|---|
0x18cb6 | draw text on at video position | int16_t DrawTextAt(x,y, *buf) |
0x18d26 | text graphic tilerefs indexed by codepoint | |
0x18b0c | decompress (LZSS) | |
0x18c46 | copy main ram to video RAM | |
0x18ec2 | clear rectangle of video ram | |
0x18f2a | copy main ram to main ram (memcpy) |
Game data 2: map, text
A selection of some of the contents:
| offset | contents | | | ------- | -------- | | | 0x1a88e | Initial world map (LAND.ORG) | 4 continents * 64 rows * 64 columns | | 0x1ebd3 | Continent names | | | 0x1ecbd | Castle names | | | 0x20d4d | Contract/villain names | | | 0x20ee0 | Signpost text | | | 0x22678 | Town names | | | 0x228d7 | Spell names | | | 0x23766 | Troop quantity bucket labels (A few/Some/Many/...)
For more about the text tables, see the text section of this document.
Code 4: publisher intro
offset | |
---|---|
0x280cc | decompress function (LZSS body) |
0x28190 | compressed data (LZSS int16le headers) |
Main game data
offset | description | type |
---|---|---|
0x2a4b4 | Chance of income chest (per continent) | |
0x2a4d0 | Random income from chest (per continent) | |
0x2a4d4 | Fixed income from chest (per continent) | |
0x2a4d8 | Additional spell slots granted from chest (per continent) | |
0x2a4ea | Text alignment for spell name (per spell) | |
0x2a4f8 | Town locations (X coordinate, per town) | |
0x2a512 | Town locations (Y coordinate, per town) | |
0x2a52c | Spell prices | 14 * int16be |
0x2a5c8 | Text alignment for villain names (per villain) | 17 * uint8 |
0x2a5d9 | Text alignment for castle names (per castle) | 27 * uint8 |
0x2a7c8 | Troop quantity bucket (A few/many/lots/...) cutoffs | |
0x2a7d2 | Troop quantity bucket label string length | |
0x2a80a | Troop max hitpoints indexed by troop type | 24 * uint8 |
0x2a823 | Troop skill level indexed by troop type | 24 * uint8 |
0x2a83c | Troop movement indexed by troop type (0 = fly) | 24 * uint8 |
0x2a855 | Troop min damage indexed by troop type | 24 * uint8 |
0x2a86e | Troop max damage indexed by troop type | 24 * uint8 |
0x2a888 | Troop gold cost indexed by troop type | 24 * uint16be |
0x2a8ba | Troop morale group indexed by troop type | 24 * uint8 |
0x2a8d3 | Troop morale group interactions indexed by morale group | 5 * 5 * int8 |
0x2aba9 | Mapping from villain number to villain graphic order | |
0x2af98 | Compressed graphics (credits, intro) | |
0x33886 | Screen border layouts (raw tilerefs) | |
0x36d86 | Compressed graphics (hero portraits, backgrounds, event pictures) | |
0x4b04c | Raw graphics (raw tiledefs) villains | |
0x57c4c | Raw graphics (raw tiledefs) sidebar graphics (siege, magic, minimap, money) | |
0x5d04c | Raw graphics (raw tiledefs) artifacts | |
0x62cec | Raw graphics (raw tiledefs) player character (walk/boat/fly) | |
0x668ec | Raw graphics (raw tiledefs) troops | |
0x794f0 | Music (sequences) |
Compression
Much of the graphical data in King's Bounty - The Conqueror's Quest is stored in a compressed form.
The King's Bounty ROM makes use of two different compression formats. Both could be called LZSS (Lempel-Ziv-Storer-Szymanski). They are very similar to each other, but not quite identical.
The main scheme is used for almost everything. It has an 8-byte header. The first 4 bytes is a (big-endian) 32-bit integer counting the length (in bytes) of the expanded data. The other 4 bytes forms another 32-bit integer which counts the length (in bytes) of the compressed data.
There is another scheme, used for some of the resources in the pre-game bootup intro. It has a pair of little-endian 16-bit integers for a header instead.
LZSS compression component | Main compression | Alternative compression |
---|---|---|
Uncompressed size header | int32be | int16le |
Compressed size header | int32be | int16le |
Window size | 0x1000 (4096) | 0x1000 (4096) |
Initial window content | 0x20 | 0x20 |
Initial window pointer | 0x0fee (4078) | 0x0fee (4078) |
Minimum lookback count | 3 | 3 |
Decompressing the data
The first byte after the header serves as the "flags" for the next eight decompression steps. The eight bits of that byte, starting from the least significant bit, choose between a single-byte literal or a two-byte reference to the window contents. If the flags byte is all-ones, then the eight bytes following are all literals that appear in the decompressed stream (and should be written to the sliding window at the current window position).
The two-byte reference to the existing window contents is made of two parts, a 12-bit window position and a 4-bit window length (beyond the minimum lookback count).
Making your own "compressed" data
You can create data compatible with this scheme that only pretends to compress (and will in fact be bigger than the source data) by inserting a byte with all bits set (value 0xff) at the start, eight bytes of source data, then another 0xff byte, then another eight bytes of source data, and so on. Count the size of your source data stream and your new "compresed" data stream and insert those as headers.
Graphics
A word about graphics on the Mega Drive / Genesis
The video display processor (VDP) does much of its work not at the pixel level but instead with 8x8 groups of pixels. These 8x8 pixel blocks are called "patterns", "tiles", "chars", and probably other names too. In this document, I call them 4bpp VDP graphics tiles.
The tiles are defined and stored in 32-byte definitions, or tiledefs for short. The format is packed 4 bits per pixel (2 pixels per byte), 4 bytes per 8-pixel row.
As an example, a tiledef beginning with the hex bytes 01, 23, 45, 67 could be displayed with color 0 (transparent) in the top-left pixel, color 1 in the next pixel across, color 2 in the third pixel from the left, and so on, finish with color 7 in the top-right pixel.
Each tile is displayed with a 4-bit (16-color) palette line. The are four palette lines, each with 16 color slots (although slot 0 in each line is usually treated as transparent and not shown). Each time a tile is displayed, it must use just one palette line for all 64 pixels.
Tiles are arranged on screen with a series of 16-bit tile references, or tilerefs for short. Other documents call these tilerefs "nametables" or "patterns". Each tileref selects a single 8x8 pixeldef tile for display, along with a palette line number, and whether to flip the pixels horizontally or vertically. Some games can use this flipping and recoloring to do all kinds of tricks, but King's Bounty tends to be pretty straightforward with its display.
Troop graphics
King's Bounty has 24 types of military unit available in the game (Peasants, Militia, Archers, and so on). Each troop type is represented by a 4-frame animation. The graphics are 6x4 graphics tiles (48x32 pixels).
The graphics are stored at ROM offset 0x668ec as raw (uncompressed, headerless) VDP graphics tile definitions. Each frame has 6 * 4 = 24 tiles, and since each tile is 32 bytes, a frame is 24 * 32 = 768 (0x300) bytes. Four frames of 0x300 bytes means the animation for each troop type is 0xc00 bytes, and the animations for all 24 troop types is 0x12000 bytes.
The tiles are stored in column-major order (like villains). That is, the tiles stored as T1 T2 T3 T4 T5 T6 T7 T8 T9 ... T24 are arranged as in the figure below:
left | right | ||||
---|---|---|---|---|---|
T1 | T5 | T9 | .. | .. | .. |
T2 | T6 | .. | .. | ... | .. |
T3 | T7 | .. | .. | ... | T23 |
T4 | T8 | .. | .. | ... | T24 |
All troop graphics use the standard palette (the one stored compressed at ROM offset 0x3371a). The array of 16-bit words at ROM offset 0x2adc6 (selected by jump table -0x1e52) determines which palette line to use for each troop graphic. The values are in the same format used for tilerefs. A value of 0x0000 means palette line 0, 0x2000 means palette line 1, 0x4000 means palette line 2.
The animations are stored in order of the troop type number, the same as the text table beginning at 0x237c0 (jump table + 0x117).
Villain graphics
Similar to the animations for troops, King's Bounty has 17 villain animations.
Like troop graphics, villain graphics are stored as raw (uncompressed, headerless) 4bpp VDP graphics tile definitions. They begin at ROM offset 0x4b04c and end at 0x57c4c.
There are animated graphics for 17 different villains.
The tiles are stored in column-major order (like troops). That is, the tiles stored as T1 T2 T3 T4 T5 T6 T7 T8 T9 ... T24 are arranged as in the figure below:
left | right | ||||
---|---|---|---|---|---|
T1 | T5 | T9 | .. | .. | .. |
T2 | T6 | .. | .. | ... | .. |
T3 | T7 | .. | .. | ... | T23 |
T4 | T8 | .. | .. | ... | T24 |
Like troop graphics, each villain has a loop of 4 animation frames.
- Each villain uses a single palette line but different villains
use different palette lines
- There is an array of 16-bit words at ROM offset 0x2adf8 that select which palette line
- This array is in the same order as the villain graphics, not ordered by "villain number" (see note below)
- The palette values are in the format used in tilerefs: 0x2000 means palette line 1, 0x4000 means palette line 2
- Each frame is 0xc00 bytes (4 frames * 6 columns * 4 tiles * 32 bytes) making each animation 0x3000 bytes.
- Each frame is stored as 6 columns of 4 tiles (column-major)
- Each column of tiles is stored top-to-bottom
- Each tile represents 8x8 pixels
- Each tile is a 32-byte pattern
The villain graphics not ordered by "villain number", so don't expect them in the same order as the table at ROM offset 0x20d6a. However, there is a mapping from villain number to graphics number in the 17 bytes at ROM offset 0x2aba9. The first byte at ROM offset 0x2aba9 is the graphic number (16) for the first villain (Murray). You can expect the animation data for Murray to begin at ROM offset 0x4b04c + (0x3000 * 16).
Event pictures
In addition to the smaller animated graphics, King's Bounty contains some bigger still images, like the intro screen and the ones shown visiting somewhere for recruiting troops.
These bigger images are stored compressed in three parts: the tile definitions, the palette definitions, and the tile references that show which tiles are displayed with which palette.
tiledefs | tilerefs | palettes | used for |
---|---|---|---|
0x2ef20 | 0x308dc | 0x30e24 | opening title logo |
0x2b28a | 0x2e774 | 0x2eeae | character select |
0x44f3c | 0x469d8 | 0x3371a | castle |
0x3a3e0 | 0x3bec6 | 0x3371a | town |
0x3f346 | 0x40db2 | 0x3371a | plains |
0x3c2c2 | 0x3ef42 | 0x3371a | forest |
0x41064 | 0x43296 | 0x3371a | hill |
0x43696 | 0x44b70 | 0x3371a | dungeon |
0x496be | 0x4ac7a | 0x4afcc | endgame lose |
0x47f5a | 0x4932e | 0x49648 | endgame win |
All compression is the same LZSS format with an 8 byte header made up of a pair of 32-bit long integers (big-endian). The first is the count of compressed bytes, while the other is the count of uncompressed bytes. The compression algorith is described in more detail elsewhere in this document.
After uncompressing, the tiledefs have a 2 byte header, a single 16-bit short integer (big-endian). That header is the count of tile definitions, and should equal the remaining bytes in the buffer divided by 32 (bytes per tiledef).
Likewise, the uncompressed tilerefs have a 4 byte header: a pair of 16-bit short integers (big-endian). That header is the count of rows and columns of tilerefs. The first number is rows (image height), the other is columns (image width).
The palettes, after uncompressing, are in the raw CRAM format. The buffer is 128 bytes: 4 palette lines, each 16 color entries, each entry a 16-bit short integer (big-endian). The entries represent RGB values, or rather BGR: 0x0eee is pure white, 0x00e is max red, 0x0e0 is max green, 0xe00 is max blue.
Terrain graphics
King's Bounty shows two different scales of terrain: in battle and out of battle. Terrains like like grass, trees, mountains, and lakes are shown in both. Despite some quite similar graphics, they are powered by entirely different definition data.
Battle terrain
The terrain graphics shown in each battle tile is made up from 6x5 graphics tiles (48x30 pixels).
The tile definitions are stored compressed at ROM offset 0x46d90, with a sixteen-bit count header on the decompressed stream.
The tile references are stored raw and headerless starting at ROM offset 0x19666.
Overland terrain
The initial world map is defined at ROM offset 0x1aa8e and covers four continents. Each continent is 64x64 map tiles (not graphics tiles). Each map tile is a byte and each byte value has a meaning. For example: a blank square of grass is 0x00, a blank square of water is 0x20, a square of water with a shoreline on the left is 0x1c.
The graphics tiles for these map tiles are stored compressed (with a 16-bit header for graphics tile count) at ROM offset 0x30e82. Each map tile is displayed as a 6x5 arrangement of graphics tiles. The tile references are in 60-byte groups (6x5 16-bit short integers), starting at ROM offset 0x1980a. There are 80 different map tiles.
The palettes are the usual set (found compressed at ROM offset 0x3371a).
Castle siege battle terrain
When the player lays siege to a castle, a battle begins. The background for such a battle does not use battle terrain as described above, but rather a single large image. The image is stored in a similar way to hero portraits or event pictures. The tile definitions are stored compressed at ROM offset 0x46d90, with a two-byte header once decompressed. The tile references are stored compressed at ROM offset 0x47cc6, with a four-byte header once decompressed (height, width).
Hero portraits
Each of the four different character class has a different "hero portrait" picture to show on the View Character screen. Each portrait is 12 graphics tiles wide by 13 graphics tiles high (96x104 pixels).
The hero portraits are stored in a similar way to event graphics: a set of 32-byte tile definitions (stored compressed with a header), combined with a set of 2-byte tile references. There are some differences: the tile references are stored uncompressed headerless, row-by-row (row-major), and each row is padded with 0xffff in unused references.
The compression is the same LZSS compression used by event graphics and other parts of the game. The palettes are the usual set (found compressed at ROM offset 0x3371a).
Portrait addresses in ROM:
tiledefs | tilerefs | palettes | class |
---|---|---|---|
0x36d86 | 0x37a0e | 0x3371a | Knight |
0x37b7a | 0x3872e | 0x3371a | Paladin |
0x3889a | 0x394b6 | 0x3371a | Sorceress |
0x39622 | 0x3a274 | 0x3371a | Barbarian |
User interface graphics
Puzzle map
The full-screen puzzle map is displayed in a frame with tilerefs stored in the compressed data at ROM offset 0x3379e.
Each cell in the puzzle grid is filled with one of three graphics:
- villain graphic/animation (the set beginning at ROM offset 0x4b04c )
- an artifact graphic (the set beginning at ROM offset 0x5d04c), or
- an overland map graphic (tilerefs beginning at ROM offset 0x1980a, tildefs stored compressed at ROM offset 0x30e82)
Player sprites
- Raw (uncompressed, headerless) graphics tiles stored in column-major order
- All are displayed with palette line 0 of the standard palette (the one stored compressed at ROM offset 0x3371a)
- One set for walking on land, one set for sailing on boat, one set for flying
- Each frame is 6 * 4 = 24 tiles (24 * 32 = 0x300 bytes per frame)
- Each set has 8 frames (8 * 0x300 bytes per frame = 0x1800 bytes per animation set)
- The walking set begins at ROM offset 0x62cec
- The boat set begins at ROM offset 0x644ec
- The flying set begins at ROM offset 0x65cec
Artifact graphics
- Eight artifacts with a hard-coded order and meaning
- Raw (uncompressed, headerless) graphics tiles stored in column-major order
- One set wider (6x4 tiles), one set narrower (5x4 tiles but still the same number of bytes)
- The wider set starts at ROM offset 0x5d04c, and is used for the Puzzle Map view
- The narrower set starts at 0x5e84c, the same place that the "ninth artifact" would, and is used in View Character for artifacts the player possesses
- The graphics are ordered by artifact number, the same order as the text strings which give each artifacts their name are found with (jump table + 0x41c) which winds up being at ROM offset 0x22108
- Each artifact is displayed with a single palette line from the
standard palette
- The palettes lines are stored in an array of 16-bit words at ROM offset 0x2ae1a (found by jump table - 0x1e4a)
- The first 16-bit word in that array is 0x2000, so the first artifact uses palette line 1
- The palette values are in the format used in tilerefs: 0x2000 means palette line 1, 0x4000 means palette line 2, 0x0000 means palette line 0
Continent maps
The player starts with a map to the first continent. The View Character screen represents this with a 6x4 graphic with tiledefs at ROM offset 0x6124c (raw, headerless, in column-major order). Each of the following 0x300 bytes serves as tiledefs for a graphic for another three continents, followed by a blank continent map. The blank continent map is used on the View Character screen for inaccessible continents.
All maps are displayed with palette line 1.
There is an exact duplicate set at 0x6034c, but that one does not seem to be used anywhere.
Side pane
The side pane shows some game status with five equally-sized graphical items. Each item is 6x4 graphics tiles in size (48x32 pixels). Some show animations, while the others are still images. All are stored as raw (not compressed) tile definitions, and most use palette line 1 from the standard palette (the one found compressed at ROM offset 0x3371a).
If a player holds a contract for a particular villain, that villain's animation is displayed as described in the Villain graphics section. If the player does not hold a contract for any villain, the still "no contract" graphic defined at ROM offset 0x5944c will be used instead. The tile definitions are stored column-major order, and they are displayed with a single palette line (1?) from the standard palette.
If the player has siege weapons with them, then an animation defined at ROM offset 0x57c4c will play instead of the "no siege weapons" graphic defined at ROM offset 0x5974c. This animation is 4 frames long, stored like a villain or troop animation. It is displayed with palette line 1 of the standard palette.
If the player has the ability to cast magic spells, then an animation defined at ROM offset 0x5884c will play instead of the "no magic" graphic defined at ROM offset 0x59a4c. The animation is four frames long, stored like a villain or troop animation. The palette lines used for display include 1 but it's mostly just flashing different colors so other palette lines also work.
(TODO: Check what palette lines the game actually uses for the active magic animation.)
The mini-puzzle is a short representation of the state of the puzzle map (that is, it changes colour each time the player claims a bounty on a villain or when the player finds an artifact). It is made of 6x4 graphics tiles like the other sidebar entries. The raw tiledefs are arranged in column-major order at ROM offset 0x59d4c and displayed with palette line 1. This graphic is drawn with a grid of red rectangles to obscure any pieces not found.
The player's money is shown as stacks of gold/silver/copper coins overlaid on a background image of a money pouch.
- The money-pouch background graphic tile definitions are raw headerless and begin at ROM offset 0x5a04c
- Piles of gold, silver, copper coins are built up by overlaying 1x3-tile (8x24 pixel) sprites with transparent backgrounds
- Overlaid coin tile definitions begin at ROM offset 0x6214c and are stored raw and headerless
- All the money graphics use palette line 0 of the usual palette set
Summary
Graphic | Tiledefs | Tiledef header | Tilerefs | Tileref header | Palette | Palette line |
---|---|---|---|---|---|---|
Troop | Raw | Headerless | Column-sequential | N/A | Standard | Varies per troop |
Villain | Raw | Headerless | Column-sequential | N/A | Standard | Varies per villain |
Artifact | Raw | Headerless | Column-sequential | N/A | Standard | Varies per artifact |
Money | Raw | Headerless | Column-sequential | N/A | Standard | Fixed |
Player sprite | Raw | Headerless | Column-sequential | N/A | Standard | Fixed |
Continent maps | Raw | Headerless | Column-sequential | N/A | Standard | Fixed |
Overland terrain | Compressed | 1x int16be | Raw | Headerless | Standard | In tilerefs |
Battle terrain | Compressed | 1x int16be | Raw | Headerless | Standard | In tilerefs |
Hero portrait | Compressed | 1x int16be | Compressed | Headerless | Standard | In tilerefs |
Castle siege | Compressed | 1x int16be | Compressed | 2x int16be | Standard? | In tilerefs |
Event | Compressed | 1x int16be | Compressed | 2x int16be | Compressed | In tilerefs |
Music and sound effects
The Mega Drive/Genesis port of King's Bounty uses a sound driver by Electronic Arts' Steve Hayes.
The sound engine is listed on Game Development Research Institute's wiki as being "Electronic Arts/Steve Hayes", the same as several other titles: Populous, Marble Madness, Zany Golf, Buck Rogers: Countdown To Doomsday, and more. Some other sound engines (notably GEMS) have tools to extract the music data. It is possible something like this could exist for the Steve Hayes engine.
http://gdri.smspower.org/wiki/index.php/Mega_Drive/Genesis_Sound_Engine_List
The sound driver itself runs on the Z80 processor, rather than the Motorola 68000 CPU that runs the main game code. The Z80 code is stored at ROM offset 0x285fe-0x29b86 (0x1588 bytes in length). The code for the two processors does some light interaction to coordinate playing and finishing, as well as loading batches of music and sound effects.
Music
Music in King's Bounty is sequenced in a format conceptually similar to MIDI: Assign instrument to track. Select a key by note number, send a "key on" for that track to that note number, delay, key off.
Note numbers are counted in semitones like piano keys. In General MIDI, note number 60 (0x3c) represents middle C, and that seems to be true in Steve Hayes music sequencing format too.
Track 9 is a special track where the note numbers represent different (percussion) sounds. It is similar to General MIDI drum note numbers ("Channel 10"), and in fact the specific numbers seem to map well enough.
offset | length | purpose |
---|---|---|
0x2a486 | 0x0005 | (Initial empty music) |
0x794f0 | 0x0fac | Walking |
0x7a4a4 | 0x0f0d | Battle |
0x7b3b8 | 0x04cd | Meeting |
0x7b88c | 0x3975 | Intro/Endgame |
0x7f208 | 0x05b2 | (Long silence) |
0x7f7c0 | 0x019b | Battle Won(?) |
0x7f960 | 0x00c9 | Battle Failed |
King's Bounty only uses few of the instruments defined in the Steve Hayes sound driver. There are many more that are defined but are never assigned to any track.
Music sequencing format
The music data is a stream with commands and arguments, interspersed with delay lengths. Most delay lengths are zero (don't wait before processing the next command) but non-zeroes are used for musical timing.
Commands in the high nybble of the first byte:
- 0xc assigns instrument to a track
- 0x9 key on (note begin)
- 0x8 key off (note end)
- 0xf special
Command 0xc - assign instrument
Cx yy: set track x to use instrument yy
C0 00: set track 0 to use instrument 0
C2 05: set track 2 to use instrument 5
Command 9 - key on (note begin)
9x: key on (note begin) for a track
90 yy zz: key on track 0, note yy, velocity zz
91 yy zz: key on track 1, note yy, velocity zz
92 yy zz: key on track 2, note yy, velocity zz
...
99 yy: play sample for note yy (like drum track)
Drum track notes:
drum track | FM instrument | PSG wav | notes in |
---|---|---|---|
"anchorage" | 0xba8 | 0x101c | 0x24, 0x29 |
"bangladesh" | 0xc34 | 0xc42 | 0x26, 0x2d |
"cincinatti" | 0xbf0 | 0xe20 | 0x2a, 0x2c, 0x30, 0x36 |
"dublin" | 0xbcc | (none) | 0x2e, 0x31, 0x33, 0x2e |
(TODO: Identify actual percussion names like snare, bass drum, etc for placeholder code names)
Command 8 - key off (note end)
8x yy zz: key off (note end) on track 0, note yy, zz is ignored (i think)
Command 0xf - special
Fx: special
FC: end of stream
Musical instruments
There are two kinds of instruments - one played on the FM synth and one played on the PSG (programmable sound generator).
The FM synth instruments are stored in a structure with register values for a YM2612 channel. They are spaced 36 bytes apart but the actual structures are 46 bytes, relying on the unused bytes at the start of the following instrument definition to store the higher values for the current instrument.
byte # | YM2612 register | meaning |
---|---|---|
12 | 30 | mul/dt op1 |
14 | 38 | mul/dt op2 |
13 | 34 | mul/dt op3 |
15 | 3c | mul/dt op4 |
16 | 40 | tl op1 |
18 | 48 | tl op2 |
17 | 44 | tl op3 |
19 | 4c | tl op4 |
20 | 50 | ar/rs op1 |
22 | 58 | ar/rs op2 |
21 | 54 | ar/rs op3 |
23 | 5c | ar/rs op4 |
24 | 60 | dr/am op1 |
26 | 68 | dr/am op2 |
25 | 64 | dr/am op3 |
27 | 6c | dr/am op4 |
28 | 70 | sr op1 |
30 | 78 | sr op2 |
29 | 74 | sr op3 |
31 | 7c | sr op4 |
32 | 80 | rr/sl op1 |
34 | 88 | rr/sl op2 |
33 | 84 | rr/sl op3 |
35 | 8c | rr/sl op4 |
36 | (unused) | |
37 | (unused) | |
38 | (unused) | |
39 | (unused) | |
41 | a4 | freq hi |
40 | a0 | freq lo |
44 | b0 | algorithm |
45 | b4 | panning/pms/ams |
The sound driver includes several instrument presets:
instrument # | offset (sound) | offset (ROM) |
---|---|---|
0 | 0x10fe | 0x285fe + 0x10fe |
1 | 0x1122 | 0x285fe + 0x1122 |
2 | 0x1146 | 0x285fe + 0x1146 |
3 | 0x116a | 0x285fe + 0x116a |
... | ... | ... |
31 | 0x155a | 0x285fe + 0x155a |
These instrument presets packed together somewhat -- the final bytes of instrument 2 are after the starting offset for instrument 1. This is not a problem because the first 11 bytes of the instrument definition are not read.
PSG envelopes
The PSG ("programmable sound generator") instruments are stored as 2-byte length header followed by a series of 4-bit attenuations (2 attenuations per byte).
Attenuation is like the opposite of volume: zero attenuation is loud, attenuation 1 is a little quieter, max attentuation is silent. The header is a 16-bit short integer (little-endian) for the number of bytes after the first byte of attenuation.
PSG waveform | offset (sound) | offset (ROM) |
---|---|---|
mizle bangladesh | 0x0c42 | 0x285fe + 0xc42 |
mizle cincinatti | 0x0e20 | |
mizle anchorage | 0x101c |
Sound effects
There is a table of 32 pointers at ROM offset 0x29f10.
The first entry points to ROM offset 0x29f90 and the final points to ROM offset 0x2a054.
sound | offset | used for |
---|---|---|
0 | 0x29f90 | Raise Control spell |
1 | ||
2 | Time Stop spell | |
3 | ||
4 | Found a map in a chest, Instant Army spell | |
5 | Money: Found treasure or income in a chest | |
6 | Magic: Found magic in a chest (spell slot, spell power, spell) | |
7 | Error message | |
8 | Town Gate spell, Castle Gate spell | |
9 | Resurrect spell | |
10 | (unused?) | |
11 | (unused?) | |
12 | Freeze spell | |
13 | Bridge spell | |
14 | (unused?) | |
15 | (unused?) | |
16 | Damage: Turn Undead spell, Fireball spell, Lightning spell | |
... | ||
31 | Artifact |
The sound effects themselves are a sequence of 23 bytes (they may overlap). Byte 0 gets copied to the first byte of an "instrument" and byte 1 is used as a note number to play. The following two bytes form a 16-bit Z80 pointer to a byte.
TODO: Identify the specifics of the bytes in a sound effect definition. They might well overlap with FM instrument definition.
Text
The font used for text is stored as LZSS-compressed tile definitions (tiledefs). The comprssed tiledefs can be found at ROM offset 0x6be. These are loaded to VRAM address 0xf000 which is tile number 0x780.
There is an array of 128 16-bit tilerefs starting at ROM offset 0x18d26 which maps ASCII values to tileref values (as in select which tile).
The game credits are stored like an event picture, rather than as ASCII-style strings. The game credits are stored as compressed tilerefs at ROM offset 0x2af98, with a height/width header just like event pictures.
Most of the rest of the text in the game is stored as strings. These strings use a 7-bit character encoding that is mostly ASCII-compatible. Strings are usually terminated either with a null byte (0x00) or with the special byte 0xa5. Many strings are stored in fixed-width structures, padded with spaces and/or null bytes.
Some of these text tables are listed below. For example, continent names are in a text table at ROM offset 0x1ebd3 (found by looking up the address 0x29c bytes after the start of the jump table at 0x312). Each entry is a fixed-length ASCII string 12 bytes long, and there are 5 of them (although there are four continents, this table has a fifth entry for "Unknown").
text for | ROM offset | jump table | size |
---|---|---|---|
Continent names (left-aligned) | 0x1ebd3 | + 0x29c | 5 * 12 |
Continent names (centred) | 0x1ec0f | + 0x13e | 4 * 12 |
Artifacts (text on finding) | 0x22108 | + 0x41c | 8 * 6 * 28 |
Town names (by number) | 0x22678 | + 0x32c | 26 * 15 |
Villain names | 0x20d6a | + 0x2f0 | 17 * 22 |
Castle names | 0x1ecbd | + 0x308 | 29 * 16 |
Troop names (plural) | 0x237c0 | + 0x116 | 25 * 11 |
Troop names (singular) | 0x238d3 | + 0x246 | 25 * 11 |
Signpost text | 0x20ee0 | + 0x27c | 79 * 2 * 29 |
Respawn (battle loss) text | 0x2445e | + 0x26c | 7 * 29 |
Tip: When the size lists three numbers, the middle number is the number of lines.
Some text tables have matching numeric tables with the lengths of each string. For example, the castle names are at 0x1ecbd and the byte array at ROM offset 0x2a59d contains the lengths. Likewise, the byte array at ROM offset 0x2a5c8 covers the lengths of the villain names at ROM offset 0x20d6a. If you change the string, you will probably need to change the length to keep the display aligned.
RAM layout
jump table a5 | RAM address | what | type |
---|---|---|---|
-0x1b62(a5) | 0xffe7b0 | spell power | int8_t |
-0x1b60(a5) | 0xffe7b2 | max learnable spells | int8_t |
-0x1b5e(a5) | 0xffe7b4 | villains captured by villain number | bool[17] |
-0x1b4c(a5) | 0xffe7c6 | artifacts found by artifact numer | bool[8] |
-0x1b28(a5) | 0xffe7ea | troop type by army slot | int8_t[5] |
-0x1b18(a5) | 0xffe7fa | player location continent | int8_t |
-0x1b12(a5) | 0xffe800 | player location x | int8_t |
-0x1b10(a5) | 0xffe802 | player location y | int8_t |
-0x1aba(a5) | 0xffe858 | castle ownership by castle number | int8_t[26] |
-0x1a6c(a5) | 0xffe8a6 | location of goal (continent, x, y) | int8_t[3] |
-0x1266(a5) | 0xffed9a | castle guard troop type by castle number | int8_t[26][5] |
-0x4dc(a5) | 0xfffb24 | castle guard population by castle number | int16_t[26][6] |
-0x4ec(a5) | 0xfffe26 | leadership | int16_t |
-0x4ea(a5) | 0xfffe28 | weekly income | int16_t |
-0x4e6(a5) | 0xfffe2c | troop population by army slot | int16_t[5] |
-0x3f8(a5) | ? | ? | ? |
-0x3d6(a5) | 0xffff3c | days remaining to find goal | int16_t |
-0x3c2(a5) | 0xffff50 | gold on hand | int32_t |
Save games (passwords)
- Game state is saved as long password strings rather than files
- Passwords are displayed as 56 alphanumeric characters across 7 lines of 3-2-3 characters
- Each character holds 5 bits of information
- although 36 characters are available for input, the game
replaces several to use a smaller set:
- '1' -> 'I'
- '0' -> 'O'
- '5' -> 'S'
- 'U' is out of bounds
- then those spots are filled by further replacement from the end
- '7' -> 'U'
- '8' -> '1'
- '9' -> '5'
- The coding is stored at ROM offset 0x2aae2
- 'A' is 0
- 'B' is 1
- '6' is 31
- although 36 characters are available for input, the game
replaces several to use a smaller set:
- There is some scrambling and a checksum
- The scrambling has two passes of XOR, one constant key and one
with a variable key
- The outer pass (first when decoding) XORs each byte in a
pattern equivalent to the following hex key:
- 11 12 1c 1d 1f 18 1a 1b 1d 1e 18 19 1b 04 06 07 09 0a 14 15 17 10 12 13 15 16 10 11 13 1c 1e 1f 01 02 0c 0d 0f 08 0a 0b 0d 0e 08 09 0b 14 16 17 19 1a 04 05 07 00 02 03
- The inner pass (first when encoding) takes the value of the twelfth byte and XORs each of the other bytes with that value
- The outer pass (first when decoding) XORs each byte in a
pattern equivalent to the following hex key:
- A checksum is stored in the twenty-third byte of the
unscrambled password as well as two of the bits in the twenty-sixth
byte
checksum = unscrambled_password[0xb] | ((unscrambled_password[0x16] & 0x18) << 2)
- The checksum is the (rolling 7-bit) sum of each byte in the unscrambled password and also each of the elements in a special unpacked loading structure
- The specially-unpacked loading structure is an array of 20
16-bit words (called
a4
here) unpacked from the first 24 bytes of the unscrambled password (calleda3
here)
a4[0] = a3[0] & 3; // rank
a4[2] = (a3[0] & 0xc) >> 2; // character class
a4[4] = (a3[0] & 0x10) >> 4; // fast search (bool)
a4[6] = (a3[1] << 10) | (a3[2] << 5) | (a3[3]); //villains captured (bits)
a4[8] = a3[4] & 3; // difficulty
a4[0xa] = (a3[4] & 0xc) >> 2; // continents discovered
a4[0xc] = (a3[5] << 9) | (a3[6] << 4); //weekly income
a4[0xe] = (a3[7] << 9) | (a3[8] << 4) | ((a3[9] & 0x18) << 0xb); // maybe resurrected ghosts count
a4[0x10] = (a3[9] & 7) | (a3[0xa] << 3); // time left (saves as weeks, but loads as days?)
a4[0x12] = (a3[0xd] << 9) | (a3[0xe] << 4) | ((a3[0xf] & 0x18) << 0xb); // some leadership thing maybe points required for next rank
a4[0x14] = (a3[0xf] & 0x4) >> 2; // has learned magic
a4[0x16] = (a3[0xf] & 0x2) >> 1; // has siege weapons
a4[0x18] = (a3[0xf] & 1) | (a3[0x10] << 1) // learnable spell count (stored as half)
a4[0x1a] = (a3[0x11] & 0xf);
a4[0x1c] = (a3[0x12] & 0xf);
a4[0x1e] = (a3[0x13] & 0xf);
a4[0x20] = ((a3[0x11] & 0x10) >> 1) | ((a3[0x12] & 0x10) >> 2) | ((a3[0x13] & 0x10) >> 3) | (a3[0x14] & 1);
a4[0x22] = (a3[0x14] & 0x1e);
a4[0x24] = (a3[0xc] << 5) | a3[0x15];
a4[0x26] = (a3[0x16] & 7) | (a3[0x17] << 3); // artifacts found (bits)
A run of five bytes starting at a3[0x18]
is the
troop type for each of the player's five army slots. The value 31
('6') represents no army in that slot.
The next pair of five-byte runs (starting at
a3[0x1d]
and a3[0x22]
) encode the number
of troops in each of the player's five army slots.
troop_count[i] = (a3[0x1d + i] | (a3[0x22 + i] << 5)) << 2
The byte at a3[0x27]
is a random number, just there
for checksum/scrambling.
The 15-byte stretch starting at a3[0x28]
are the
carried spells.
Gold on hand is a combination of the pair of bytes at
a3[0x36]
and a3[0x37]
.
gold = (a3_36 << 15) + (a3_37 << 9)
One might expect there would be a third byte for the lowest 8 bits but those bits don't seem to appear anywhere in the password. There is evidence that gold was planned to be stored with dynamic precision but the real released passwords are fixed at 10 bits at a 512-gold resolution.
Code
Much of the game code appears to have written in the C language.
From around 0x10a4 to 0x18b0b, there are a bunch of C functions that follow the same conventions:
- Each function begins with bytes 0x4e 0x56
(
link a6, nnnn
) - The
a6
register is used as base pointer for the function (the stack at call-time) - The
a7
register is used as the stack - The function is responsible for restoring any registers that it writes to beyond the volatile list: a0, a1, d0, d1
- All arguments are on the stack, never by register
- The function signals a return value (if any) by writing to the
d0
register - The
a5
register is a constant pointer to the start of the jump table (at ROM offset 0x312) - Positive a5 offsets look into the jump table
- Negative a5 offsets are for accessing static global variables in RAM
- Each function ends with the bytes 0x4e 0x5e 0x4e 0x75
(
unlk a6
;rts
) and then its label - The label is a length byte (with the high bit set) followed by a null-terminated ASCII string (there may be additional null bytes for alignment)
Some other functions seem to follow most of the same conventions but lacks the label at the end.
Multiplication tip
A pattern that occurs frequently in the code that tripped me up in the early days was a pair of multiplications, a pair of swaps, a clear and an addition. An example is the sequence of bytes:
2001 c2fc0019 4840 c0fc0019 4840 4240 d280
which is the assembled version of the following:
move.l d1, d0
mulu.w 0x19, d1
swap d0
mulu.w 0x19, d0
swap d0
clr.w d0
add.l d0, d1
I believe this was compiled from a fragment of C code that read
something like i * 0x19
in a fragment like the
following:
int i;
for (i = 0; i < 5; i++) {
Print(messages[i * 0x19], 2, i);
}
Not covered
RAM layout at runtime- The publisher/developer intro animation at game boot
- Specific game logic (eg damage calculations)
Translation/modding guide for text etc- Anything about the author
- Who to contact for corrections or clarifications etc