Tes4Mod:Save File Format
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
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.
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.
|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
|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.|
|pcLocation||bzstring||PC's current cell.|
|gameDays||float||Days that have passed in game.
|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.|
||ulong||Size of remaining snapshot data. Value = (width * height * 3) + 8.|
||ubyte[3*width*height]||RGB bytes with no compression. Remember that bitmap's scan line typically have different length.|
|pluginsNum||ubyte||Number of plugins including masters.|
|plugins||list[pluginsNum]||List of plugin names.|
||bstring||Plugin file name (including extension).|
|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.|
|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).|
||ulong||FormID of cell. (Not iref).|
|globals||struct[globalsNum]||Array of global variables.|
||ulong||Iref of global.|
||float||Value of global.
|tesClassSize||ushort||Size of DeathCount data and GameModeSeconds (numDeathCounts * 6 + 8)|
|numDeathCounts||ulong||Number DeathCounts in list|
|deathCounts||struct[numDeathCounts]||Array of death counts|
||iref||Actor base form|
||ushort||Number of times an instance of this actor has died|
|gameModeSeconds||float||Seconds elapsed in game mode (menus closed) for this character|
|specEventData||ubyte[specEventSize]||Spectator Event data.|
|playerCombatCount||ulong||Number of Actors in combat with the player|
|createdData||list[createdNum]||Items (potions, enchanted items, spells) created in-game.
|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.|
||ubyte||Indicates that the quick key is set (1) or not (0).|
||ulong||Quick Key setting if flag = 1. If flag = 0 then the next byte is the flag for the next quick key|
|regionsSize||ushort||Size of regionsNum + regions array.|
|regions||struct[regionsNum]||Array of information about regions.|
||ulong||Iref of region.|
||ulong||Region data. ?Flags?|
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.
||ulong||FormId of the data object being changed or created. FormIds for objects created in-game have value between 0xFF000000 - 0xFFFFFFFF.|
||ubyte||Type of change record.
||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
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).
||ubyte||Version of change record; value is 125. See minorVersion for details|
||ushort||Total size of the data section (of all the change sub-records).|
||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.|
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.
|data||ubyte[tempEffectsSize]||Contains irefs to EFSH objects.|
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.
|formIdsNum||ulong||Number of formIds in array.|
|formIds||ulong[formIdsNum]||Array of formIds.|
World Spaces Array
Probably an array that maps internal references to world space formIds. But if that's so, doesn't it duplicate functionality of FormId array?
|worldSpacesNum||ulong||Number of world spaces in array.|
|worldSpaces||ulong[worldSpacesNum]||Array of world space formIds.|