LYT (Layout File)
LYT files are ASCII configuration arrays that define the spatial 3D placement and orientation of independent room models to construct a complete area map.
At a Glance
| Property | Value |
|---|---|
| Extension(s) | .lyt |
| Magic Signature | None |
| Type | Plain Text Layout |
| Rust Reference | View rakata_formats::Lyt in Rustdocs |
File Structure
Four count-led sections between a beginlayout and donelayout pair. Each section declares how many entries follow, and the entries are whitespace-separated fields on their own lines. Text is Windows-1252. Counted throughout, with no terminator on any run: see counted, terminated, or neither for why guessing the wrong one is not a graceful failure.
beginlayout
roomcount <N>
<room_model> <x> <y> <z>
trackcount <N>
<track_model> <x> <y> <z>
obstaclecount <N>
<obstacle_model> <x> <y> <z>
doorhookcount <N>
<room_name> <door_name> <reserved> <x> <y> <z> <qw> <qx> <qy> <qz>
donelayout
| Section | Fields per entry | Meaning |
|---|---|---|
roomcount | model, then x y z | A room model and where to place it |
trackcount | model, then x y z | Swoop track geometry |
obstaclecount | model, then x y z | Swoop obstacle geometry |
doorhookcount | room, door, reserved, x y z, then qw qx qy qz | Where a door attaches, with a quaternion orientation rather than a bare position |
Doorhook entries
The doorhook reserved field sits third, between the two names and the position, and it is an integer written as 0. That is its value in every doorhook of every .lyt a retail install ships, so a writer emits 0. It cannot be omitted: the fields are positional and whitespace-separated, so dropping it shifts x into its place and the whole entry reads wrong.
Important
The doorhook quaternion is
wfirst The four floats after the position arew, x, y, z, the same order as GFF’sVector4and the MDL quaternion. Decode them asx, y, z, wand you write back a different orientation than you read.
A door hook is a yaw about the vertical axis, so x and y are zero in all but eight rows across a retail install. Many rows read exactly 1.0 0.0 0.0 0.0, the identity, and the yaws that remain land on multiples of 90 degrees. The eight exceptions sit on angled geometry and carry a populated y or all four components.
Read the same rows as x, y, z, w and the zeroes move to the first two slots for one subset only, the half turns, where w is genuinely 0. That covers a minority of the rows. Reading w first covers all but the eight.
Measured over every .lyt indexed by a retail chitin.key and every doorhook row in them. Provenance: measured.
Warning
The engine stores every doorhook field and reads none of them back
CLYT::LoadLayoutparses each doorhook line into five live arrays: room name, door name, the integer third field, the position and the orientation. Nothing reads them back, so no door is linked to a room through this structure. See what can reach the doorhook arrays.
Write the section anyway. Every .lyt a retail install ships declares it, and the ones with no hooks say so with doorhookcount 0 followed by donelayout.
Keep the count matching the rows that follow it. No shipped file disagrees, so the engine’s behaviour on a mismatch is untested, the parse loop does not verify its own terminator (see Boundary Oversight below), and the arrays are sized from the declared count before anything else can intervene.
Omitting the section is survivable but pointless: the parser meets donelayout, never reaches the branch that reads the count, and leaves it at zero.
Reports of memory corruption in modules with the doorhook section stripped come from the parse. Nothing is reading these values back.
Section order
The section order is not a convention. The engine’s parser walks them sequentially, and a file presenting them out of order will not load.
Note
The four sections cascade, and only the tail is optional The parser reads
roomcountthe moment it findsbeginlayout, with nodonelayouttest first, so you can never skip the room section.After that it tests each following line against the literal
donelayoutbefore treating it as the next section’s count. A match ends the file there and the remaining sections go unread. A non-match means the parser takes that line astrackcount Nwhatever it actually says, then runs the same test beforeobstaclecountand again beforedoorhookcount.So a file may stop early, and shipped layouts do exactly that in effect by declaring the later sections empty. What a file may not do is skip a section in the middle: drop
trackcountwhile keepingobstaclecountand the parser reads the obstacle count as the track count, putting everything after it in the wrong section.A count keyword followed by no number is a third case. The read is an unchecked
sscanf: the conversion fails, writes nothing, and returns a value nobody examines, so the count keeps what it already held. Every caller supplies a fresh instance, so that is zero.Provenance: traced.
Unchecked counts
Warning
No count in this format is bounds-checked before it sizes an allocation All four sections behave alike.
roomcount,trackcount,obstaclecountanddoorhookcountare each read as a plain integer and multiplied into a byte size, with no upper-bound check anywhere in the loader. The parse loop then writes that many elements into whatever the allocation turned out to be.A large enough count overflows the multiplication into an allocation smaller than the loop is about to fill. This belongs to the layout parser as a whole rather than to any one section, and it holds whether or not anything downstream reads the values.
Provenance: traced, as far as the missing check. What a given oversized count does past that point was not followed.
Where Rakata differs
Note
Rakata reads more loosely than the engine does The engine skips everything before
beginlayout, which is what makes the ubiquitous#MAXLAYOUT ASCIIpreamble harmless. Our parser goes further and ignores any line it does not recognise as a known count section, wherever it appears, so files carrying comments or dependency metadata between sections still parse. That is deliberate tolerance on the read side; anything written back out is canonical.
Engine Audits & Decompilation
Read from CLYT::LoadLayout at 0x005de900 in swkotor.exe. Individual claims below carry their own provenance where it is known. Rows with no marker are derived, not attested, and sit on the reverse-engineering queue.
| Pipeline Event | Ghidra Provenance & Engine Behavior |
|---|---|
| Newline Bounds | The parser heavily expects explicit \r\n (CRLF) endings. Scanning extracts target strings utilizing _sscanf("%[^\r\n]", ...) patterns and frequently relies on blind +2 byte pointer leaps to manually clear the terminators. |
| Preamble Skipping | All file lines existing prior to the beginlayout execution marker (such as the ubiquitous #MAXLAYOUT ASCII header) are deliberately skipped and ignored. |
| Sequential Parsing | The structure mandates a rigid sequential ingestion. Data collections must explicitly appear geographically in the exact order: roomcount → trackcount → obstaclecount → doorhookcount → donelayout. |
| Doorhook data is stored and never read | Provenance: traced. Parsed into five live arrays that nothing reads back. Enumerated in full below. |
Warning
Boundary Oversight While the engine systematically verifies
donelayoutboundaries separating the primary collections, the underlying parse loop functionally neglects to verify the finaldonelayoutsignature upon closing thedoorhookssegment.
What can reach the doorhook arrays
Each doorhook line is parsed with a single sscanf into five separate arrays. The integer third field gets an array of its own, so the parser retains it rather than discarding it.
CLYT exposes getters for room, track and obstacle data and none for any doorhook field. CLYT::LoadLayout has two callers, CSWSArea::LoadRoomInfo (0x005073d0) and CSWCArea::LoadArea (0x00607610). Neither references the doorhook arrays, and CLYT::UnloadLayout (0x005de450) releases the resource without reading them.
Nothing reaches the arrays indirectly either. CLYT offers no path a caller could dispatch through, and the instance never outlives the call that loads it.