Riven tBMP resources
This page shows the structure of Riven tBMP resources, which store game bitmaps.
General tBMP structure
tBMP resources store 8-bit and 24-bit bitmaps, which can be plain or compressed. Everything is in big-endian order. Each resource begins with this structure:
|unsigned short||bitmap width (pixels)|
|unsigned short||bitmap height (pixels)|
|unsigned short||bytes per row|
|unsigned char||compression flag|
|unsigned char||truecolor flag|
If the truecolor flag is 4 then the bitmap is a 24-bit image, and the structure goes on with height rows, each row following this structure:
|width*3 unsigned chars||pixel values (BGR, BGR, BGR...)|
|width unsigned chars||unknown (zero)|
Note that there is only one 24-bit bitmap in the whole game! It's tBMP 24 in b2_data.mhk, and it's a secret image.
If the truecolor flag is not 4 the tBMP is a normal 8-bit bitmap and after the initial fields we find:
|unsigned long||unknown (seems always 0x030418ff)|
|256*3 unsigned chars||color table (BGR, BGR, BGR...)|
If the compression flag is 0, the bitmap is uncompressed: pixel data follows immediately as a simple block of pixels. Each pixel is a byte representing an index in the color table. Rows may be padded with filling bytes to reach the specified number of bytes per row; just discard those extra bytes. Otherwise, if the compression flag is 4 then the bitmap is compressed with a proprietary format (explained below); the compressed data block follows immediately.
In compressed tBMP bitmaps, pixels are encoded as a data stream made of variable length commands. Pixels are always decoded in duplets: each command generates at least 2 pixels. The encoding is heavily based on what comes before each command, so even a little decoding bug can cripple the whole image. The commands can appear in any order inside the data stream. The first 4 bytes of the data stream are unknown and can be ignored. Many thanks to Arthur Muller for his precious help in decoding this format. Main commands
They are all 1-byte commands, followed by a variable number of arguments.
|0x00||End of stream: when reaching it, the decoding is complete. No additional bytes follow. I think some bitmaps don't have this, so just stop when you have decoded enough pixels to fill the image.|
|0x01 -0x3f||Output n pixel duplets, where n is the command value itself. Pixel data comes immediately after the command as 2*n bytes representing direct indices in the 8-bit color table.|
|0x40-0x7f||Repeat last 2 pixels n times, where n = command_value & 0x3F. No additional bytes follow.|
|0x80-0xbf||Repeat last 4 pixels n times, where n = command_value & 0x3F. No additional bytes follow.|
|0xc0-0xff||Begin of a subcommand stream. This is like the main command stream, but contains another set of commands which are somewhat more specific and a bit more complex. This command says that command_value & 0x3F subcommands will follow. It doesn't generate pixels itself.|
Subcommands, part 1: arithmetic operations
Subcommands are not simply 1-byte values, but are somewhat mixed with their arguments, so the full byte pattern is reported.
|0x01-0x0f||0000mmmm||Repeat duplet at relative position -m, where m is given in duplets. So if m=1, repeat the last duplet.|
|0x10||0x10 p||Repeat last duplet, but change second pixel to p.|
|0x11-0x1f||0001mmmm||Output the first pixel of last duplet, then pixel at relative position -m. m is given in pixels.|
|0x20-0x2f||0010xxxx||Repeat last duplet, but add x to second pixel.|
|0x30-0x3f||0011xxxx||Repeat last duplet, but subtract x to second pixel.|
|0x40||0x40 p||Repeat last duplet, but change first pixel to p.|
|0x41-0x4f||0100mmmm||Output pixel at relative position -m, then second pixel of last duplet.|
|0x50||0x50 p1 p2||Output two absolute pixel values, p1 and p2.|
|0x51-0x57||01010mmm p||Output pixel at relative position -m, then absolute pixel value p.|
|0x59-0x5f||01011mmm p||Output absolute pixel value p, then pixel at relative position -m.|
|0x60-0x6f||0110xxxx p||Output absolute pixel value p, then (second pixel of last duplet) + x.|
|0x70-0x7f||0111xxxx p||Output absolute pixel value p, then (second pixel of last duplet) - x.|
|0x80-0x8f||1000xxxx||Repeat last duplet adding x to the first pixel.|
|0x90-0x9f||1001xxxx p||Output (first pixel of last duplet) + x, then absolute pixel value p.|
|0xa0||0xa0 xxxxyyyy||Repeat last duplet, adding x to the first pixel and y to the second.|
|0xb0||0xb0 xxxxyyyy||Repeat last duplet, adding x to the first pixel and subtracting y from the second.|
|0xc0-0xcf||1100xxxx||Repeat last duplet subtracting x from first pixel.|
|0xd0-0xdf||1101xxxx p||Output (first pixel of last duplet) - x, then absolute pixel value p.|
|0xe0||0xe0 xxxxyyyy||Repeat last duplet, subtracting x from first pixel and adding y to second.|
|0xf0 and 0xff||0xfx xxxxyyyy||Repeat last duplet, subtracting x from first pixel and y from second.|
Subcommands, part 2: repeat operations
Repeat n duplets from relative position -m (given in pixels, not duplets). If r is 0, another byte follows and the last pixel is set to that value. n and r come from the table on the right.
|0xfc||0xfc nnnnnrmm mmmmmmmm (p)||Repeat n+2 duplets from relative position -m (given in pixels, not duplets). If r is 0, another byte p follows and the last pixel is set to absolute value p.|