Riven Image File Format

The tBMP images stored in Riven Mohawk archives are 8-bpp (256-color) palettized bitmaps. Each image file contains a header, a color table, and bitmap data, which is usually compressed.

The header specifies the dimensions and format of the image:

OffsetSizeDescription
0x0000WordThe width of the image in pixels.
0x0002WordThe height of the image in pixels.
0x0004WordThe number of bytes per row of pixels.
0x0006Word0x000A if the bitmap data is uncompressed; 0x040A if the bitmap data is compressed.
0x0008WordThe value 0x0304. (Perhaps this is the offset of the bitmap data relative to this field.)
0x000AWordThe value 0x18FF. (Unknown)

Note: “Word” and “double word” denote two- and four-byte big-endian integers.

Color Table

The color table is an array of 256 red-green-blue triplets that specify the colors used in the image:

OffsetSizeDescription
0x000CByteColor 0: Blue.
0x000DByteColor 0: Green.
0x000EByteColor 0: Red.
0x000FByteColor 1: Blue.
0x0010ByteColor 1: Green.
0x0011ByteColor 1: Red.
. . .
0x0309ByteColor 255: Blue.
0x030AByteColor 255: Green.
0x030BByteColor 255: Red.

Bitmap Data

When uncompressed, the bitmap data consists of the color-table index for each pixel in the bitmap, in order from left to right and top to bottom. Each index is one byte. If necessary, up to three zero-valued bytes pad every row of indexes so that each row ends on a four-byte boundary.

For uncompressed images, the bitmap data immediately follows the color table:

OffsetSizeDescription
0x030CByteThe first byte of uncompressed bitmap data (the color-table index for the top-left pixel in the bitmap).

For compressed images, a four-byte value precedes the compressed bitmap data:

OffsetSizeDescription
0x030CDWord(Unknown)
0x0310ByteThe first byte of compressed bitmap data (the opcode of the first instruction).

Decompression Algorithm

When compressed, the bitmap data is a series of variable-length “instructions”. Each instruction consists of a one-byte opcode followed by zero or more operands. There are many different opcodes; each opcode specifies an operation that outputs one or more words of uncompressed data (color-table indexes and padding).

The Riven image decompressor uses two distinct sets of opcodes, which are documented in the sections below. Initially, the decompressor uses Instruction Set 1. It uses Instruction Set 2 when directed to do so.

The instruction set tables use the following conventions:

SymbolDescription
N, X, YRepresents a value ranging from 0x00 to 0xFF.
[n]Represents a byte with the value n.
<n>Represents the value of the n-th previous output byte, counting bytes already output by the current instruction. For example, <1> represents the value of the last byte that was output.
{n}Indicates that the previous term is repeated n times. For example, [<4>]{2} stands for [<4>] [<4>].
..Indicates a range of consecutive values. For example, 1..3 represents the values 1, 2, and 3.

Instruction Set 1

InstructionOutput BytesDescription
[0x00]NoneStop. (This instruction terminates the compressed bitmap data.)
[0x00+n] [X1] ... [X2n]; n=2..31[X1] ... [X2n]Output n words of raw data.
[0x40+n]; n=1..31[<2>]{2n}Copy the last word n times.
[0x80+n]; n=1..31[<4>]{4n}Copy the last two words n times.
[0xC0+n]; n=1..63Execute the next n instructions using Instruction Set 2.

The remaining opcodes (0x01, 0x20–0x40, 0x60–0x80, and 0xA0–0xC0) are unused.

Instruction Set 2

In the following table, the variables n, r, and s represent hexadecimal digits ranging from 1 to F.

InstructionOutput BytesDescription
[0x00](Unused)
[0x0n][<2n>] [<2n>]Copy the n-th previous word.
[0x10] [X][<2>] [X]Copy the last word; replace its second byte by X.
[0x1n][<2>] [<n>]Copy the last word; replace its second byte by the n-th previous byte.
[0x20](Unused)
[0x2s][<2>] [<2>+s]Copy the last word; increment its second byte by s.
[0x30](Unused)
[0x3s][<2>] [<2>−s]Copy the last word; decrement its second byte by s.
[0x40] [X][X] [<2>]Copy the last word; replace its first byte by X.
[0x4n][<n>] [<2>]Copy the last word; replace its first byte by the n-th previous byte.
[0x50] [X] [Y][X] [Y]Output X and Y.
[0x50+k] [X]; k=1..7[<k>] [X]Copy the k-th previous byte and output X.
[0x58](Unused)
[0x58+k] [X]; k=1..7[X] [<k>]Output X and copy the k-th previous byte.
[0x60](Unused)
[0x6s] [X][X] [<2>+s]Copy the last word; replace its first byte by X and increment its second byte by s.
[0x70](Unused)
[0x7s] [X][X] [<2>−s]Copy the last word; replace its first byte by X and decrement its second byte by s.
[0x80](Unused)
[0x8r][<2>+r] [<2>]Copy the last word; increment its first byte by r.
[0x90](Unused)
[0x9r] [X][<2>+r] [X] Copy the last word; increment its first byte by r and replace its second byte by X.
[0xA0] [rs][<2>+r] [<2>+s]Copy the last word; increment its first byte by r and increment its second byte by s.
[0xA1]
[0xA2]
[0xA3]
(Unused)
[0xA4+k] [N] [X]; k=0..3[<N+256k>]{3} [X]Copy 3 bytes starting at the (N + 256k)-th previous byte. Then output X.
[0xA8+k] [N]; k=0..3[<N+256k>]{4}Copy 4 bytes starting at the (N + 256k)-th previous byte.
[0xAC+k] [N] [X]; k=0..3[<N+256k>]{5} [X]Copy 5 bytes starting at the (N + 256k)-th previous byte. Then output X.
[0xB0] [rs][<2>+r] [<2>−s]Copy the last word; increment its first byte by r and decrement its second byte by s.
[0xB1]
[0xB2]
[0xB3]
(Unused)
[0xB4+k] [N]; k=0..3[<N+256k>]{6}Copy 6 bytes starting at the (N + 256k)-th previous byte.
[0xB8+k] [N] [X]; k=0..3[<N+256k>]{7} [X]Copy 7 bytes starting at the (N + 256k)-th previous byte. Then output X.
[0xBC+k] [N]; k=0..3[<N+256k>]{8}Copy 8 bytes starting at the (N + 256k)-th previous byte.
[0xC0](Unused)
[0xCr][<2>−r] [<2>]Copy the last word; decrement its first byte by r.
[0xD0](Unused)
[0xDr] [X][<2>−r] [X]Copy the last word; decrement its first byte by r and replace its second byte by X.
[0xE0] [rs][<2>−r] [<2>+s]Copy the last word; decrement its first byte by r and increment its second byte by s.
[0xE1]
[0xE2]
[0xE3]
(Unused)
[0xE4+k] [N] [X]; k=0..3[<N+256k>]{9} [X]Copy 9 bytes starting at the (N + 256k)-th previous byte. Then output X.
[0xE8+k] [N]; k=0..3[<N+256k>]{10}Copy 10 bytes starting at the (N + 256k)-th previous byte.
[0xEC+k] [N] [X]; k=0..3[<N+256k>]{11} [X]Copy 11 bytes starting at the (N + 256k)-th previous byte. Then output X.
[0xF0] [rs][<2>−r] [<2>−s]Copy the last word; decrement its first byte by r and decrement its second byte by s.
[0xF1]
[0xF2]
[0xF3]
(Unused)
[0xF4+k] [N]; k=0..3[<N+256k>]{12}Copy 12 bytes starting at the (N + 256k)-th previous byte.
[0xF8+k] [N] [X]; k=0..3[<N+256k>]{13} [X]Copy 13 bytes starting at the (N + 256k)-th previous byte. Then output X.
[0xFC] [8m+k] [N] [X]; m=0..31, k=0..3[<N+256k>]{2m+3} [X]Copy (2m + 3) bytes starting at the (N + 256k)-th previous byte. Then output X.
[0xFC] [8m+k+4] [N]; m=0..31, k=0..3[<N+256k>]{2m+4}Copy (2m + 4) bytes starting at the (N + 256k)-th previous byte.
[0xFD]
[0xFE]
[0xFF]
(Unused)

Bytes are incremented and decremented modulo 256. For example, 0xFF + 1 = 0x00, and 0x00 − 1 = 0xFF.

Some opcodes are unused because equivalent opcodes exist: