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

Save Game Deep Dive

KotOR stores a save as a folder, not a single file. This page documents what the folder contains, how the engine assembles it, and the serialization quirks that make save GFFs disagree with their static template counterparts. Evidence throughout is drawn from Ghidra decompilation of swkotor.exe (K1 GOG build), cross-checked against a vanilla save folder on disk. Findings are recorded as behaviour notes, not as transcribed engine code.

Overview

PropertyValue
On-disk unitA directory under the SAVES: alias, named NNNNNN - <name> (for example, 000231 - Game230); slots 000000 and 000001 are reserved, see below
Main archiveSAVEGAME.sav, an ERF with version tag MOD V1.0
Loose sidecarssavenfo.res, PARTYTABLE.res, GLOBALVARS.res (each a GFF), plus Screen.tga
Rust referencerakata-save (mid-refactor); see Save Games for the field tables

A save is the engine’s snapshot of the player’s whole session. That state spans every module you have visited, so the engine does not assemble it in one pass. It keeps a working directory while you play and packages that directory into the save folder when you save.

The staged working directory

The engine maintains a live working directory under the GAMEINPROGRESS: alias. As you play, two things accumulate there:

  • Per-module runtime state, written every time you leave a module.
  • Global session state (party table, globals), refreshed at save time.

When you save to a slot, the engine packages that working directory into SAVEGAME.sav and writes a few small metadata files loose alongside it. Loading reverses the process: unpack the archive back into a working directory and replay it.

The save folder layout

A vanilla save folder contains:

FileFormatRole
SAVEGAME.savERF (MOD V1.0)The bundle: every per-module archive plus the global session resources
savenfo.resGFF (NFO )Menu metadata: name, area, last module, play time, portraits
PARTYTABLE.resGFF (PT )Party roster, gold, XP, journal, available companions, pazaak, galaxy map
GLOBALVARS.resGFF (GVT )Campaign global variables (booleans, numbers, locations, strings)
Screen.tgaTGASave-slot preview thumbnail

These metadata files sit loose so the load menu can read them straight from the slot, with no archive opened: a name, area, play time and thumbnail for every save. SAVEGAME.sav grows with every module you visit and runs to megabytes late in a game, while savenfo.res stays a few hundred bytes, so listing a folder of saves costs the same whenever you do it.

Two tag details matter to tooling: the PT party-table tag has two trailing spaces, and the ERF container’s tag is the full MOD V1.0. Every loose GFF is version V3.2, like every GFF the engine writes (see GFF for why the version never varies).

The filename casing is the engine’s own, and it is not uniform: SAVEGAME.sav, savenfo.res, PARTYTABLE.res, GLOBALVARS.res, and Screen.tga appear in exactly this mixed casing across every folder of a 104-save corpus and, independently, across saves written months apart on a second install. Read the names case-insensitively, but emit this exact casing when writing a save folder.

Per-module state: archives within the archive

A save is an archive of archives. Each module you visit is saved as its own ERF, and the engine bundles those per-module ERFs inside SAVEGAME.sav.

StoreCurrentModule writes a module’s ERF in two cases: when you save to a slot, and on every module transition, where it snapshots the module you are leaving before the next one loads. So a save holds the runtime state of every module you have visited, not just the current one. An IncludeModuleInSave gate decides which modules qualify: it reads the IncludeInSave column of modulesave.2da, finding the row by the module’s name as a string, and excludes a module only when that row exists and holds 0.

Fail-open holds on every failure path, traced through CServerExoAppInternal::IncludeModuleInSave (0x004b2121). A table that will not allocate, a load that fails, and a row or column that does not resolve all fall through to including the module.

What a per-module ERF holds

Each per-module ERF carries the MOD V1.0 tag (created by CSWSModule::SaveModuleStart, finalized by CSWSModule::SaveModuleFinish) and holds three resources:

ResourceTypeWhat it holds
IFO (resref Module)module infoThe saved module clock, runtime id counters, and the party and limbo creature lists. See IFO for the save-only fields.
ARE 2012 (0x7dc)The area static. Skipped for Mod_IsNWMFile modules.
GIT 2023 (0x7e7)The dynamic object state: live creatures, doors, placeables, triggers, and the rest. Covered below.

Note

NWM is a “NeverWinter Module” (.nwm), a type the Odyssey engine inherited from BioWare’s Aurora engine (the one behind Neverwinter Nights). For an NWM module, the engine does not re-save the area static.

Faction state is global, not per-module

When the engine stores a module, it also rewrites the whole-session faction table to a single global REPUTE file (type FAC ; see FAC). That file ends up as the one REPUTE resource in SAVEGAME.sav, never duplicated per module.

The party roster

The active module’s IFO carries the party roster in Mod_PlayerList: one full creature snapshot per member, written by SavePlayers (structure in IFO).

Between modules the party can also live in a transient pifo (party-info) file: a GFF tagged IFO , written to the working directory by StorePlayerCharacters. It uses the same Mod_PlayerList container as a module roster, and its entries are not the same shape. Both hold the same creature serialization under struct id 48813, but SavePlayers wraps a module-roster entry in the header fields that StorePlayerCharacters never writes. A pifo entry is the bare snapshot.

Each player is stamped with the roster slot it was written to, on the live object rather than in the file. Nothing in a pifo entry records it, so the stamp does not outlive the session that made it.

The sentinel index 0xffffffff is what selects pifo. LoadCharacterFromIFO normally reads a slot out of the module’s own Module IFO; that sentinel switches it to pifo. Its one call site is LoadCharacterStart, and the module-roster path passes a real slot index instead. The branch is reached only when the player object already carries a slot from an earlier write, which is why an ordinary manual load never takes it, and why a manual save folder carries no pifo.ifo to read.

So the party crosses a module boundary staged to pifo on the way out, restored from it on the way in before the destination module’s roster exists, then rebuilt by CreateParty.

Do not rely on a Player.bic being present. The engine can write the primary player as a standalone Player file in the BIC character format, a SaveCreature snapshot behind a small header written by SavePrimaryPlayerInfo, but that path is gated behind a global flag and does not run for ordinary single-player saves.

There is no matching .bic reader. On load the primary player comes from the module roster: LoadPrimaryPlayer takes the module’s primary-player index and hands off to LoadCharacterFinish. Any Player record is consumed through the same Mod_PlayerList creature-load path as a roster member, keyed by ObjectId.

The companion pool and the shared inventory

Mod_PlayerList only carries the members standing in the module. The rest of the recruited crew lives in the working directory as standalone UTC resources, one per companion, named AVAILNPC%d with the companion’s npc.2da row as the index (nine slots, AVAILNPC0-AVAILNPC8). Each is a full SaveCreature snapshot in its own GFF (typed UTC ), so every recruited companion lands inside SAVEGAME.sav whether or not they are in the active party. Slots never recruited have no file: an early-game save carries one or two AVAILNPC entries, a late-game save close to all nine.

The write path has two triggers. CSWPartyTable::AddNPC writes the snapshot the moment a companion is recruited (it also moves their inventory into the party stash, computes a joining-XP top-up, and adds them to the player’s faction). After that, SaveMember refreshes any slot that still has a live creature object: CSWPartyTable::Save loops all nine slots at save time, and party-member switches (SwitchPlayerCharacter) and the SaveNPCState script command hit the same function.

Reading back is gated by the party table. GetNPCObject refuses a slot unless its PT_NPC_AVAIL flag is set in PARTYTABLE.res, then instantiates the companion through the ordinary template loader (LoadFromTemplate) with the resref pointed at the save’s AVAILNPC%d resource, caching the live object id so the file is read at most once per session. The partytable flags are the index and the AVAILNPC files are the data: clearing a flag hides a companion whose snapshot still sits in the archive.

Two rejoin behaviours ride this path through SpawnNPC:

  • Placement. A member spawns at the position already on its creature snapshot. When that position resolves to a room of the current area, the engine adjusts it through ComputeSafeLocation (20-unit search radius) before adding the creature; a position that resolves to no room skips the adjustment and the member is added at the origin. In practice a member restored into the module it was saved in comes back where it stood. A member loaded back dead is resurrected on rejoin (GetNPCObject applies a resurrection effect when asked to).
  • XP catch-up. A rejoining member is topped up toward their share of the party XP pool: npc.2da’s PercentXP for their row, applied to PT_XP_POOL from the party table, with an auto-level-up when that client option is on. That is what the pool is for: benched companions do not earn XP live, they settle up when they rejoin.

The party’s shared item stash is a sibling resource: INVENTORY, a GFF typed INV holding a single ItemList of item snapshots. CSWPartyTable::UpdateInventory writes it right after the member loop at save time, and CreateParty reads it back when the party is rebuilt.

Neither side bounds how many items it holds. The writer walks the repository’s own item array on a full-width index and the reader walks the GFF list count the same way, with no ceiling compared against in either loop. That is a different shape from the party-table counts nearby, which cap at 255 because the count is stored as a byte; an inventory’s count comes from the GFF list header and is never narrowed, so there is no width to run out of. A save carrying exactly 255 items is a coincidence of that player’s stash rather than the engine stopping there.

Gold and the party pool

Party wealth is one number, PT_GOLD in PARTYTABLE.res, but every creature’s save block still carries its own Gold field. The in-party flag that SetInParty maintains is what keeps the two from fighting, and it is used on both sides of the cycle.

Writing. SaveCreature briefly clears the flag around its call into SaveStats. The gold accessors read that flag to decide whether “this creature’s gold” means a private ledger or the shared pool, so clearing it captures the member’s frozen personal value rather than the live party total.

Reading. ReadStatsFromGff uses the same flag as a gate: a creature currently in the party skips its Gold field entirely. Without that, each member’s stale snapshot would clobber PT_GOLD as the blocks loaded one after another. The pool is authoritative and the per-member values ride along.

A creature that was never in the party is unaffected. An ordinary NPC, a merchant or a corpse has the flag clear throughout and its Gold round-trips like any other field.

A benched companion’s gold is a one-shot amount

AVAILNPCn snapshots land in that same clear-flag case, by a route worth following. GetNPCObject constructs a fresh CSWSCreature, whose flag starts clear like any new object, and loads the companion’s GFF through LoadFromTemplate before anything sets it. So a rejoining companion’s Gold reads back normally rather than being skipped.

It does not stay personal. AddMember calls TransferInventory on the freshly spawned creature, folding that just-loaded Gold into PT_GOLD and moving their items into the party stash, before the flag is set to 1. First-time recruitment goes the same way through AddNPC, so a companion’s starting gold from their .utc template enters the pool the moment they are recruited.

The stray PC file: a party-leader-swap artifact, not a save artifact

A save can carry a bare PC resource: a UTC-shaped GFF in the same raw format as an AVAILNPCn snapshot. It is a transient swap buffer, not a save mechanism. CSWPartyTable::SwitchPlayerCharacter writes it to GAMEINPROGRESS:PC when module content hands control away from the born player character, and reads it back when control returns.

It reaches SAVEGAME.sav by accident. StallEventSaveGame and DoPCAutosave both import the entire working directory into the ERF unconditionally, sweeping up whatever is sitting there, and nothing clears GAMEINPROGRESS: between saves. The directory is wiped only at session teardown, in CServerExoAppInternal::StopServices, when you quit to the menu or load a different game.

So once a leader swap has fired anywhere in a session, PC is archived into every save taken afterward, quicksave, manual and autosave alike.

That rules it out as a save-type discriminator. Its presence tracks session history rather than which writer produced the file. It is also unrelated to the pifo roster-staging file and the gated Player BIC record above; all three reuse the SaveCreature serializer for otherwise unconnected purposes.

Building and reading SAVEGAME.sav

At save time the engine imports the entire GAMEINPROGRESS: working directory into one ERF, through StallEventSaveGame and CERFFile::ImportFiles. The per-module ERFs already live there, so they land inside SAVEGAME.sav as nested resources keyed by module resref under resource type 2057 (sav).

The flat session resources that accumulate in the same directory come along with them: the REPUTE faction table, the AVAILNPC companion snapshots, and the party INVENTORY, which is stored under the generic resource type 0 and so has to be looked up by name.

That import is a blanket sweep, not a curated list, so an incidental leftover like the PC file above rides along too. Treat those three named resources as what you can rely on rather than as a closed inventory of what a SAVEGAME.sav contains.

Reading a module’s state back is a two-level walk:

  1. Open SAVEGAME.sav.
  2. Find the resource named after the module.
  3. Parse that resource as its own ERF, then read the GIT inside it.

The party table and global variables go into the same working directory at save time (CSWPartyTable::Save, CSWGlobalVariableTable::Save), which is why PARTYTABLE.res and GLOBALVARS.res also sit loose in the folder. Those loose copies are what the engine reads back for global session state.

The .rsv intermediate format: module state before packaging

The GAMEINPROGRESS working directory holds more than the flat session resources. Each visited module’s runtime state lives there as a standalone .rsv file before it is bundled into SAVEGAME.sav.

The .rsv extension maps to resource type RSV (0x0bc1), registered in the engine’s extension table (CExoBaseInternal::CreateResourceExtensionTable). It sits alongside the other extensions (.sav, .nwm, .mod, and dozens more) in a linear lookup table that GetResTypeFromExtension and GetResourceExtension walk.

Where they come from. When you leave a module, StoreCurrentModule snapshots the module state into GAMEINPROGRESS. The engine writes a per-module ERF through the same save pipeline (CSWSModule::SaveModuleStart / SaveModuleInProgress / SaveModuleFinish) that produces the nested ERFs inside SAVEGAME.sav. These ERFs land as <resref>.rsv files in the working directory, one per visited module, carrying the same three resources (IFO, ARE, GIT) as the packaged copies. A file dropped into GAMEINPROGRESS between transitions lands in the snapshot the same way.

How the engine uses them. At load time, CServerExoAppInternal::LoadModule checks for RSV before SAV, for any module that IncludeModuleInSave (the same modulesave.2da-driven gate the save side uses) says belongs in the save at all:

  1. If the module exists as an RSV resource, the engine resolves the filename through the GAMEINPROGRESS: alias and opens it as type RSV (0x0bc1).
  2. If RSV is not found, it falls back to SAV (type 0x0809, decimal 2057, the same type the packaged archive copies use), the committed-archive copy.

This is a priority check, not a validation. The engine does not verify that a .rsv file came from its own save pipeline: it only checks whether one exists. A file dropped into GAMEINPROGRESS takes priority over the committed archive with no provenance check.

The RSV and SAV types are treated identically for the load-bar stall event: both trigger a type-3 (save-game) stall, where every other module type (MOD/RIM/NWM) triggers a type-1 (module) stall.

Important

is_nwm_file is not affected by whether a module loaded as RSV or SAV. It comes from one place only: the module’s own Mod_IsNWMFile IFO field, read the same way regardless of resource type (see the NWM note above). ARE loading is unconditional too. The area object always demands its ARE resource as the first step of loading, with no code path that skips it for any resource type. A hand-staged .rsv still needs a valid ARE to load successfully; it cannot get by on IFO and GIT alone.

During save size estimation. SetEstimatedSaveSize walks every file in GAMEINPROGRESS, maps each extension to its resource type, and estimates the eventual archive size. The padding is not type-keyed the way it sounds:

  • A file that fails to open gets a flat 2.5 MB stand-in, whatever its type, rather than being skipped.
  • Once every file is summed, a further flat 2.5 MB is added, but only when the module currently loading is neither RSV nor SAV. Loading from either is exempt from that top-up.
  • An empty GAMEINPROGRESS skips both and uses a fixed 3.75 MB baseline.
  • The total is then scaled up by roughly 11% before being stored.

To packaging. When a save is committed, StallEventSaveGame (manual / quicksave) or DoPCAutosave calls CERFFile::ImportFiles to sweep the entire GAMEINPROGRESS directory into SAVEGAME.sav. The .rsv files land inside the archive as nested ERF resources, keyed by module resref under resource type 2057 (sav), and a staged one is indistinguishable from an engine-written one once it is in there.

So .rsv is the working-directory format and sav is the archive type. A tool reading SAVEGAME.sav never meets the extension. A tool reading GAMEINPROGRESS directly, to inspect a session with no committed save, must look for <module>.rsv: same content, different extension and resource type.

savenfo: the menu metadata block

savenfo.res is a small GFF (type NFO ) built field by field in StallEventSaveGame. Its field table lives on the savenfo page. The one behaviour worth recording here: CHEATUSED is not tracked independently. The engine writes the same cheat flag the party table serializes, so the load menu stays in sync without opening the party table.

The global sidecars: partytable and globalvars

The other two loose GFFs hold session-wide state that is not tied to any single module. Full field tables live on the partytable and globalvars pages; the engine-side notes worth recording here:

PARTYTABLE.res (type PT ) is written by CSWPartyTable::SaveTableInfo. The journal is folded into the same file by a helper (CSWPartyTable::SaveJournal) that SaveTableInfo invokes, and the entire journal block is omitted when the party journal is empty.

GLOBALVARS.res (type GVT ) is written by CSWGlobalVariableTable::WriteTable. It carries four global types, not two: boolean, number, location, and string, each a catalogue-name list paired with its own value block (VOID-packed for booleans, numbers, and locations; a list for strings). Tooling that models only numbers and booleans silently drops every location and string global. The globalvars page documents the byte-exact encoding of each value block (bit-packed booleans, one byte per number, a fixed 100-slot location array) and the per-type capacity limits.

Save types: manual, quicksave, and autosave

A save folder can come from either of two write paths, and they do not produce the same thing. A tool that parses a save should know which made it. The partytable/globalvars sidecars and the SAVEGAME.sav bundle are identical across types; the differences are confined to savenfo and two loose files:

Manual / quicksaveAutosave
WriterStallEventSaveGameDoPCAutosave
Fires whenyou save to a slot, or quicksaveyou cross into a new module (StartNewModule)
savenfo-only fieldsSAVEGAMENAME, LIVE1-LIVE6, LIVECONTENTPCAUTOSAVE (=1), SCREENSHOT, AUTOSAVEPARAMS
Slot thumbnailScreen.tga (captured frame)none; SCREENSHOT holds a load_<module> resref
pifo.ifoabsentpresent

PCAUTOSAVE is the reliable tell for an autosave: its presence means the file came from DoPCAutosave. AUTOSAVEPARAMS is a nested struct holding the pending move-to-module state; the savenfo page has the full field-by-field breakdown.

The autosave writer places that pifo.ifo itself, and pifo’s presence is as reliable a tell as PCAUTOSAVE is. The scratch copy at TEMP:pifo is written on every module exit, whatever kind of save follows, so its existence says nothing. What separates the two writers is that DoPCAutosave copies it into the save folder and StallEventSaveGame never touches it. No filter is involved: TEMP: is a separate resource alias from the GAMEINPROGRESS: directory the writers sweep, so the scratch file was never a candidate for the archive.

The scratch copy is consumed on module entry and not deleted. The reader adds TEMP: to the search path, reads the roster out, and removes it again, leaving the file to be overwritten by the next exit. So a manual save taken immediately after a transition still has no pifo.ifo beside it, not because the scratch file is gone but because its writer never copies one.

For a tool that means a folder where pifo’s presence and PCAUTOSAVE disagree has been rearranged by something other than the game. Both come from the same writer, so they cannot legitimately come apart.

It does not separate a quicksave from a manual save, though. Both come from StallEventSaveGame and emit the same field set, so nothing inside the files tells them apart. The folder name does: every save folder is NNNNNN - <name>, and the first two slot numbers are reserved.

SlotFolderKind
000000000000 - QUICKSAVEQuicksave
000001000001 - AUTOSAVEAutosave
000002 and up000002 - Game1, …Manual saves, in creation order

There is no literal QUICKSAVE or AUTOSAVE directory; the words are the name half of the ordinary slot format. This is also why a save folder listing from a played-through install starts at 000002. To classify a slot: read the folder’s slot number, and corroborate with PCAUTOSAVE.

The reserved numbers are hardcoded literals rather than anything derived from scanning folders, and the autosave loader, the save-menu filter and the quickload finder each compare a parsed slot number against them independently. The set of names that can reach the formatter is closed, established by walking every caller down to the shared write backend (CServerExoAppInternal::SaveGame) and the shared unpack/copy helpers:

NameSlot numberWriter
QUICKSAVE0 (literal)CClientExoAppInternal::DoQuickSave, via the generic SaveGame backend
AUTOSAVE1 (literal)DoPCAutosave (self-contained), and separately the periodic-autosave branch in CServerExoAppInternal::MainLoop, which calls the same generic SaveGame backend directly
the player-entered save name2 and up (allocation mechanism below)The manual-save flow, through the same generic SaveGame backend

No fourth name reaches it, and nothing in the traced call graph reserves a number below 2.

Note

REBOOTAUTOSAVE is not a folder name and never reaches the slot-name formatter. It is a boolean byte inside savenfo.res, beside PCAUTOSAVE, read unconditionally by the load-menu’s slot parser. That read is its only cross-reference in the binary.

Nothing writes it. DoPCAutosave sets PCAUTOSAVE alone. The read path is live: the save-list preview checks this bit alongside PCAUTOSAVE when deciding where to pull a slot’s screenshot from. But with no producer on PC the field is permanently 0 in every save this build creates.

The name suggests a hard-reset or dashboard-return autosave, most likely inherited console-SKU logic parsed tolerantly here, the same shape as the LIVE%d Xbox content mounts. (Provenance: inferred.)

Allocating a new manual-save slot number happens in the Save menu’s list builder (CSWGuiSaveLoad::PopulateGameList), which filters out any folder parsed as slot 0 or 1 before manual saves are ever considered (the Load menu skips this filter, so quicksave/autosave still show up there). Over what’s left, the engine tracks both the highest existing manual-save number and the lowest unused one starting from 2. The normal case appends after the highest number found. If that would push past 999, the commit step (CSWGuiSaveLoad::WriteGame) falls back to the first gap instead, and if the gap search also comes up empty, with 2 through 999 all occupied, the save is rejected outright. 999 manual saves is a hard ceiling, not a soft one.

Both loose-file differences trace to autosaves firing mid-transition (StartNewModule), while a loading screen is up:

  • pifo.ifo exists because at a transition the party is staged in the transient pifo party-info file rather than a module roster (the same file the 0xffffffff load path reads, see The party roster); DoPCAutosave copies it into the folder. It is a GFF tagged IFO holding a Mod_PlayerList of the party.
  • No Screen.tga because there is no gameplay frame to capture mid-loading-screen, so the autosave records the loading-screen resref in SCREENSHOT instead. (The slot thumbnail is independent of the EnableScreenShot ini option, which governs only the manual F12 screenshot.)

Treat a stray pifo.ifo, or a missing Screen.tga, as an expected autosave artifact rather than malformed data.

The GIT: dynamic object state

A module’s live objects (creatures, doors, placeables, triggers, items, and so on) are serialized into a GIT GFF inside the module ERF (CSWSArea::SaveGIT). The engine walks the area’s object array, buckets each object by runtime type, and emits one list per type:

GIT list labelObjectNotes
Creature Listcreaturesplayer characters are split out into a separate player list, not this one
Listitem instancesitems in the area use the bare label List
Door Listdoors
TriggerListtriggers
Encounter Listencounters
WaypointListwaypoints
SoundListsounds
Placeable Listplaceablescorpses are excluded
StoreListstores
AreaEffectListarea-of-effect objects

The list labels are gleefully inconsistent: some are spaced (Creature List, Door List, Encounter List, Placeable List), some are jammed together (TriggerList, WaypointList, SoundList, StoreList, AreaEffectList), and item instances get the bare word List. There is no rule to derive them; they are simply the literal strings the engine hardcodes.

Alongside the object lists, the GIT struct carries area-level state: CurrentWeather, WeatherStarted, TransPending, TransPendNextID, TransPendCurrID (all BYTE), plus script variable tables.

Templates versus snapshots: the UseTemplates flag

The most important thing to know about a save GIT: the same GIT schema is read two completely different ways, chosen by a single UseTemplates BYTE in the GIT’s top-level struct. The area loader (CSWSArea::LoadGIT) reads UseTemplates once and hands it to every per-type loader.

AspectUseTemplates = 1 (static .git)UseTemplates = 0 (savegame GIT)
Object elementsparse placementfull self-contained snapshot
TemplateResRefpresentabsent (not read)
Blueprint loadyes, via the object’s LoadFromTemplate (UTC/UTD/UTP/UTT/…), then instance fields overlaidnone; the engine reads every field directly (CSWSCreature::LoadCreature, CSWSDoor::LoadDoor, …)
Where the data livesmostly in the blueprintentirely in the GIT element
A field missing from the elementcomes from the blueprintcomes from the engine’s hardcoded default

The consequence for any field that is absent from a savegame element: it falls back to the engine’s hardcoded default, not to the blueprint. The per-object loaders read each field with a default argument (for example a missing trigger TrapType defaults to the value already on the object, and most BYTE fields default to zero). So a savegame instance carries everything the saver wrote, and nothing more: anything the saver left out comes from an engine default, never from the template.

Important

When reading a savegame, do not reach for the blueprint. A UseTemplates = 0 object is the whole truth; any field it leaves out comes from the engine’s hardcoded default, never the .utc / .utd / … template. Synthesizing a template lookup for a savegame instance invents data the engine never used.

A module’s static layout is the sparse, template-relative form; a savegame is the already-flattened form with runtime overrides baked in. To read “the object as it exists in this save”, read the savegame GIT directly under UseTemplates = 0; only a module’s own .git needs the blueprint flatten. Evidence: CSWSArea::LoadGIT, CSWSArea::LoadCreatures, CSWSCreature::LoadCreature, and CSWSCreature::LoadFromTemplate.

Note

Area-level state (weather, the script variable tables) is gated separately, by LoadGIT’s own caller flag, not by UseTemplates. It is restored only on a full area load.

Object fields

Save objects use the ordinary GIT object schema, so the full per-object field map, the per-type position/orientation naming, and the geometry conventions live in the GIT format spec and the per-object specs (for example, UTT for trigger geometry). This page doesn’t repeat those tables. It records only the points the save path adds or makes clearer:

Save-path behaviourWhat the engine does
Field names vary by object typeNo generic position or orientation field exists. Positions come in three spellings: X/Y/Z, XPosition/YPosition/ZPosition, and PositionX/PositionY/PositionZ (area-of-effects only). Orientations come in four: the Bearing scalar, the XOrientation/… vector, the OrientationX/… vector, and a single-float Orientation on encounter spawn points. Sounds and encounters store none at all.
Bearing is two different thingsA door stores its Bearing verbatim. A placeable derives it from its orientation yaw at save time, so the stored value is lossy. Confirmed in CSWSDoor::SaveDoor and CSWSPlaceable::SavePlaceable.
Vector orientation is stored in fullCreatures, triggers, waypoints, and stores keep the whole orientation vector; nothing reduces it to a yaw at save time. On load the vector goes to SetOrientation, normalized first if it is not unit length (confirmed for triggers). Only the trigger geometry re-bake consumes the yaw alone.
Trigger geometry is position-relative and orientation-coupledTrigger vertices are offsets from the trigger’s position, not absolute points. Supplying an orientation on load re-rotates the geometry by the yaw delta.
Creature stat totals are recomputed, not restoredMaxHitPoints, ArmorClass, and the saving-throw totals in a creature block are write-only snapshots; the engine rebuilds them on load from inputs that round-trip through other fields. Details on the UTC page.
DetectMode never survives a reloadWritten faithfully, read only to skip past, then reset to 1 by construction. One of a small family of creature round-trip quirks catalogued on the UTC page.

Loading a save: the unpack flow

Loading mirrors the staged working-directory model in reverse, and the engine never runs a session from the slot itself. CSWGuiSaveLoad::UnpackGame drives the sequence:

  1. The load menu reads the loose savenfo.res straight from each slot for its name, area, play time, and thumbnail; no archive is opened. When you pick a slot, its LASTMODULE names the first module to restore.
  2. CopyGameToFutureGame unpacks the chosen slot, the SAVEGAME.sav ERF plus the loose sidecars, into the FUTUREGAME: staging area. It calls CERFFile::ReadHeaderVariance and then ExportFilesFromERF, which writes one file per archive entry, taking each filename from the entry’s key and its bytes from the resource at the same table index. See what a writer must preserve.
  3. The engine clears GAMEINPROGRESS: and renames FUTUREGAME: onto it. The live session runs from this unpacked copy.
  4. CSWSModule::LoadModule replays the module against the working directory, running LoadModuleStart / LoadModuleInProgress / LoadModuleFinish (the inverse of StoreCurrentModule). LoadModuleStart reads the per-module IFO including its save-only fields, loads the global REPUTE faction table (LoadFactionsFromSaveGame / LoadReputationsFromSaveGame), and reads the GIT with UseTemplates = 0, so every object comes from its full snapshot.
  5. The party table and global variables come back from their loose sidecars.

Note

A successful load never modifies the slot; only the working directory changes. If the unpack fails partway, the engine drops a CORRUPT marker file into the slot and abandons the load.

The CORRUPT.res marker

The marker is a real file, CORRUPT.res, written straight into the slot folder with the literal ASCII text "CORRUPT" as its entire content: a sentinel, not a structured GFF. UnpackGame (and the equivalent quicksave path, CGuiInGame::UnpackQuickSaveGame) writes it whenever the archive-copy step (CopyGameToFutureGame / CopyQuickSaveGameToFutureGame) fails.

No checked validation can reach that branch. None of the ordinary return-code paths in the ERF-reading chain signals failure for a missing, truncated or garbled file. That chain runs CERFFile::Read, ReadHeaderVariance and ExportFilesFromERF down to the raw read and write calls; they tolerate short reads with a retry-and-log, and CopyGameToFutureGame has a single return path, an unconditional success.

So the marker can only be written if a genuine exception unwinds out of the copy, and the functions in this chain do install real C++ exception handling.

(Provenance: inferred.) The likeliest trigger, going by what the code does with the data, is a corrupted archive header’s entry or language counts driving an allocation or a read past what is there. There is no explicit corruption check anywhere in this path to name as the validation.

How the load menu treats a corrupt slot

CSWGuiSaveLoadEntry::LoadData (the per-slot load-menu populator) checks for the marker by trying to open it; if present, the entry skips reading savenfo.res entirely (area name, last module, and play time stay blank) and unmounts the slot’s resource directory. A corrupted slot still shows up in the load-menu list, though, with the same click and delete handlers as any other entry. The marker only blanks the preview (area name, screenshot, and party portraits) and disables the Delete button when that entry is hovered or selected. Nothing stops the player from trying to load it anyway; if the underlying fault reproduces, the attempt silently abandons again with no error dialog.

Nothing clears the marker by name: no code deletes CORRUPT.res specifically. It does disappear as a side effect of a normal save, though: writing to an existing slot wipes every recognized resource file in that folder before laying down the new SAVEGAME.sav/savenfo.res/screenshot, and .res is a recognized extension, so CORRUPT.res gets swept up in that generic cleanup along with everything else. A subsequent successful save to a marked slot clears it, but only incidentally.

The read side is symmetric with the documented write side. The load-only behaviours are noted where they occur: the reputation default-baseline restore (FAC), the FactionGlobal default, and the trigger geometry re-bake (UTT).

What this means for tooling

  • Treat a save as a folder: one MOD V1.0 ERF plus three loose GFF sidecars and a thumbnail.
  • Read a module’s runtime objects with a two-level archive walk: open SAVEGAME.sav, find the module’s nested ERF, then read the GIT inside it.
  • Check UseTemplates first. A savegame GIT (UseTemplates = 0) is self-contained, so read objects directly. A module’s static .git (UseTemplates = 1) is template-relative, so resolve each TemplateResRef against the module’s blueprints and overlay the instance fields.
  • Resolve a savegame object’s missing fields to engine defaults, never to a blueprint. A template lookup supplies values the engine does not use.
  • Branch position and orientation field access on the object type; no single field name covers every type.
  • Preserve the raw GFF tree to round-trip placeable bearing and trigger geometry byte-exactly; on disk those values are derived, lossy, or position-relative.

Key Ghidra addresses

Function addresses in swkotor.exe (K1 GOG build), for anyone continuing this archaeology. Where a name appears twice the rows say why.

FunctionAddress
CServerExoAppInternal::StallEventSaveGame0x004b3110
CServerExoAppInternal::SaveGame0x004b58a0
CServerExoAppInternal::DoPCAutosave0x004b8300
CServerExoAppInternal::StartNewModule0x004ba920
CServerExoAppInternal::StoreCurrentModule0x004b2e70
IncludeModuleInSave0x004b20e0
CSWSModule::SaveModuleStart0x004c8960
CSWSModule::SaveModuleInProgress0x004c3b10
CSWSModule::SaveModuleFinish0x004ca680
CSWSModule::SavePrimaryPlayerInfo0x004c3c70
CSWSModule::SavePlayers0x004c7870
CSWPartyTable::Save0x005665c0
CSWPartyTable::SaveTableInfo0x005648c0
CSWPartyTable::SaveJournal0x00563d90
CSWPartyTable::AddNPC0x00564300
CSWPartyTable::SaveMember0x00563e80
CSWPartyTable::UpdateInventory0x00564030
CSWPartyTable::GetNPCObject0x00564700
CSWPartyTable::CreateParty0x00565760
CSWPartyTable::SpawnNPC0x00565130
CSWGlobalVariableTable::Save0x0052ad10
CSWGlobalVariableTable::WriteTable0x005299b0
CSWSArea::SaveGIT0x0050ba00
CSWSArea::SaveCreatures0x00507680
CSWSArea::SaveDoors0x00507810
CSWSArea::SaveTriggers0x005078d0
CSWSArea::SavePlaceables0x00507bd0
CSWSDoor::SaveDoor0x00588ad0
CSWSPlaceable::SavePlaceable0x00586a70
CSWSTrigger::SaveTrigger0x0058e660
CSWSCreature::SaveCreature0x00500610
CSWSCreatureStats::SaveStats0x005b1b90
CSWSWaypoint::SaveWaypoint0x005c8230
CSWSStore::SaveStore0x005c6cd0
CSWSSoundObject::Save0x005c86d0
CSWSEncounter::SaveEncounter0x00591350
CSWSAreaOfEffectObject::SaveEffect0x00594d80
CResGFF::CreateGFFFile0x00411260
CSWSArea::LoadGIT0x0050dd80
CSWSArea::LoadCreatures0x00504a70
CSWSCreature::LoadCreature0x00500350
CSWSCreature::LoadFromTemplate0x005026d0
CSWSTrigger::LoadTrigger0x0058da80
LoadTriggers0x0050a350
LoadTriggerGeometry0x0058d060
CSWGuiSaveLoad::UnpackGame0x006caaf0
CopyGameToFutureGame0x006c9a90
CSWSModule::LoadModule0x004b95b0
CSWSModule::LoadModuleStart0x004c9050
CServerExoAppInternal::LoadPrimaryPlayer0x004b5f50
CServerExoAppInternal::LoadCharacterStart0x004b7470
CServerExoAppInternal::LoadCharacterFinish0x004b5c50
CServerExoAppInternal::StorePlayerCharacters0x004b2ba0
CSWSPlayer::LoadCharacterFromIFO0x00561e30
CSWGlobalVariableTable::ReadTableWithCatalogue0x0052a280
CSWGlobalVariableTable::GetValueLocation0x00529350
CSWSObject::GetScriptLocation0x004cb7b0
CFactionManager::LoadFactionsFromSaveGame0x0052b5c0
CFactionManager::LoadReputationsFromSaveGame0x0052bbe0
CSWSCreatureStats::ReadStatsFromGff0x005afce0
CSWSCreatureStats::ReadSpellsFromGff0x005aeb30
CSWSCreatureStats::SaveClassInfo0x005aec90
CSWSCreatureStats::CSWSCreatureStats (constructor)0x005aca80
CCombatInformation::LoadData0x00552350
CCombatInformation::SaveData0x00550f30
CSWSCombatRound::LoadCombatRound0x004d5120
CSWSPlayer::LoadCreatureData0x00560e60
CSWSMessage::SendServerToPlayerUpdateCharResponse0x00570c60
CSWSCreature::ReadScriptsFromGff0x004ebf20
CSWSCreature::LoadFollowInfo0x004fb180
CSWSCreaturePartyFollowInfo::Load0x004eb020
CSWSCreaturePartyFollowInfo::Save0x004eaf70
CSWSCreaturePartyFollowInfo::CSWSCreaturePartyFollowInfo (constructor)0x004f79e0
CSWSObject::LoadListenData0x004d0480
CSWSObject::SaveListenData0x004cca50
CSWSObject::LoadObjectState0x004d1cf0
CSWSObject::SaveObjectState0x004cec50
CSWSObject::LoadEffectList0x004d1be0
CSWSObject::SaveEffectList0x004cc9d0
CGameEffect::LoadGameEffect0x005043a0
CSWSObject::LoadActionQueue0x004cecb0
CSWSObject::SaveActionQueue0x004cc7e0
CSWSObject::CSWSObject (base constructor)0x004cfcb0
CSWSScriptVarTable::LoadVarTable0x0059aa80
CSWSScriptVarTable::SaveVarTable0x0059adb0
CSWVarTable::LoadVarTable0x0059b0f0
CSWVarTable::SaveVarTable0x0059b250
CSWSCreature::ReadItemsFromGff0x004ffda0
CSWSCreature::CSWSCreature (constructor)0x004f7a10
CSWSCreature::SetDetectMode0x0050ee30
CSWSCreature::SetStealthMode0x0050ee50
CSWSModule::LoadLimboCreatures0x004c8c70
CSWSModule::SaveLimboCreatures0x004c5bb0
CSWSModule::LoadModuleInProgress0x004c5720
CSWSArea::LoadArea0x0050e190
CItemRepository::GetItemRepository0x004ef770
CSWSItem::LoadItem0x00560970
CSWSItem::LoadFromTemplate0x005608b0
CSWSItem::LoadDataFromGff0x0055fcd0
CSWSItem::CSWSItem (constructor)0x005530a0
CSWItem::CSWItem (base constructor)0x005b4660
CSWSItem::SetPossessor0x00553210
CSWSItem::SaveItem0x0055ccd0
CSWSItem::SaveItemProperties0x00555790
CSWSItem::SaveContainerItems0x0055cfa0
CSWSItem::ReadContainerItemsFromGff0x0055f0f0
CSWSArea::LoadItems0x00504de0
CSWSArea::SaveItems0x00507750
CSWSDoor::LoadDoor0x0058a1f0
CSWSDoor::LoadFromTemplate0x0058b3d0
CSWSDoor::LoadDoorExternal0x0058c5f0
CSWSDoor::CSWSDoor (constructor)0x00589ee0
CSWSDoor::PostProcess0x00589d40
CSWSArea::LoadDoors0x0050a0e0
CSWSPlaceable::LoadPlaceable0x00585670
CSWSPlaceable::LoadFromTemplate0x00587a70
CSWSPlaceable::CSWSPlaceable (constructor)0x005877e0
CSWSPlaceable::LoadBodyBag0x005864b0
CSWSPlaceable::SpawnBodyBag0x004ce220
CSWSPlaceable::AcquireItem0x00584b10
CSWSPlaceable::PostProcess0x00584870
CSWSArea::LoadPlaceables0x0050a7b0
ExecuteCommandCreateObject0x0052f820
CSWSTrigger::CSWSTrigger (constructor)0x0058eae0
CSWSTrigger::LoadFromTemplate0x0058ed70
CSWSTrigger::AddToArea0x0058f030
CSWSWaypoint::LoadWaypoint0x005c7f30
CSWSWaypoint::CSWSWaypoint (constructor)0x005c7e70
CSWSArea::LoadWaypoints0x00505360
CSWSStore::LoadStore0x005c7180
CSWSStore::LoadFromTemplate0x005c7760
CSWSStore::CSWSStore (constructor)0x005c6ab0
CSWSStore::AddItemToInventory0x005c70c0
CSWSArea::LoadStores0x005057a0
CSWSSoundObject::Load0x005c9040
CSWSSoundObject::LoadFromTemplate0x005c94e0
CSWSSoundObject::CSWSSoundObject (constructor)0x005c8f30
CSWSArea::LoadSounds0x00505560
CSWSEncounter::ReadEncounterFromGff0x00592430
CSWSEncounter::ReadEncounterScriptsFromGff0x00590820
CSWSEncounter::LoadEncounter0x00593830
CSWSEncounter::LoadFromTemplate0x00593a90
CSWSEncounter::LoadEncounterGeometry0x00590580
CSWSEncounter::LoadEncounterSpawnPoints0x00590410
CSWSEncounter::CSWSEncounter (constructor)0x00593c70
CSWSArea::LoadEncounters0x00505060
CSWSAreaOfEffectObject::LoadEffect0x00594b00
CSWSAreaOfEffectObject::CSWSAreaOfEffectObject (constructor)0x00594480
CSWSArea::LoadAreaEffects0x00505af0
CSWSArea::LoadProperties0x00507490
CSWSArea::SaveProperties0x00506090
CSWSArea::LoadMaps0x00505da0
CSWSArea::SaveMaps0x005061d0
CSWSArea::LoadPlaceableCameras0x00505eb0
CSWSArea::SavePlaceableCameras0x005062a0
CSWSModule::SaveModuleIFOStart0x004c7050
CSWSModule::SaveModuleIFOFinish0x004c8b90
CSWSModule::SaveStatic0x004c5980
CSWSAmbientSound::CSWSAmbientSound (constructor located; Load/Save not individually decompiled)0x005c95a0
MainLoop (two call sites feed LoadModuleStart/limbo-creature handling)0x004babb0, 0x004ae860
CSWSCreatureStats::GetFortSavingThrow0x005ab810
CSWSCreatureStats::GetWillSavingThrow0x005ab880
CSWSCreatureStats::GetReflexSavingThrow0x005ab8f0
CSWSCreatureStats::GetBaseFortSavingThrow0x005aa1b0
CSWSCreatureStats::GetBaseWillSavingThrow0x005aa2f0
CSWSCreatureStats::GetBaseReflexSavingThrow0x005aa430
CSWSCreature::GetArmorClass0x004ed1d0
CSWCCreatureStats::GetArmorClass (client-side display cache, not the save source of truth)0x00647720
CSWSItem::ComputeArmorClass (an item’s own base AC contribution, unrelated to the creature-level getter above)0x00553cc0
CSWSObject::GetMaxHitPoints0x004d01a0
CSWSCreature::GetMaxHitPoints0x004ed310
CSWCCreatureStats::GetMaxHitPoints (client-side display cache)0x00647a80
SaveCharGenCreature (character-generation/BIC export path; also writes MClassLevUpIn and PregameCurrent, with no reader for either)0x006123e0
CSWSCreature::GetGold0x004edd60
CSWSCreature::SetGold0x004edda2
CSWSCreature::AddGold0x004f3dc8
CSWSCreature::RemoveGold0x004f3eea
CSWSCreature::TransferGold0x004fd769
CSWSCreature::SetInParty0x004fdb2d
CSWSCreature::CSWSCreature (a second constructor address; 0x004f7a10 appears elsewhere in this table under the same name. Likely a different overload, not reconciled)0x004f7b47
CSWSPlayer::LoadLocalCharacter0x00561d70
ExecuteCommandAddPartyMember0x0052de70
ExecuteCommandRemovePartyMember0x00541c00
SwitchPlayerCharacter0x005667c0
TransferInventory0x005641e0
CSWSAreaOfEffectObject::LoadAreaEffect (singular; the vfx_persistent.2da-driven definition lookup, wired only to fresh spell-cast creation, never to a save load)0x005947b0
CSWSEffectListHandler::OnApplyAreaOfEffect0x004dade0
ApplyEffect0x0050c6b0
CSWSAreaOfEffectObject::AIUpdate (heartbeat tick)0x00595d10
CSWSAreaOfEffectObject::EventHandler (enter/exit collision events)0x005964e0
CVirtualMachineInternal::RunScript0x005d45d0
CVirtualMachine::RunScript (thin forwarder)0x005d0fc0
CSWSAreaOfEffectObject::GetEffectSpellId / SetEffectSpellId0x005945d0 / 0x005945e0
CClientExoAppInternal::DoQuickSave0x005f4b50
CSWGuiSaveLoadEntry::LoadData0x006c8e50
CSWGuiSaveLoad::LoadPCAutoSave0x006ca250
CSWGuiMainMenu::OnPanelAdded (disk-space probe reusing the reserved AUTOSAVE name)0x0067b6c0
CSWGuiSaveLoad::PopulateGameList0x006cc160
CSWGuiSaveLoad::HandleSaveButton0x006cbb60
CSWGuiSaveLoad::PromptForSaveName0x006cb820
CSWGuiSaveLoad::WriteGame0x006c8790
CSWGuiSaveLoad::ShowGame0x006c89d0
CSWGuiSaveLoadEntry::SetXboxTitle0x006c9780
CGuiInGame::DoQuickLoad0x00633c50
CSWGuiSaveLoadEntry::CSWGuiSaveLoadEntry (constructor)0x006cb940
KOTOR_AUTOSAVE_PARAMS::SaveToGFF0x004b28e0
CStatusSummary::SaveToGFF0x004b26c0
CGuiInGame::GetStatusSummary / SetStatusSummary0x0062f0a0 / 0x0062f040
CGuiInGame::SuppressStatusSummary0x0062f0c0
CGuiInGame::GetPendingStatusSummary0x0062ef70
CGuiInGame::ShowStatusSummary0x0062ef90
CGuiInGame::UpdateStatus0x0062eeb0
CSWGuiStatusSummary::AddAlignmentShift0x00624a70
CSWGuiStatusSummary::AddCredits0x00624ab0
CSWGuiStatusSummary::AddXp0x0062b580
CSWGuiStatusSummary::AddStealthXp0x0062b5a0
CSWVirtualMachineCommands::ExecuteCommandSuppressStatusSummaryEntry0x00547e50
CSWVirtualMachineCommands::ExecuteCommandStartNewModule0x00544390
CClientExoApp::GetMoveToModuleMovies0x005edb60
CClientExoApp::AddMoveToModuleMovie0x005edb50
CClientExoApp::RemoveMoveToModuleMovies0x005ee380
CServerExoApp::GetMoveToModuleStartWaypoint / SetMoveToModuleStartWaypoint0x004aed40 / 0x004aed30
CServerExoApp::SetMoveToModulePending0x004aecc0
CServerExoApp::SetMoveToModuleString0x004aecd0
CClientExoApp::SetLoadScreenByModuleName0x005edcf0
CClientExoApp::GetLoadMusicByModuleName (thunk) / CClientExoAppInternal::GetLoadMusicByModuleName (implementation)0x005edd00 / 0x005f3650
CWorldTimer::GetWorldTime0x004ade40
CWorldTimer::ConvertFromTimeOfDay0x004add90
CSWSModule::GetTime0x004c4100
CGuiInGame::UnpackQuickSaveGame0x006323a0
CopyQuickSaveGameToFutureGame (quicksave counterpart to CopyGameToFutureGame; not fully decompiled, assumed the same shape by symmetry)0x0062fbe0
CERFFile::Read0x005dce50
CERFFile::ReadHeaderVariance0x005dd3c0
CERFFile::ExportFilesFromERF0x005dd710
CERFRes::CopyToFile0x005dd170
CExoFile::FileOpened / Read / Write0x005e6a10 / 0x005e6960 / 0x005e69a0
CExoFileInternal::Read / Write0x005eba40 / 0x005ebc60
CSWGuiSaveLoad::VerifyLoadGame0x006cc0e0
CSWGuiSaveLoad::LoadGame0x006cb0e0
CExoResMan::CleanDirectory0x00409460
CExoResMan::WipeDirectory0x00408e90
CExoAliasListInternal::ResolveFileName0x005eb6b0
CExoBaseInternal::GetResourceExtension0x005e7a00
CSWGuiSaveLoadEntry::GetGameDirectory0x006c8250
CServerExoApp::SaveGame (thin wrapper over CServerExoAppInternal::SaveGame, dispatched from network message handlers)0x004ae6e0
HandlePlayerToServerModuleMessage / HandleServerAdminToServerMessage0x00524800 / 0x00528380
CSWGuiSaveLoad::DeleteGame0x006caa90
ExecuteCommandSwitchPlayerCharacter (nwscript action dispatch, sole caller of SwitchPlayerCharacter)0x00544910
CSWPartyTable::GetFilename (computes AVAILNPC%d)0x00563620
CSWPartyTable::UpdateMembers0x00565530
CSWSPlayer::SaveServerCharacter (multiplayer server-vault BIC writer; ruled out)0x005624c0
CSWPartyTable::AddGameInProgress / RemoveGameInProgress (reference-counted mount/unmount of the GAMEINPROGRESS: scratch directory)0x005638d0 / 0x00563950
CServerExoAppInternal::StopServices (nukes GAMEINPROGRESS: at session teardown, the only place a stray PC file is ever cleared)0x004b7e25
CServerExoAppInternal::DoModuleEnterSaveCleanup (prunes stale nested-module entries in GAMEINPROGRESS:; unrelated to PC, recorded to clarify what does and doesn’t get cleaned there)0x004b23ea
CServerExoAppInternal::LoadModule (a second LoadModule address; CSWSModule::LoadModule at 0x004b95b0 appears elsewhere in this table under that different class-qualified name. Likely a caller/callee pair rather than a conflict, not reconciled)0x004b98e0
CExoBaseInternal::CreateResourceExtensionTable0x005e6d20
CExoBaseInternal::GetResTypeFromExtension0x005e7a40
CExoBase::GetResTypeFromExtension (thin forwarder to the Internal version above)0x005e6670
CServerExoAppInternal::SetEstimatedSaveSize0x004b5f90
CExoResMan::GetResTypeFromFile0x00406650
CSWPartyTable::AddMember0x00565620
CSWGuiPartySelection::AcceptParty0x006be560
CSWVirtualMachineCommands::ExecuteCommandSpawnAvailableNPC0x00543ed0