345 texture pages
2026-10-23, third session. Goal for now: an asset viewer.
One routine for every texture
The track, car, font and chrome loaders all call FUN_0046aca0. Its pseudocode hid the sizes it reads, because Watcom passes them in registers, so this one came from the disassembly. A texture block is a count of pages, an unused word, then for each page a count and a list of 52-byte records, an optional palette of 256 colours, and finally the pixels.
The records are the key to the whole thing. Each holds a name such as route2l2.TGA and the corners of that texture on the page. A page is an atlas: four, eight or eleven small textures packed into one square.
Drawing to find the format
The code says the software build reads 0x10000 bytes per page and a palette in two of its load modes. The files disagreed with that in useful ways, and the quickest way to settle each case was to draw the bytes and look:
- The
.BL3track pages are 256 × 256 at one byte per pixel, with the palette right after the records. They came out right the first time. - The
.BL4track pages drawn as 128 × 128 showed every other row from a different tile: the page was twice as wide as drawn. At 256 × 256 in 16-bit RGB565 they are perfect. - The
.BV4car pages really are 128 × 128 RGB565: half the size of the track pages, with the corners still counted on a 256 grid. - The
.BV3car pages are 8-bit, with no palette in the block. The track's palette gives the wrong colours, so the car palette is still to be found. The viewer draws them in grey.
The spacing of the blocks in the stream backs the drawings up. The sky block in a .BL3 file takes exactly a palette and one 8-bit page; in the .BL4 file the same block takes one 16-bit page.
The sky itself was odd. Drawn square it repeats four times. Drawn 64 pixels wide it becomes a skyline lying on its side, which suggests a 1024 × 64 panorama stored column by column.
Names are messier than they look
The decoder finds blocks by their records, and the first version missed some because of the names. Two have a stray character after .TGA (winnight2.TGA@), one has a stray 0x80 byte there, and the Scorpion car has textures called coté.TGA with a Latin-1 accent. The name field is a plain zero-terminated string, and the decoder now accepts any bytes in it. With that, all 56 texture files decode: 345 pages.
The viewer

CIRCUITS/BELTANE.BL3 pages 0 to 4, VOITURES/ARC.BV4 pages 0 and 1, ALIEN.BV4 page 0, as the first viewer drew them, sub-textures outlined in magenta. © Ubi Soft; shown from our own viewer.
viewer/build.py reads your own copy of the disc and writes a gallery to an ignored folder: every page, with each sub-texture outlined and named. It is the first version of the project viewer. Images, text and sounds come next.
The texture blocks page has the layout and the formats.