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

NFO Format (Save Metadata Block)

Description: The savenfo (savenfo.res) file is a save’s load-menu metadata block (type NFO ). It sits loose in the save folder so the load screen can show a name, area, play time, and portraits for every slot without opening SAVEGAME.sav, an archive that grows with every module you visit. The engine builds it field by field at save time.

At a Glance

PropertyValue
Filenamesavenfo.res
Magic SignatureNFO / V3.2
TypeSave Metadata Block
Rust ReferenceHandled by rakata-save (mid-refactor).

Data Model Structure

A flat GFF of scalar fields, no lists (autosaves add one nested struct). The Save type column marks which write path emits each field: both, manual (manual saves and quicksaves), or autosave.

FieldTypeMeaningSave type
SAVEGAMENAMECExoStringDisplay name of the save.manual
AREANAMECExoStringLocalized display name of the current area.both
LASTMODULECExoStringResref of the module the engine restores first.both
TIMEPLAYEDDWORDRunning play time, in seconds.both
CHEATUSEDBYTECheat flag; mirrors the party table’s cheat state.both
GAMEPLAYHINTBYTELoading-screen hint state.both
STORYHINTBYTELoading-screen hint state.both
LIVE1 .. LIVE6CExoStringDownloadable-content slot names (six); empty on a vanilla PC install.manual
LIVECONTENTBYTEBitmask of which of the six LIVE%d slots are installed; 0 when none.manual
PORTRAIT0 .. PORTRAITNCResRefOne portrait resref per active party member.both
PCAUTOSAVEBYTEAlways 1; its presence marks the file as an autosave.autosave
REBOOTAUTOSAVEBYTERead by the load menu’s slot parser, but no write site exists anywhere in this build; see the note below.none (dead on PC)
SCREENSHOTCExoStringLoading-screen resref (load_<module>) used as the slot preview, in place of a Screen.tga.autosave
AUTOSAVEPARAMSStructPending move-to-module state; see AUTOSAVEPARAMS Fields below.autosave

Engine Audits & Decompilation

(Documented from Ghidra decompilation of swkotor.exe (K1 GOG build). Manual-save writer: CServerExoAppInternal::StallEventSaveGame at 0x004b3110. The autosave variant is written by CServerExoAppInternal::DoPCAutosave at 0x004b8300.)

Note

savenfo quirks.

  • CHEATUSED is not tracked here independently. The engine writes the same cheat flag the party table serializes, so the menu shows the right state without opening the party table.
  • PORTRAIT0, PORTRAIT1, … is a numbered field series, not a GFF list: one field per active member, suffixed by index.
  • Downloadable (“live”) content always leaves a footprint. The manual/quicksave path writes all six LIVE1-LIVE6 name fields plus the LIVECONTENT bitmask unconditionally, whether or not content is installed. This is the Xbox Live download mechanism (LIVE%d aliases resolving to RIMSXBOX\live%d RIMs). A vanilla PC install has nothing aliased in, so a real quicksave carries all six LIVE%d as empty strings and LIVECONTENT = 0 (verified against a K1 GOG save). The Yavin Station DLC on PC is not delivered through this path.
  • REBOOTAUTOSAVE is read unconditionally by the slot parser and folded into the same bit-field as PCAUTOSAVE, but no code path in this build ever writes it – DoPCAutosave only sets PCAUTOSAVE. The read path is live regardless: the save-list preview code checks this bit together with PCAUTOSAVE when it decides where a slot’s screenshot comes from, so REBOOTAUTOSAVE just stays permanently 0. See the Save Game Deep Dive for what the field likely inherits from.
  • This is the one file read straight from the slot for the load menu, and LASTMODULE is what selects the first module to restore.

Important

Two write paths, two field sets. savenfo comes from StallEventSaveGame (manual saves and quicksaves) or DoPCAutosave (autosaves); the Save type column above marks which fields each produces. PCAUTOSAVE is the reliable discriminator. Autosaves also differ at the folder level (a loose pifo.ifo, no Screen.tga); the Save Game Deep Dive covers why.

AUTOSAVEPARAMS Fields

AUTOSAVEPARAMS snapshots the module transition that was in flight when the autosave fired, so the engine can resume it once the save is reloaded. Every field traces to a live piece of transition or world-clock state; none of it is autosave-invented data.

FieldTypeSource
LOADMUSICCExoStringThe destination module’s loadscreens.2da row, MusicResRef column. Falls back to a load_<modulename> resref if that cell is empty and a matching .mp3 exists, then to the 2DA row literally named DEFAULT if neither resolves. Same fallback chain (different column) as the ordinary loading-screen background picker, CClientExoApp::SetLoadScreenByModuleName.
STARTWAYPOINTCExoStringThe arrival waypoint tag passed to the StartNewModule script action by whatever door or trigger initiated the transition – where the player lands once the autosave resolves.
MOVIE1 .. MOVIE6CExoStringDrained from the pending movie queue, filled by calls to AddMoveToModuleMovie from the same StartNewModule action, one movie per slot. Unfilled slots write as empty strings. There’s no queue-overflow handling to speak of: StartNewModule’s own script signature only accepts six movie arguments, so a seventh movie never reaches the autosave writer in the first place.
TIME_YEARDWORDThe destination module’s start_year, fixed at module load rather than a running counter – this is why a real sample often reads 0.
TIME_MONTH / TIME_DAY / TIME_HOURBYTEThe module’s live calendar fields (current_month/current_day/current_hour), read through the module’s own time accessor.
TIME_MINUTE / TIME_SECOND / TIME_MILLISECONDWORDNot from the module’s calendar at all – derived by converting the world timer’s raw time-of-day tick count into minute/second/millisecond components. One snapshot, two different accessors: the module object supplies year/month/day/hour, the lower-level world timer supplies the rest.
TIME_PAUSEDAY / TIME_PAUSETIMEDWORDThe paused day/time halves of the same world timer’s tick-pair snapshot – the identical live clock that seeds the module IFO’s Mod_PauseDay/Mod_PauseTime on an ordinary module-save, just captured at a mid-transition autosave instead.
STATUSSUMMARYStructSee below.

STATUSSUMMARY is a “since you last saw a loading screen” delta accumulator, not a snapshot of current totals:

FieldTypeBehaviour
CREDITS / XP / STEALTHXPINTRunning totals added to by every credit/XP/stealth-XP gain since the popup last displayed.
CREDITSNETBYTESet when credits moved in both directions since the last display (gained and lost), distinguishing a net change from a one-way one.
LIGHTSHIFT / DARKSHIFTBYTEAlignment-shift deltas, same accumulate-then-drain pattern.
DISPLAYSPENDING / ITEMRECEIVED / ITEMLOST / JOURNALBYTEPending-event flags set by the corresponding gameplay hooks (item give/take, journal updates).
SOUNDPENDING / LEVELUPSOUND / NEWQUESTSOUND / COMPLETESOUNDBYTESelects which stinger, if any, plays alongside the popup.
SUPPRESSEDINTNot a display toggle – a countdown. While positive, incoming gameplay events (credits, items, journal updates, and anything else listed above) are dropped rather than accumulated, and the counter ticks down by one per dropped event. Driven by the SuppressStatusSummaryEntry script command, presumably so a scripted sequence of item grants doesn’t leave a stale or inflated delta behind.

The dispatcher (CGuiInGame::UpdateStatus) is the one place gameplay events get folded in; the drain point (CGuiInGame::ShowStatusSummary) zeroes the running totals and pending flags once the popup has been queued. Confirmed autosave-exclusive: StallEventSaveGame (the manual save/quicksave writer) never constructs this struct at all – there’s no in-flight transition for a manual save to preserve.

Implemented Linter Rules (Rakata-Lint)

None yet. Documented here ahead of any dedicated rakata-lint rules.