Skip to content
Lukas Dürrenberger edited this page Oct 8, 2026 · 5 revisions

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:

  1. Decompress the tile info block for mip level N from the .vtex_idx.
  2. Look up the row for the tile you want.
  3. Slice size bytes at offset out of static.vtexN.
  4. Decode the tile, see Tile.

FauFau decodes all 980'560 tiles of the 1962 install except two, see Broken tiles.

010 Editor Template

  • vtex.bt - a stub, there is no container structure to describe

Implementations

  • FauFau decodes the tiles with VTexIndex.ReadTile and VTexTile
  • Client: ReadVTile is at 0x012a0bf0 and the tile decoder at 0x012990c0 in build 1962.

Tile

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.

Header

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

Modes

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.

Mode 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.

CRC

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!.

Broken tiles

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.

Compression

For the texture compression here are some papers on the topic:

See also

  • 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

Clone this wiki locally