Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

TGA (Truevision Targa)

TGA is the standard uncompressed image format utilized by the engine, typically reserved for UI elements, icons, or high-fidelity models that demand lossless alpha channels.

At a Glance

PropertyValue
Extension(s).tga
Magic SignatureTruevision Standard
TypeUncompressed RGB/A Raster
Rust ReferenceView rakata_formats::Tga in Rustdocs

File Layout

The standard Truevision structure: an 18-byte header, then up to four optional or variable-length regions.

BlockSizeNotes
Header18 bytesAlways at 0x00
Image IDid_len bytesUsually absent
Colour mapper the colour map specAbsent for true-colour images
Pixel dataderived from dimensions and depth
Footer / extensions26 or 521 bytesOn some toolset-written textures, never on engine output. See below.

Header (18 bytes)

OffsetFieldTypeDoes K1 read it?
0x00id_lenu8No
0x01color_map_typeu8No
0x02image_typeu8No
0x03colour map spec5 bytesNo
0x08x_originu16No
0x0Ay_originu16No
0x0Cwidthu16Yes
0x0Eheightu16Yes
0x10pixel_depthu8Yes, and it is the only thing validated
0x11image_descriptoru8No

Warning

A run-length encoded TGA is not rejected. It renders as garbage. image_type is one of the bytes the engine never looks at, so nothing distinguishes an RLE file from an uncompressed one at load. The compressed bytes are read as though they were raw pixels, at the dimensions and depth the header declares, and the result is drawn.

This follows from the table above rather than adding to it, and it is worth stating because “the engine ignores the type byte” reads like tolerance until you notice what it means for a format the byte exists to distinguish. Do not write RLE, and do not expect a reader that accepts it to be doing you a favour.

That last column is the point of this page. Of eighteen header bytes the engine reads dimensions and depth and disregards the rest, so a KotOR .tga is far less constrained than a Truevision one, and correspondingly a file that a general-purpose image tool considers well-formed can still be one the engine refuses, since pixel_depth outside 8, 24 or 32 is a hard failure while everything else passes unexamined.

Important

The column answers a reader’s question, not a writer’s These are fixed-position bytes in an eighteen-byte header, so No cannot mean “omit it”: the byte exists whatever you put there. It means any value passes the load.

A writer aiming at canonical output wants the opposite answer, and this page has it further down: the engine’s own writer hardcodes all three, to 0 for id_len, 2 for image_type and 0 for image_descriptor. So “anything loads” and “vanilla writes one specific value” are both true, and which you want depends on whether you are producing a file the engine accepts or one that matches what shipped. See the general rule.

Pixel data

DepthLayoutNotes
32B, G, R, AAlpha is the fourth byte, and near-opaque in most vanilla textures.
24B, G, R
8one greyscale sampleEvery vanilla 8-bit file carries image_type = 3, uncompressed greyscale, so no ordering question arises.

Blue first. This is standard Truevision order and it is the single easiest thing to get wrong here, because the wrong guess parses perfectly and silently swaps red and blue in every texture in the game.

Note

How the order was established, since the obvious test gives the wrong answer Comparing channel means across the vanilla corpus points at RGBA, and that result is an artifact: lightmaps dominate the shipped textures and are not natural imagery.

The decisive test pairs a .tga against a .tpc of the same texture at the same dimensions, because DXT stores colour as RGB565 whose bit layout is fixed by the S3TC standard, which is ground truth from outside this manual. Across every such pair the sign of the DXT red-minus-blue difference agrees with b2 - b0. The skin textures settle it outright, since human skin is always R > G > B: p_joleeh01 reads (123, 70, 47) from DXT and (36, 51, 87) from the TGA. The largest DXT channel is red; the largest TGA byte is b2. Depth 24 was confirmed the same way.

Warning

Footers depend on which program wrote the file Two producers put .tga files on disk and they behave differently, so a single rule covers neither.

Toolset output, meaning the textures shipped in a full install: a small fraction carry a Truevision footer, each ending with the literal TRUEVISION-XFILE\0. Most of those carry a 495-byte extension area as well, for a 521-byte trailer; the rest carry the 26-byte footer alone.

Engine output, meaning the Screen.tga the game writes into a save folder: no footer at all, in every screenshot measured. The header plus payload accounts for the file exactly, with nothing after it.

This matters in both directions and only for the first producer. A reader that computes the pixel payload’s extent from the header and treats whatever follows as absent mis-handles a slice of the shipped textures. A writer that reproduces header and payload and stops loses the extension area on the files that have one. The region is not addressed by any offset in the header. It is found by looking at the end of the file, so it is easy to write a parser that never notices it exists.

What the engine’s own writer emits

Save screenshots are the only files in this project’s reach that the game itself produced, which makes them the one direct check on the write path documented below. Every claim there was derived from decompilation with no files behind it. All of them hold.

PropertyEngine output
image_type2, uncompressed true-colour, without exception
id_len0, so no image ID block
image_descriptor0, including the origin bit
Pixel depth24-bit, so no alpha channel
Dimensions256 x 256
color_map_type and colour map speczero throughout
x_origin, y_originzero
Footernone

The file length is exactly the header plus width x height x 3, with no slack, which is what confirms the header fields are being read correctly rather than merely being plausible.

The vertical flip is confirmed too, and by looking rather than by parsing: decoding a screenshot both ways and rendering it, honouring the bottom-left origin puts the ceiling at the top and the figure upright, while a naive top-left read hangs the figure from the ceiling. image_descriptor bit 5 is clear in every one, so the file declares bottom-left as well as being written that way.

Warning

Origin is not negotiable, and nothing in the file will tell you image_descriptor at 0x11 is the byte that normally carries the origin bit, and the engine ignores it. Its own writer hardcodes the field to 0 and flips the image vertically on the way out, because its in-memory raster is top-left while its on-disk convention is bottom-left. So orientation is a convention the format cannot express here: a top-left file written with the descriptor set correctly will still load upside down.

Engine Audits & Decompilation

Read from ImageReadTGAHeader at 0x0045e2e0 in swkotor.exe. Provenance: derived, not attested unless a claim says otherwise: the rows have not been separately re-derived, so they sit on the reverse-engineering queue. Individual claims below may carry a level of their own, and where one does it overrides this line for that claim.

Pipeline EventGhidra Provenance & Engine Behavior
Header StrippingFunction: ImageReadTGAHeader (0x0045e2e0)
The native engine parser is exceptionally loose. Standard Truevision fields such as image_type (offset +0x02), image_descriptor (offset +0x11 governing the origin bit), and the id_len field are completely ignored and never validated during a read sequence.
Depth ValidationFunction: ImageReadTGAHeader (0x0045e2e0)
The sole structural validation check performed before memory allocation dictates that the pixel_depth must strictly equal 8, 24, or 32. Any other depth integer triggers an immediate process failure.
Write GenerationFunction: ImageWriteTGA
The engine’s in-memory rasterization is strictly top-left, but its canonical on-disk .tga format is entirely bottom-left. When saving screenshot files or extracting buffers to disk, the engine forcefully accommodates this by hardcoding image_type=2, id_len=0, and image_descriptor=0, explicitly triggering an ImageFlipY vertical inversion on the memory payload before pushing the image to disk.