Dangerous Dave Tileset Format

From ModdingWiki
Jump to navigation Jump to search
Dangerous Dave Tileset Format
Ddave-tileset-vga.png
Format typeTileset
HardwareCGA, EGA, VGA
Max tile count232-1
PaletteCGA 1i, Default EGA, shared VGA
Tile names?No
Minimum tile size (pixels)16×16 (0-53), 0×0 (54+)
Maximum tile size (pixels)16×16 (0-53), 65535×65535 (54+)
Plane count1 (CGA/VGA), 4 (EGA)
Plane arrangementLinear CGA, Row-planar EGA, Linear VGA
Transparent pixels?No
Hitmap pixels?No
Metadata?None
Supports sub-tilesets?No
Compressed tiles?No
Hidden data?No
Games

Apart from the interface graphics (menu, font, etc.) Dangerous Dave stores its graphics in three files in this format. The EGA graphics are stored externally in EGADAVE.DAV, while the CGA and VGA graphics are stored internally in DAVE.EXE.

These graphics in this format are the tiles and sprites, and most images used in-level.

Compression

Both the CGA and VGA data (stored in the .exe) is compressed with Keen 1-3 RLE compression. (This is not counting the LZEXE compression applied to the final .exe as a whole.) The compression applies to the entire file, headers and all. The EGA data (stored in EGADAVE.DAV) is not compressed.

The game allocates a buffer of exactly 90112 (0x16000) bytes for the decompressed tile graphics, so even though the original VGA tileset only has an expanded size of 71238 bytes, there is still some space left for additional graphics.

Due to a bug in the RLE decompression code used by the game, great care must be taken when decompressing the original VGA tile graphics or compressing new tile graphics that are larger than 65,280 (0xFF00) bytes.

The game's decompression code needs to shift its pointers to the next memory segment when it passes offset 0xFF00 in the output stream, but it does not do so correctly. Instead of shifting by the required number of bytes, it shifts by a multiple of 16 that is less than or equal to the number of decompressed bytes. This means if an RLE code finishes at offset 0xFF00, 0xFF10, 0xFF20, etc. then everything is fine. But if it finishes at any offset between these, that data is lost. So an RLE code that finishes at output offset 0xFF04 will have the last four bytes lost. One way of thinking of it is that the game jumps back to offset 0xFF00 before processing the next RLE code, which will then overwrite those four bytes. Since this is a multiple of 16, if the same process put data at 0xFF14 instead (with 20 bytes past the boundary instead of just 4 as in the previous example), then the code would jump back to 0xFF10 instead (as that is the next closest multiple of 16) and again only four bytes would be lost.

To keep things simple for the compressor, no RLE compression code should run across a 65,280 byte boundary, otherwise the game will start dropping bytes and not load the graphics correctly unless the correct number of bytes are duplicated (which is not a good idea due to the limited amount of space available for the graphics inside the .exe file). When compressing modified graphics this can be achieved by only compressing 65,280 bytes at a time.

For example the code 0B 00 ("repeat 0x00 14 times") might have to be changed to 02 00 06 00 ("repeat 0x00 five times, then repeat 0x00 nine times") if the 65,280 byte boundary occurs in the middle of the code. (In this case the boundary would be between the 02 00 code and the 06 00 code.) The last RLE code must result in bytes from index 0 to index 65,279 being written, with the next RLE code outputting byte 65,280. Failing to take this into account when compressing data will result in the decompression code skipping/overwriting between 1 and 15 bytes at this boundary, depending on how many bytes follow the boundary.

For the original VGA tileset data of this game, the RLE processing at the boundary point ends at offset 0xFF01. This offset divides into 16-byte blocks with one remainder byte, thus the last one byte decompressed must be discarded. Failure to take this into account when reading the file will cause the last 10 images in the VGA tileset to appear as though they are 2048 pixels wide instead of 8 pixels wide.

Note that previous versions of this page said an extra byte appeared every 65,536 bytes but this is incorrect - image #157 (the final frame in the "Dangerous Dave" title screen animation) will have the first few rows of the image come out differently to the way it looks in the game if the "padding" byte is every 65,536 bytes. It is also only the original game graphics that have one extra byte - modified graphics may have a different number, or no extra bytes at all if the compressor follows the above block-size rules.

Note that as this is a side effect of the RLE compression, these extra bytes only need to be handled for the CGA and VGA graphics. As the EGA graphics are not compressed, they are not affected by this bug.

File structure

After decompression (if required), the file starts with the number of chunks (graphics) in the file, followed by a number of values giving the offset of each chunk.

Data type Description
UINT32LE count Number of images in the file
UINT32LE offsets[count] Offset of image data
BYTE data[] Image data

There are two types of chunks, those that are 128 bytes in size are taken to be 16x16 tiles (by default the first 53 chunks are these) and consist entirely of raw graphics data with no header. Other chunks start with two UINT16LE values giving the width and height of the graphic in pixels followed by the image data.

Image formats

CGA

Partial CGA tileset

Image data is Linear CGA (like VGA but 2bpp instead of 8bpp.) In other words, it's not split up into planes like the EGA data is. The pixels are broken up in big-endian order within the byte, so this value:

11001001  (0xC9)

Would translate as four pixels of the following values:

11 00 10 01  (3, 0, 2, 1)

The actual colours for each pixel depend on the active CGA palette, of which there are three basic ones. See wp:Color Graphics Adapter for details.

EGA

Partial EGA tileset

All EGA data is stored in the Row-planar EGA arrangement, meaning each graphic is split into rows which are then split into EGA planes.

All graphics have four planes, stored in the order I, R, G, B. Tiles are first, followed by player sprites, enemy sprites, in-level images and in-level font.

This is a very basic implementation of EGA data. The only masked sprite is the player sprite (the only sprite that needs to appear over colored tiles and other sprites - enemy sprites are drawn using XOR to avoid the mask, at the expense of causing colour changes should they ever overlap map tiles.) Masking is accomplished by storing the mask as a second, black and white image - that is, black and white EGA. The mask graphic is the same size, in pixels and bytes, as the sprite image it masks - a very wasteful way of doing things.

Dave is also interesting in that it contains graphics whose width does not divide evenly by eight pixels. As EGA data these are stored as if they divided by the next highest multiple of 8 pixels (a 26x8 image is stored like a 32x8 image, with the 'extra' space being blank.) This is standard for storing images padded to a certain byte width (e.g. .BMP images) however it is not often done in games due to the 'wasted' space.

VGA

Partial VGA tileset

The VGA data is standard 8bpp single-plane data. The palette is stored in the main EXE file (see Dangerous Dave for its location.)

Example code

  • Javascript: tls-ddave in Camoto's gamegraphics.js

Credits

This file format and the EGA + VGA graphics were reverse engineered by Levellass, the RLE algorithm and CGA graphics were reverse engineered by Malvineous, and the quirks of the RLE extra bytes were figured out by K1n9_Duk3. If you find this information helpful in a project you're working on, please give credit where credit is due. (A link back to this wiki would be nice too!)