TXI (Texture Extensions)
TXI files (or TPC appended arrays) are highly forgiving plain-text metadata blocks applied adjacent to graphical files to enforce custom mipmap, bumpmap, or animation shaders.
At a Glance
| Property | Value |
|---|---|
| Extension(s) | .txi |
| Magic Signature | None |
| Type | ASCII Configuration Strings |
| Rust Reference | View rakata_formats::Txi in Rustdocs |
File Structure
One directive per line: a command token, then whatever arguments it takes. There is no header, no terminator, and no required ordering.
<command> <args...>
<command> <args...>
upperleftcoords <count>
<u> <v> <w>
...
lowerrightcoords <count>
<u> <v> <w>
...
Two commands break the one-line rule. upperleftcoords and lowerrightcoords declare a count and are followed by that many coordinate triples on their own lines, which is the only place the format has structure beyond a flat list. A list here takes either that counted form or an endlist terminator, which is the one format in the manual that does both; see counted, terminated, or neither.
Empty lines are ignored, and command matching is case-insensitive. Text is Windows-1252. Rakata offers two write modes: a policy-normalised one that lowercases the command token, and a source-preserving one that keeps whatever casing the input used, for round-tripping someone else’s file without editorialising it. Neither mode consults a list of known commands, because there is no such list: the reader is deliberately a tolerant key-and-arguments parser, matching the engine, which as the audit below records bypasses anything it does not recognise without complaint.
The recognised vocabulary
Three layers run for every line, in order. A directive matching none of them is silently discarded.
Texture directives, matched directly:
| Purpose | Directives |
|---|---|
| Sizing and sampling | defaultwidth, defaultheight, downsamplemax, downsamplemin, mipmap, filter, maptexelstopixels, clamp, filerange |
| Colour and alpha | gamma, alphamean, useglobalalpha, envmapalpha, specularcolor (three floats) |
| Bump mapping | isbumpmap (an int, not a bool), isdiffusebumpmap, isspecularbumpmap, bumpmapscaling, bumpintensity, diffusebumpintensity, specularbumpintensity |
| Environment and animation | cube, isenvironmentmapped, numx, numy, temporary |
| Procedural | proceduretype |
Font directives, delegated on every line regardless of what already matched: numchars, fontheight, baselineheight, texturewidth, spacingR, spacingB, plus the two coordinate lists.
Procedural-controller directives, delegated only once a proceduretype line has appeared earlier in the same file. proceduretype takes one of eight values, each constructing a controller and destroying any previous one, so the last proceduretype line in a file is the one that counts.
Every controller accepts the same base set:
| Directive | Shape |
|---|---|
channelscale, channeltranslate | Multi-line lists of floats, with the same count-or-endlist shape as the coordinate lists |
channelscale0-channelscale3, channeltranslate0-channeltranslate3 | Single values. Writing any one of them for the first time allocates the underlying array as [1.0, 1.0, 1.0, 1.0], so an unwritten channel reads as 1.0 rather than as zero |
distort, distortangle, distortionamplitude, speed | Single values |
Three of the eight add directives of their own, and the other five add nothing:
proceduretype | Adds |
|---|---|
water | forcecyclespeed, anglecyclespeed, waterwidth, waterheight |
arturo | arturowidth, arturoheight |
cycle | fps |
life, perlin, wave, random, ringtexdistort | nothing beyond the base set |
A directive from the wrong controller’s set is not an error. It falls through to the same silent discard as any unrecognised token, so waterwidth under proceduretype arturo does exactly nothing and says exactly nothing.
Important
Four multi-line lists have a second terminator, not two
upperleftcoords,lowerrightcoords,channelscaleandchanneltranslateall normally take a count and then that many lines. If the token after the command fails to parse as an integer, the parser instead reads lines until it meets one whose first word isendlist. So a file can legitimately use either form for any of the four, and a reader that only implements the counted one will consume the rest of the file as list entries when it meets the other.
Tip
An unknown directive is not an error anywhere in the chain The engine bypasses commands it does not recognise without logging or failing the texture load, as the audit below records. That makes TXI unusually safe to extend and unusually easy to get silently wrong: a typo’d command name behaves exactly like a command that does nothing, with no diagnostic on either side. The boolean-parsing note further down is the sharpest case of this.
Engine Audits & Decompilation
Read from CAurTextureBasic::ParseField at 0x00422390 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 Event | Ghidra Provenance & Engine Behavior |
|---|---|
| Invalid Commands | Function: CAurTextureBasic::ParseField (0x00422390)Unknown or unsupported TXI commands are safely bypassed. If the parsed string evaluation fails to match an explicit configuration branch, the subroutine immediately exits without throwing any logger alarms or terminating texture load. |
| Case Agnosticism | Function: CAurTextureBasic::ParseField (0x00422390)Field matching acts strictly case-insensitive (e.g. cMgTxi == cmgtxi). |
| Line Normalization | Function: CAurTextureBasic::ParseField (0x00422390)The native internal engine scanner searches exclusively for LF (\n) bounds. However, if the read targets an active disk file, the underlying standard C fgets call automatically handles CRLF normalization before handing strings to the regex evaluator. |
| Boolean Parsing | Function: Parse_bool (0x00463680)The native Parse_bool validation explicitly performs lowercase scans evaluating against exact variants of "true", "false", "1", or "0". |
Note
Boolean Parsing Nuance Modding documentation often warns against specific formats or keywords (like
decal). Decompilation reveals the universal behavior applied to all boolean flags:
- Missing Space: Keys merged with their arguments (e.g.
"decal1","mipmap0") silently abort. Thefirstword()extractor pulls the merged string, completely failing the target evaluation list.- Separated Numbers: Space-separated numbers (e.g.
"decal 1") are completely structurally valid.firstword()pulls"decal"and hands" 1"off toParse_bool(). Ansscanfstrips the whitespace and evaluates"1"totrue.- Argument-less Flags: Passing just a flag (
"decal") triggers the branch, butParse_boolphysically finds no argument. It fails to match"true","false","1", or"0", silently safely leaving the boolean integer unchanged from its previous memory allocation.