DLG Format (Dialogue Blueprint)
A .dlg file is one conversation: a graph of NPC lines and player replies, plus everything the engine needs to stage it. Each node carries its text and voice-over, the camera framing and fades, the animations to play, and any script to run when it fires.
At a Glance
| Property | Value |
|---|---|
| Extension(s) | .dlg |
| Magic Signature | DLG / V3.2 |
| Type | Dialogue Blueprint |
| Rust Reference | View rakata_generics::Dlg in Rustdocs |
Field Schema
The format’s field families, as an orientation before the full list.
| Category | Covers | Representative fields |
|---|---|---|
| Root Configuration | Conversation-wide rules: skippability, pacing delays, and cinematic-versus-computer type | Skippable, DelayEntry, ConversationType, ComputerType |
| Termination Hooks | Scripts fired when the conversation ends or aborts, plus the ambient audio bed | EndConversation, EndConverAbort, AmbientTrack |
| Node Graph | The NPC entry and player reply nodes, plus the entry points into the graph | EntryList, ReplyList, StartingList |
| Per-Node Delivery | Each node’s localized line, voice-over, camera framing, fades, and follow-up links | Text, VO_ResRef, CameraAngle, RepliesList |
| Cutscene Casting | Stunt-model substitution and animation loops for cinematic participants | StuntList, AnimList |
Engine Audits & Decompilation
Read from CSWSDialog::LoadDialog (0x005a2ae0), cascading through LoadDialogBase (0x0059f5f0) and LoadDialogCamera (0x0059eaa0) in swkotor.exe. Provenance: derived, not attested. The rows below have not been separately re-derived, so they sit on the reverse-engineering queue.
The LoadDialog subroutine processes the root-level conversation configuration before iterating over the nested EntryList and ReplyList. For each of those nodes, it delegates parsing to LoadDialogBase (for text and scripts) and LoadDialogCamera (for viewport directions).
StartingList provides the dialogue entry points, while the StuntList associates cutscene actor models.
Root Conversation Configuration
| Field Category | Engine Property & Type | Notable Default or Behavioural Quirk |
|---|---|---|
| Identity & Rules | CameraModel (ResRef), DelayEntry/Reply (DWord) | CameraModel defaults to an empty resref. DelayEntry and DelayReply default to 0 if missing. |
| Identity & Rules | Skippable (Byte) | Explicitly defaults to 1 (True) if missing. |
| Logic Hooks | EndConversation, EndConverAbort (ResRefs), AmbientTrack | Fire when the dialogue terminates abruptly or via conclusion. Fallback to empty strings "" if missing. |
| Hardware Interfacing | ConversationType (Int) | 0 = Cinematic, 1 = Computer, 2 = Special. Cinematic explicitly unstealths the party. Defaults to 0 if missing, and that default is itself the sentinel that decides the branch: an absent field takes the identical Cinematic path as an explicit 0, indistinguishable at runtime. |
| Hardware Interfacing | ComputerType (Byte) | Only evaluated if ConversationType is 1. Otherwise, standard camera positioning and animations are bypassed. |
| Equipment & Actions | UnequipItems, UnequipHItem, AnimatedCut, OldHitCheck | AnimatedCut forces a global unpauseable state if non-zero. All four default to 0 if missing. |
Shared Dialogue Node Properties (LoadDialogBase)
These fields apply to both entries (NPC spoken) and replies (Player spoken), and are parsed via LoadDialogBase.
| Field | Type | Engine Evaluation |
|---|---|---|
Text | LocString | The spoken localized string. |
Script, Speaker, Quest | Strings/ResRefs | Standard execution scripts and entity mapping. Speaker and Quest both default to an empty string if missing. |
WaitFlags, QuestEntry | DWord | Defaults to 0 if missing; WaitFlags is separately mutated by the Delay special case below, a later write, not its own absent-value. |
Sound, VO_ResRef | ResRef | Sound Fallback: If Sound fails to execute, the engine will attempt to play VO_ResRef. If both fail, the bitmask SoundExists is forcibly downgraded to 0. Both fields independently default to an empty resref at their own read site: each read constructs its own fresh empty default immediately before the call, rather than carrying a value over from the other or from any prior state. |
Delay | DWord | Delay Special Case: If the value is 0xFFFFFFFF, the engine reads from the root DelayEntry/DelayReply field instead and modulates WaitFlags. The field’s own read-time default, confirmed directly against the read call, is a literal 0 rather than 0xFFFFFFFF, so an absent Delay does not by itself trigger the substitution; the file would need to write 0xFFFFFFFF explicitly to reach that path. |
FadeType | Byte | Determines the FadeDelay and FadeLength. If set to 0 or missing, every fade configuration is zeroed. |
Important
PlotIndexandPlotXPPercentageeach fall back to a plain, unconditional literal.PlotIndexreads with a fallback of0, not-1, a value that would look like a deliberate “no plot” sentinel but isn’t what the read call itself falls back to.PlotXPPercentagereads with a fallback of0.0, not1.0.Delay’s own fallback is documented above: a literal0, not0xFFFFFFFF. The confusion there is understandable, since0xFFFFFFFFis a real, meaningful sentinel for that field, just not the one the read call falls back to on absence.
Note
SoundExists’s own absent default is a genuine oddity:0x80(128), not0or1. That’s what the field resolves to when absent and neither the runtime-downgrade condition (SoundandVO_ResRefboth invalid, which forces it to0) nor an explicit file value overrides it.FadeColor,FadeDelay, andFadeLengtheach default to zero (black,0.0,0.0respectively) at their own read site, distinct from the laterFadeType == 0pass that zeroes them again regardless of what was just read.
Warning
A node that carries
SoundExistscarries it six times. Where it appears in anEntryListorReplyListelement it is always present exactly six times, never fewer, and the six always agree on their value. It is the only duplicated label anywhere in the corpus: every other repeated label in every other resource type is a list element rather than a repeat within one struct.It is six field records sharing a single label-table entry, not six labels. The label table itself carries no duplicate, so a tool walking labels sees one and a tool walking a struct’s fields sees six.
Reading is unaffected and writing is not. Since the six agree, taking the first is correct for a reader. A writer that updates one copy leaves five holding the old value, and a file in that state has never been observed, so a tool producing one is producing something the game has never written.
Viewport Framing (LoadDialogCamera)
| Field | Type | Engine Evaluation |
|---|---|---|
CameraID | INT | Dependent Field: Only permitted when CameraAngle = 6 (Placeable Camera). Otherwise, the engine forces the ID to -1 regardless of the static binary value. The field’s own read-time default, confirmed against the read call, is a literal 0 rather than -1. That -1 is purely the CameraAngle != 6 post-processing result, which runs identically whether CameraID was present or fell back to that 0. |
CamFieldOfView | FLOAT | If the property is missing or explicitly negative, the engine forces the perspective to -1.0. |
CamHeightOffset, TarHeightOffset | FLOAT | Standard float deltas. Both default to 0.0 if missing. |
Listener | CExoString | Defaults to an empty string if missing. |
CameraAngle | DWord | Defaults to 0 if missing, a plain ungated default. It is the value the engine later checks against 6 to gate CameraID, but the default itself carries no special meaning. |
CameraAnimation | WORD | Defaults to 0 if missing. |
CamVidEffect | INT | Defaults to -1 if missing, confirmed against the binary. Never read again after the store. |
Link Fields: Active and Index
Active and Index (on RepliesList/EntriesList/StartingList entries alike) both default unconditionally when absent: Active to an empty resref, Index to 0. Neither default is a placeholder that gets special-cased later; both feed directly into real behaviour:
- An absent (empty)
Activegenuinely means “always active,” not just “no condition configured that happens to evaluate true.”CSWSDialog::CheckScript, the function that evaluates a link’s condition at runtime, opens with an explicit check for an empty resref and returns true immediately without ever touching the script virtual machine. Only a non-empty resref gets compiled and run for real. Absence and “always true” are the same code path by construction. - An absent
Indexresolves to0and is bounds-checked exactly like an explicit0, since the already-documented fatal-bounds-check behaviour runs against whatever value ends up stored, absent or not. Since0is a valid index into every target list, an absentIndexdoesn’t trigger the fatal path; it silently links to the first element of the target list instead.
Relational Data Trees
Dialogues are built out of link-lists pointing at each other.
- Entry -> Reply Links (
RepliesListwithin an Entry Node): Maps theIndex(DWORD) against the.ReplyListbounds. The only variant that parses theDisplayInactiveByte. - Reply -> Entry Links (
EntriesListwithin a Reply Node): Maps theIndexagainst the.EntryListbounds. - Start Indices (
StartingList): Uses the exact same linkage schema as a Reply->Entry link. ValidatesIndexagainstentry_count.
All three link-list variants read only Active (a CResRef condition script) and Index; RepliesList additionally reads DisplayInactive, and no link-list variant reads anything else.
Warning
Corrupted Link Constraints, and the one value that gets through
Indexpaths are checked at load, before anything traverses them, and the same idiom covers all three link relationships: aRepliesListlink against the reply count,StartingListand aReplyListentry’sEntriesListagainst the entry count. A link that fails aborts the whole file, not just itself. The loader short-circuits its remaining work, cleans up, and returns failure, so one bad index costs the entire conversation.The bound is off by one, in the permissive direction. The test is
count < index, so an index exactly equal to the count passes: one element past the end of the array, admitted by the check that exists to keep it out.That value is not caught anywhere later either.
SendDialogRepliesreaches a reply by pointer arithmetic straight off the base of the reply block, with no bounds test of its own, and the block is allocated to hold exactlycountelements. Soindex == countreads one whole reply structure past the end of the allocation, and what comes back is whatever the heap has put there.For a tool this is the sharpest case on the page: a file carrying that one value loads cleanly, passes every check the engine makes, and misbehaves later during the conversation itself.
DisplayInactive Gates Whether a Failing Link Is Hidden or Shown Disabled
DisplayInactive defaults to 0 when absent from a RepliesList entry. At runtime, SendDialogReplies (0x005a3820) evaluates each reply link’s Active condition script; when that condition is false, DisplayInactive decides what happens next: a nonzero value still builds and sends the reply to the client (shown as a disabled option), while a zero value drops the reply from the outgoing list entirely, so the client never sees it at all. EntriesList and StartingList links have no equivalent field and are always dropped outright when their condition fails.
No vanilla .dlg file contains DisplayInactive at all, which means every vanilla dialogue takes the drop-entirely branch for every conditionally-failing reply. The shipped game never exercises the “show as disabled” behaviour. Writing an explicit DisplayInactive = 0 is behaviourally identical to omitting the field outright, since both resolve to the same default, though it diverges from vanilla’s own convention of never writing the field at all.
Fields the Loader Never Reads: NumWords, VO_ID, IsChild, Comment, LinkComment
These fields appear in vanilla .dlg files carrying real values that no loader ever reads, and the evidence is stronger than “untraced”: none of their label strings exists anywhere in the executable, so no reader can reach them on any code path. They are unmodelled, so a Rakata round trip drops them.
The full write-up lives in Fields Vanilla Writes That the Engine Never Reads, alongside the same finding on other formats.
Ancillary Configuration Lists
- AnimList: Defines custom
Participantmodels and their accompanyingAnimation(WORD) action index to loop. - StuntList: Names which
StuntModelstands in for a givenParticipantduring the cutscene.
Implemented Linter Rules (Rakata-Lint)
Phase 1 (intra-resource, no context)
Implemented under rakata_lint::rules::dlg.
- DLG-001 (Camera Angle Compliance): Warns when
CameraIDis populated whileCameraAngle != 6; the engine forces the ID to -1. - DLG-002 (Conversation Type Mismatch): Warns when
ComputerTypeis set butConversationType != 1(Computer Dialog); ComputerType is dead data otherwise. - DLG-003 (Ghost Delay Flags): Warns when an entry delay is maxed (
0xFFFFFFFF) but no sound/VO is configured and the parent fallback delay is 0; the node terminates instantly. - DLG-004 (Fatal Bounds Checking): Errors when any
Indexin a node’s link list, the starting list, or a reply list points past the end of the array it targets; this triggers a fatal engine load failure. - DLG-005 (Context Zeroing): Warns when
FadeDelay,FadeLength, orFadeColorare configured butFadeType=0; the engine discards the timings. - DLG-007 (Admitted Link Index): Errors when an
Indexequals the length of the array it targets. Separate from DLG-004 because the two fail in opposite ways: past the end the file will not open, while exactly at the end it opens, passes every check the loader makes, and reads one element past the allocation during the conversation. The engine’s own test iscount < indexand cannot reject this value.
Phase 2 (resource existence, requires LintContext)
Implemented under rakata_lint::rules::dlg_range.
- DLG-006 (Resref Existence): Warns when any of
EndConversation/EndConverAbort(.ncs),CameraModel(.mdl),AmbientTrack(.wav), per-stuntStuntList[i].StuntModel(.mdl), or per-nodeScript(.ncs),Sound/VO_ResRef(.wav), andLinks[j].Activecondition scripts (.ncs) do not resolve in the configured resource sources.
Every label the schema declares
Generated from the schema, so no label can be quietly left out. How to read these tables.
What the engine does with each field
| Field | Type | Engine | When absent |
|---|---|---|---|
EntryList[].Comment | CExoString | never reads it: the label string does not exist anywhere in the engine binary, so no read path can name it | NOT EXAMINED; we substitute "" |
EntryList[].RepliesList[].IsChild | BYTE | never reads it: the label string does not exist anywhere in the engine binary, so no read path can name it | NOT EXAMINED; we substitute 0 |
EntryList[].RepliesList[].LinkComment | CExoString | never reads it: the label string does not exist anywhere in the engine binary, so no read path can name it | NOT EXAMINED; we substitute "" |
ReplyList[].Comment | CExoString | never reads it: the label string does not exist anywhere in the engine binary, so no read path can name it | NOT EXAMINED; we substitute "" |
ReplyList[].EntriesList[].IsChild | BYTE | never reads it: the label string does not exist anywhere in the engine binary, so no read path can name it | NOT EXAMINED; we substitute 0 |
ReplyList[].EntriesList[].LinkComment | CExoString | never reads it: the label string does not exist anywhere in the engine binary, so no read path can name it | NOT EXAMINED; we substitute "" |
StartingList[].IsChild | BYTE | never reads it: the label string does not exist anywhere in the engine binary, so no read path can name it | NOT EXAMINED; we substitute 0 |
StartingList[].LinkComment | CExoString | never reads it: the label string does not exist anywhere in the engine binary, so no read path can name it | NOT EXAMINED; we substitute "" |
NumWords | DWORD | never reads it: the label string does not exist anywhere in the engine binary, so no read path can name it | NOT EXAMINED; we substitute 0 |
VO_ID | CExoString | never reads it: the label string does not exist anywhere in the engine binary, so no read path can name it | NOT EXAMINED; we substitute "" |
Fields nobody has examined
Whether the engine reads these has not been established, which is not the same as establishing that it does not. Where When absent carries an answer, that half is settled.
| Field | Type | When absent |
|---|---|---|
CameraModel | CResRef | stamps "" |
DelayEntry | DWORD | stamps 0 |
DelayReply | DWORD | stamps 0 |
EndConversation | CResRef | stamps "" |
EndConverAbort | CResRef | stamps "" |
Skippable | BYTE | stamps 1 |
ConversationType | INT | stamps 0 |
ComputerType | BYTE | stamps 0 |
AmbientTrack | CResRef | stamps "" |
UnequipItems | BYTE | stamps 0 |
UnequipHItem | BYTE | stamps 0 |
AnimatedCut | BYTE | stamps 0 |
OldHitCheck | BYTE | stamps 0 |
EntryList | List | NOT EXAMINED; we substitute container |
EntryList[].AnimList | List | NOT EXAMINED; we substitute container |
EntryList[].AnimList[].Participant | CExoString | NOT EXAMINED; we substitute "" |
EntryList[].AnimList[].Animation | WORD | NOT EXAMINED; we substitute 0 |
EntryList[].Text | CExoLocString | NOT EXAMINED; we substitute empty |
EntryList[].Script | CResRef | NOT EXAMINED; we substitute "" |
EntryList[].Speaker | CExoString | stamps "" |
EntryList[].WaitFlags | DWORD | stamps 0 |
EntryList[].Quest | CExoString | stamps "" |
EntryList[].QuestEntry | DWORD | stamps 0 |
EntryList[].PlotIndex | INT | stamps 0 |
EntryList[].PlotXPPercentage | FLOAT | stamps 0.0 |
EntryList[].Delay | DWORD | stamps 0 |
EntryList[].FadeType | BYTE | stamps 0 |
EntryList[].FadeColor | Vector3 | stamps (0.0, 0.0, 0.0) |
EntryList[].FadeDelay | FLOAT | stamps 0.0 |
EntryList[].FadeLength | FLOAT | stamps 0.0 |
EntryList[].Sound | CResRef | NOT EXAMINED; we substitute "" |
EntryList[].VO_ResRef | CResRef | stamps "" |
EntryList[].SoundExists | BYTE | stamps 128 |
EntryList[].Listener | CExoString | stamps "" |
EntryList[].CameraAngle | DWORD | stamps 0 |
EntryList[].CameraID | INT | stamps 0 |
EntryList[].CamHeightOffset | FLOAT | stamps 0.0 |
EntryList[].TarHeightOffset | FLOAT | stamps 0.0 |
EntryList[].CameraAnimation | WORD | stamps 0 |
EntryList[].CamVidEffect | INT | stamps -1 |
EntryList[].CamFieldOfView | FLOAT | stamps -1.0 |
EntryList[].RepliesList | List | NOT EXAMINED; we substitute container |
EntryList[].RepliesList[].Active | CResRef | stamps "" |
EntryList[].RepliesList[].Index | DWORD | stamps 0 |
EntryList[].RepliesList[].DisplayInactive | BYTE | stamps 0 |
ReplyList | List | NOT EXAMINED; we substitute container |
ReplyList[].AnimList | List | NOT EXAMINED; we substitute container |
ReplyList[].AnimList[].Participant | CExoString | NOT EXAMINED; we substitute "" |
ReplyList[].AnimList[].Animation | WORD | NOT EXAMINED; we substitute 0 |
ReplyList[].Text | CExoLocString | NOT EXAMINED; we substitute empty |
ReplyList[].Script | CResRef | NOT EXAMINED; we substitute "" |
ReplyList[].Speaker | CExoString | stamps "" |
ReplyList[].WaitFlags | DWORD | stamps 0 |
ReplyList[].Quest | CExoString | stamps "" |
ReplyList[].QuestEntry | DWORD | stamps 0 |
ReplyList[].PlotIndex | INT | stamps 0 |
ReplyList[].PlotXPPercentage | FLOAT | stamps 0.0 |
ReplyList[].Delay | DWORD | stamps 0 |
ReplyList[].FadeType | BYTE | stamps 0 |
ReplyList[].FadeColor | Vector3 | stamps (0.0, 0.0, 0.0) |
ReplyList[].FadeDelay | FLOAT | stamps 0.0 |
ReplyList[].FadeLength | FLOAT | stamps 0.0 |
ReplyList[].Sound | CResRef | NOT EXAMINED; we substitute "" |
ReplyList[].VO_ResRef | CResRef | stamps "" |
ReplyList[].SoundExists | BYTE | stamps 128 |
ReplyList[].Listener | CExoString | stamps "" |
ReplyList[].CameraAngle | DWORD | stamps 0 |
ReplyList[].CameraID | INT | stamps 0 |
ReplyList[].CamHeightOffset | FLOAT | stamps 0.0 |
ReplyList[].TarHeightOffset | FLOAT | stamps 0.0 |
ReplyList[].CameraAnimation | WORD | stamps 0 |
ReplyList[].CamVidEffect | INT | stamps -1 |
ReplyList[].CamFieldOfView | FLOAT | stamps -1.0 |
ReplyList[].EntriesList | List | NOT EXAMINED; we substitute container |
ReplyList[].EntriesList[].Active | CResRef | stamps "" |
ReplyList[].EntriesList[].Index | DWORD | stamps 0 |
StartingList | List | NOT EXAMINED; we substitute container |
StartingList[].Active | CResRef | stamps "" |
StartingList[].Index | DWORD | stamps 0 |
StuntList | List | NOT EXAMINED; we substitute container |
StuntList[].Participant | CExoString | NOT EXAMINED; we substitute "" |
StuntList[].StuntModel | CResRef | NOT EXAMINED; we substitute "" |