Repository navigation
vtex
Virtual Texture. The physical map backing the game's virtual texturing system, holding compressed tiles for each mip level.
There is one file per mip level, static.vtex0 through static.vtex6, and they have no header
of their own. They are pure tile data, and are only usable together with
static.vtex_idx, which holds a table of offset, size and crc per tile for
each of the seven files. Pulling a tile out is therefore:
- Decompress the tile info block for mip level
Nfrom the.vtex_idx. - Look up the row for the tile you want.
- Slice
sizebytes atoffsetout ofstatic.vtexN. - Decode the tile, see Tile.
FauFau decodes all 980'560 tiles of the 1962 install except two, see Broken tiles.
- vtex.bt - a stub, there is no container structure to describe
- FauFau decodes the tiles with
VTexIndex.ReadTileandVTexTile - Client:
ReadVTileis at0x012a0bf0and the tile decoder at0x012990c0in build 1962.
A decoded tile is three layers of 128 x 128 pixels, each layer 1'024 DXT5 blocks, i.e.
0x4000 bytes per layer and 0xC000 bytes for the whole tile.
Little endian, 9 bytes:
| Field | Type | Notes |
|---|---|---|
properties |
byte[5] |
LZMA properties, the lc/lp/pb byte and the dictionary size |
size |
uint24 |
Decoded size, always 0xC000
|
mode |
uint8 |
How the rest of the tile is stored |
| Mode | Data after the header |
|---|---|
0 |
Entropy coded DXT end points, not used by the files of build 1962 |
1 |
One raw LZMA stream that decodes to the three layers back to back |
13 |
The three layers uncompressed, not used by the files of build 1962 |
15 |
Same as 1, the client handles both the same way |
16 |
The DXT5 fields split into planes, in three LZMA streams |
The LZMA streams have no .lzma header, the decoded size comes from the tile header. In
build 1962, level 0 has 2'304 tiles in mode 1, 344'128 in mode 15 and 386'311 in mode 16.
The smaller levels mostly use 1 and 16.
The three bytes after the header are the first property byte of each stream, the dictionary size from the header is shared. The streams follow from offset 12, back to back without a length in front of them, so each stream ends where the LZMA decoder stops reading. After the third stream there are 12 more bytes that the decoder doesn't need.
| Stream | Decoded size | Contents |
|---|---|---|
| 1 | 0x3000 |
Color indices, 0x1000 bytes per layer |
| 2 | 0x4800 |
Alpha indices, 0x1800 bytes per layer |
| 3 | 0x4800 |
Color end points (0x1000 per layer), then alpha end points (0x800 per layer) |
Decoded back to back that gives a 0xC000 byte buffer of planes, and every plane holds the
field for all three layers, layer 0 first. DXT5 block b of layer l is put back together
from:
| Block bytes | Field | Plane offset |
|---|---|---|
0..1 |
Alpha end points | 0xA800 + l * 0x800 + b * 2 |
2..7 |
Alpha indices | 0x3000 + l * 0x1800 + b * 6 |
8..11 |
Color end points | 0x7800 + l * 0x1000 + b * 4 |
12..15 |
Color indices | l * 0x1000 + b * 4 |
Presumably the point of the split is that LZMA finds a lot more repetition in the planes than in the interleaved blocks.
The crc of a tile in the vtex_idx is the standard CRC-32 (zlib) of each of the three
decoded layers, XORed together. The client checks it after decoding and retries the read up to
30 times, a second apart, logging ReadVTile crc failure or no or tileInfos!.
Tiles 40068 and 40069 of static.vtex2 are broken in the shipped data. 40068 starts with
the same bytes as the tile before it and fails to decode, 40069 has a header that makes no
sense (mode 119 and a size of over 10 MB). The copy in the archive is identical, so this
isn't a bad download.
For the texture compression here are some papers on the topic:
- Virtual Texturing - Andreas Neu (2010)
- DXT Compression, Codebooks and Sliding Windows - Jon Olick (2013)
- How to Compress an Index Buffer - Jon Olick (2021)
- vtex_idx - the index, and the only way to address anything in these files
- lowmiptextures - the low mip fallback pack, which is a separate and much simpler format