Oblivion Mod:Save File Format

The UESPWiki – Your source for The Elder Scrolls since 1995
Jump to: navigation, search

The format used for save files (.ess files) in Oblivion is a binary format, different from that used for mod files. The overall structure of the save file format is largely understood, but in many cases the specifics of the data contents have not yet been decoded.

You can make a text dump overview of a save game's contents by using the SaveGame (save) or LoadGame (load) console commands. Simply append a non-zero value to the command arguments, eg: save test 1 or load test 1. It will generate a text file named with the same filename as your save game, but with .txt appended, eg: save test 1 will generate a "test.ess.txt" file. The dump file contains a list of all the save file's Change Records (sorted by type and length).

Within a save file, most object references use iref values, instead of FormIDs. An iref can be translated into a FormID using the FormIDs Array. However, iref values greater than 0xFF000000 are actual FormIDs, and refer to objects created within the save file. These created objects are detailed either in the Change Records section of the save file (for individual object references, i.e. ACHR, ACRE and REFR record types) or in the createdData array (for other record types).

See File Format Conventions for conventions used to document variables and datatypes.

File Header[edit]

If the first four characters of a save game file are "CON ", then the file is from an Xbox 360, and the Xbox 360 File Container has not been removed. The real game data starts after 53248 bytes. However, simply skipping the first 53248 bytes of the file is not sufficient. The File Container format also inserts other chunks of information into the Oblivion save file, independent of the Oblivion save file format. These are 4-16 KB pieces that typically appear somewhere after the first 600KB or so of "real" game data. Therefore any file starting with a "CON " record needs to have the container information removed (for example, using WxPirs) before it can be parsed according to the format on the rest of this page (and before it can be read by the PC version of Oblivion). See Xbox 360 for more information on accessing Xbox 360 save files.

Name Type/Size Info
fileId char[12] Constant: "TES4SAVEGAME"
majorVersion ubyte Current Oblivion major version is 0.
minorVersion ubyte The minor version for the Oblivion save file format has been 125 since the game's release. A version 126 has been reported, but the conditions under which version 126 appears are unknown. Even with installation of the latest patch and installation of Shivering Isles, the minor version is still 125. UESP's documentation describes the format of version 125 save files.
exeTime systemtime Time when game executable was last modified (see gameTime below for time when save file was created). Obtained with GetLocalTime() API. This field is not present in save files less than version 0.82

Save Game Header[edit]

Name Type/Size Info
headerVersion ulong Header's version number; value is 125. See minorVersion for details.
saveHeaderSize ulong Size in bytes of Save Game Header (not including this size variable).
saveNum ulong Save number, used for default name of save file. Presumably this is a counter keeping track of the total number of saves you have made to date.
pcName bzstring PC's (player character's) name.
pcLevel ushort PC's level.
pcLocation bzstring PC's current cell.
gameDays float Days that have passed in game.
  • Starts at 1.042 (Day 1, 1 AM).
gameTicks ulong Uses the GetTickCount() API to obtain the total number of ticks that have elapsed during gameplay. Equivalent to milliseconds spent playing the game, and is used to obtain statistics on "Playing Time."
gameTime systemtime Real time that save file was created. Obtained with GetLocalTime() API.
screenshot struct Screenshot at time of save.
size
ulong Size of remaining snapshot data. Value = (width * height * 3) + 8.
width
ulong Pixel width.
height
ulong Pixel height.
data
ubyte[3*width*height] RGB bytes with no compression. Remember that bitmap's scan line typically have different length.

Plugins[edit]

Name Type/Size Info
pluginsNum ubyte Number of plugins including masters.
plugins list[pluginsNum] List of plugin names.
name
bstring Plugin file name (including extension).

Global[edit]

Name Type/Size Info
formIdsOffset ulong Absolute address of formIdsNum.
recordsNum ulong Number of change records.
nextObjectid formid Number of next savegame specific object id, i.e. FFxxxxxx. When this value overflows it causes the Reference Bug.
worldId formid
worldX ulong Most likely part of Worldspace ID data.
worldY ulong Most likely part of Worldspace ID data.
pcLocation struct Current PC location (cell and position within cell).
cell
ulong FormID of cell. (Not iref).
x
float X coordinate.
y
float Y coordinate.
z
float Z coordinate.
globalsNum ushort
globals struct[globalsNum] Array of global variables.
iref
ulong Iref of global.
value
float Value of global.
  • Note that all globals are represented as floats regardless of their CS4 type (float, long, short).
tesClassSize ushort Size of DeathCount data and GameModeSeconds (numDeathCounts * 6 + 8)
numDeathCounts ulong Number DeathCounts in list
deathCounts struct[numDeathCounts] Array of death counts
actor
iref Actor base form
deathCount
ushort Number of times an instance of this actor has died
gameModeSeconds float Seconds elapsed in game mode (menus closed) for this character
processesSize ushort
processesData ubyte[processesSize] Processes data.
specEventSize ushort
specEventData ubyte[specEventSize] Spectator Event data.
weatherSize ushort
weatherData ubyte[weatherSize] Sky/Weather data.
playerCombatCount ulong Number of Actors in combat with the player
createdNum ulong
createdData list[createdNum] Items (potions, enchanted items, spells) created in-game.
  • The format is the same as for mod file records.
  • The size of individual records vary, but the size is always provided as the second value in the record, making it possible to scan past the createdData without fully processing each record.
quickKeysSize ushort This is the size (in bytes) of the entire quickKeysData array -- not the number of individual quickKeys entries.
quickKeysData struct[quickKeysSize] Quick Keys settings. The size of individual data records can be 1 or 5 bytes, depending upon the value of "flag" in each record.
flag
ubyte Indicates that the quick key is set (1) or not (0).
iref
ulong Quick Key setting if flag = 1. If flag = 0 then the next byte is the flag for the next quick key
reticuleSize ushort
reticuleData ubyte[reticuleSize] HUD Reticule.
interfaceSize ushort
interfaceData ubyte[interfaceSize] Interface stuff.
regionsSize ushort Size of regionsNum + regions array.
regionsNum ushort
regions struct[regionsNum] Array of information about regions.
iref
ulong Iref of region.
unknown6
ulong Region data. ?Flags?

Change Records[edit]

This section consists of a collection of records documenting changes to data objects supplied by the master mods, and new objects added during game play. The number of records is provided by recordsNum. The total size of a record is provided in dataSize, so records can be skipped past relatively easily.

However, processing any record details requires a fairly comprehensive understanding of the record format, dictated by the record type, the flags for that record, and then further influenced by the contents of the sub-records. The record type controls which sub-records may appear and their order and the flags (roughly) list which sub-records appear. Unlike sub-records in mod files, the individual sub-records are not clearly demarcated, nor are their sizes provided. Therefore, each sub-record needs to be processed, in order, before subsequent records can be read. Separate subarticles describe the format of each record type; those articles are linked under the list of types.

Newly-created types of objects (custom spells, enchantments, etc.) are listed in the createdData section. For example, if the player creates a custom-enchanted ring, the ENCH record detailing the enchantment and the CLOT record detailing the ring are both listed under createdData. Any individual instances of that ring are listed in Change Records -- whether as REFR objects, or as inventory entries for the player's ACHR object, or inventory entries for a container's REFR object.

Name Type/Size Info
records list[global.recordsNum]
formId
ulong FormId of the data object being changed or created. FormIds for objects created in-game have value between 0xFF000000 - 0xFFFFFFFF.
type
ubyte Type of change record.
Type Name Info
6 FACT Factions
19 APPA Alchemical Apparatus
20 ARMO Armor
21 BOOK Books
22 CLOT Clothing
25 INGR Ingredients
26 LIGH Lights
27 MISC Misc. Items
33 WEAP Weapons
34 AMMO Arrows
35 NPC_ Player and NPC data: attributes, spells, factions, etc.
  • These changes affect all instances of objects.
  • The change record for FormId 0x00000007 contains player character data (see also ACHR).
36 CREA Creature information (rats, horses, hostile creatures.)
  • These changes affect all instances of objects.
38 SLGM Soul gems
39 KEYM Keys
40 ALCH Alchemy (potions)
48 CELL Cells
49 REFR Placed instances of inanimate objects (containers, dropped items, chairs, doors, etc.)
50 ACHR Placed instances of PC (player character) and NPCs (non-player characters).
  • The change record for FormId 0x00000014 (20) contains player character data (see also NPC_).
51 ACRE Placed instances of creatures.
58 INFO Dialog entries.
59 QUST Quest information
61 PACK AI Packages.
flags
ulong Flags indicating what has been changed. The meaning of each flag depends upon the type of record; all known values are documented on the individual record subpages (see above table). Most flags have a corresponding sub-record in the record data.

For example, an NPC_ record might have 0x1000027c as the flags, indicating that the following changes have been made to the record

  • 2: Form Flags
  • 3: Base Health
  • 4: Base Attributes
  • 5: Spell List
  • 6: Factions
  • 9: Skills
  • 28: Base Modifiers

Note that these flags values do not indicate the order used to list the corresponding change sub-records. The sub-record Overview provides the order of the sub-records, showing, for example that Base Health (3) is detailed after the Spell List (5).

version
ubyte Version of change record; value is 125. See minorVersion for details
dataSize
ushort Total size of the data section (of all the change sub-records).
data
ubyte[dataSize] Collection of change sub-records. The format of the change sub-records depends upon the type of record; see the individual record type subpages for details.

Temporary Effects[edit]

The temporary effects listed in this section are effect shaders (EFSH objects). Temporary magic effects, such as the effects listed in the player Journal's Active Effects sections, are listed within the appropriate ACHR change record.

Name Type/Size Info
tempEffectsSize ulong
data ubyte[tempEffectsSize] Contains irefs to EFSH objects.

FormIds Array[edit]

The FormIds array is located at formIdsOffset. It allows iref values to be deferenced into FormIds. Although the formIds array starts at index 0, iref 0 is always empty (0x00000000); 1 is the smallest valid value for an iref, and the corresponding formid is the second entry in the formIds array. Iref=0 is used in cases where the value is unchanged relative to the original.

FormIds for objects created in-game are not included in the FormIds array. "irefs" are only dereferenced using the FormId array if they are less than 0xFF000000; otherwise, the iref is actually a FormID.

Name Type/Size Info
formIdsNum ulong Number of formIds in array.
formIds ulong[formIdsNum] Array of formIds.

World Spaces Array[edit]

Probably an array that maps internal references to world space formIds. But if that's so, doesn't it duplicate functionality of FormId array?

Name Type/Size Info
worldSpacesNum ulong Number of world spaces in array.
worldSpaces ulong[worldSpacesNum] Array of world space formIds.