TPC File Format - NickHugi/PyKotor GitHub Wiki

KotOR TPC File Format Documentation

TPC (Texture Pack Container) is KotOR’s native texture format. It supports paletteless RGB/RGBA, Greyscale, and block-compressed DXT1/DXT3/DXT5 data, optional mipmaps, cube maps, and flipbook animations controlled by companion TXI files.

Table of Contents


File Structure Overview

Offset Size Description
0x00 4 Data size (0 for uncompressed RGB; compressed textures store total bytes)
0x04 4 Alpha test/threshold float
0x08 2 Width (uint16)
0x0A 2 Height (uint16)
0x0C 1 Pixel encoding flag
0x0D 1 Mipmap count
0x0E 0x72 Reserved / padding
0x80 Texture data (per layer, per mipmap)
... Optional ASCII TXI footer

This layout is identical across PyKotor, Reone, Xoreos, KotOR.js, and the original Bioware tools; KotOR-Unity and NorthernLights consume the same header.

Implementation: Libraries/PyKotor/src/pykotor/resource/formats/tpc/


Header Layout

Field Description
data_size If non-zero, specifies total compressed payload size; uncompressed textures set this to 0 and derive size from format/width/height.
alpha_test Float threshold used by punch-through rendering (commonly 0.0 or 0.5).
pixel_encoding Bitfield describing format (see next section).
mipmap_count Number of mip levels per layer (minimum 1).
Reserved 0x72 bytes reserved; KotOR stores platform hints here but all implementations skip them.

Reference: Libraries/PyKotor/src/pykotor/resource/formats/tpc/io_tpc.py:112-167


Pixel Formats

TPC supports the following encodings (documented in TPCTextureFormat):

Encoding Description Notes
0x01 (Greyscale) 8-bit luminance Stored as linear bytes
0x02 (RGB) 24-bit RGB Linear bytes, may be swizzled on Xbox
0x04 (RGBA) 32-bit RGBA Linear bytes
0x0C (BGRA) 32-bit BGRA swizzled Xbox-specific swizzle; PyKotor deswizzles on load
DXT1 Block-compressed (4×4 blocks, 8 bytes) Detected via data_size and encoding flags
DXT3/DXT5 Block-compressed (4×4 blocks, 16 bytes) Chosen based on pixel_type and compression flag

Reference: Libraries/PyKotor/src/pykotor/resource/formats/tpc/tpc_data.py:54-178


Mipmaps, Layers, and Animation

  • Each texture can have multiple layers (used for cube maps or animated flipbooks).
  • Every layer stores mipmap_count levels. For uncompressed textures, each level’s size equals width × height × bytes_per_pixel; for DXT formats it equals the block size calculation.
  • Animated textures rely on TXI fields (proceduretype cycle, numx, numy, fps). PyKotor splits the sprite sheet into layers and recalculates mip counts per frame.

Reference: Libraries/PyKotor/src/pykotor/resource/formats/tpc/io_tpc.py:216-285


Cube Maps

  • Detected when the stored height is exactly six times the width for compressed textures (DXT1/DXT5).
  • PyKotor normalizes cube faces after reading (deswizzle + rotation) so that face ordering matches the high-level texture API.
  • Reone and KotOR.js use the same inference logic, so the cube-map detection below mirrors their behavior.

Reference: Libraries/PyKotor/src/pykotor/resource/formats/tpc/io_tpc.py:138-285


TXI Metadata

  • If bytes remain after the texture payload, they are treated as ASCII TXI content.
  • TXI commands drive animations, environment mapping, font metrics, downsampling directives, etc. See the TXI File Format document for exhaustive command descriptions.
  • PyKotor automatically parses the TXI footer and exposes TPC.txi plus convenience flags (is_animated, is_cube_map).

Reference: Libraries/PyKotor/src/pykotor/resource/formats/tpc/io_tpc.py:159-188


Implementation Details

All of the engines listed above treat the header and mipmap data identically. The only notable difference is that KotOR.js stores textures as WebGL-friendly blobs internally, but it imports/exports the same TPC binary format.