lowmiptextures - themeldingwars/Documentation GitHub Wiki

lowmiptextures.pak lives in system/vt next to the virtual texture files. It is the low mip fallback pack: the smallest mip levels of every texture in the virtual texture, bundled into one archive so the renderer always has something to draw while the real tiles are still being streamed in. The client side of this is lowmiptexturecache.cpp, and fx.lowMipTextureThreshold is the cvar that decides when it gets used.

Unlike vtex it is a completely ordinary archive: an LZMA compressed index followed by one LZMA compressed block per texture. Everything needed to unpack it is in the file itself, no .vtex_idx involved.

The index is essentially the DDS header of each full size texture, so turning an entry back into a .dds with only the stored mips is a matter of writing that header with the size fields shifted down by firstMip and appending the data. The 1962 pak has 2'464 entries, FauFau decodes all of them.

010 Editor Templates

Implementations

  • Forgery unpacks it, see the vtex test-mips command in forgery.js
  • FauFau reads it and writes the entries out as DDS files
  • Client: the loader is at 0x012b44b0 in build 1962.

Format

Little endian.

Container

Field Type Notes
magic char[4] LMTD
indexOriginalSize uint32 Decompressed size of the index
indexCompressedSize uint32 Compressed size of the index, including the LZMA properties
index byte[indexCompressedSize] LZMA
blocks byte[] The texture data, to the end of the file

blocks starts at offset 12 + indexCompressedSize. Call that the base offset, every compressedOffset in the index is relative to it.

Every LZMA stream in the file, the index and each block, is 5 property bytes followed by the raw stream. There is no 8 byte size like in an .lzma file, the decompressed size always comes from the surrounding fields.

Index

Once decompressed:

Field Type
rowCount uint32
rows Row[rowCount]

Row

The fields after the path are the fields of a DDS_HEADER for the full size texture, in the same order, without the size field and the reserved ones. The client copies them straight into a DDS header.

Field Type Notes
path cstring Asset path of the texture, like 00118000\00118090.dds
flags uint32 dwFlags
width uint32 Of the full size texture
height uint32 Of the full size texture
depth uint32
pitchOrLinearSize uint32 Of the full size texture
mipMapCount uint32 Of the full size texture
pixelFormat byte[32] DDS_PIXELFORMAT, the FourCC like DXT5 is at offset 8
caps byte[16] dwCaps to dwCaps4
firstMip uint32 Index of the largest stored mip
decompressedSize uint32 Size of this texture's data once decompressed
compressedOffset uint32 Offset of the block, relative to the base offset
compressedSize uint32 Size of the block, including the LZMA properties

Rows are variable length because of the leading path string, so the index has to be walked from the start.

Blocks

Each block is a standalone LZMA stream with its own properties: slice compressedSize bytes at baseOffset + compressedOffset and decompress to exactly decompressedSize bytes. The rows appear in offset order in practice, but nothing depends on that.

The data holds the mips from firstMip down to the smallest one, back to back, the same way they'd follow the header in a .dds. To build the DDS header for it, shift width and height right by firstMip (at least 1), take mipMapCount - firstMip as the mip count and shift pitchOrLinearSize by firstMip if flags has the pitch bit (0x8) set, by 2 * firstMip otherwise.