Overview[edit]
Mod files for CS4 (TES4 Construction Set) are essentially collections of records, which are further divided into fields. Records generally correspond to objects in the construction set (e.g., a creature, a GMST setting, a dialog entry), with the fine details of the object (e.g., health of a creature, a dialog entry test) being handled by the fields of the record. Records themselves are organized into groups by GRUP records. At the highest grouping level, the TES4 file is simply:
- A single TES4 record
- A collection of top groups.
While CS4 seems to have some flexibility in the ordering and structure of records and groups in the files it reads, it also clearly likes to write files in a more specific ordering, which is described below. If your application is writing a mod file, it is suggested that you follow this preferred format if possible.
For a comparison with Morrowind (TES3) file format, see: Mod File Format/Vs Morrowind.
GRUPs were introduced in Tes4 to improve scanning of files since they make it easier to skip over blocks of records that the reading program isn't interested in. In addition to this, subgroups for WRLD and CELLS provide some useful structural information (e.g., the division of cell data into persistent and non-persistent references). The header is comparable to a Record header (see below), but with many of the fields repurposed.
Type/Size |
Info |
char[4] |
Record type (always "GRUP") |
uint32 |
Size of the entire group, including the group header (20 bytes).
- This is in contrast to records and fields, whose sizes do not include their header sizes.
|
uint8[4] |
Label. Format depends on group type (see next field).
- In the CS4 Details view, you can mark a group as ignored, but CS4 ignores this setting and reads the group anyway. The ignore flag interferes with what should ordinarily be in the label field (e.g., "HAIR" becomes "HQIR"). This mislabeling has no effect on record loading. In short, the label field of a group is not reliable. If you subsequently save, the group will be written without the ignore markings.
|
uint32 |
Group type
Type |
Info |
Label |
Label |
0 |
Top (Type) |
char[4] |
Record type |
1 |
World Children |
formid |
Parent |
2 |
Interior Cell Block |
long |
Block number |
3 |
Interior Cell Sub-Block |
long |
Sub-block number |
4 |
Exterior Cell Block |
short[2] |
Grid Y, X (Note the reverse order) |
5 |
Exterior Cell Sub-Block |
short[2] |
Grid Y, X (Note the reverse order) |
6 |
Cell Children |
formid |
Parent |
7 |
Topic Children |
formid |
Parent |
8 |
Cell Persistent Childen |
formid |
Parent |
9 |
Cell Temporary Children |
formid |
Parent |
10 |
Cell Visible Distant Children |
formid |
Parent |
|
uint32 |
Version Control Info
- The low word is a timestamp or 0. The low byte of that word is the day of the month and the high byte is nominally the number of months since December 2002. (1 = January 2003, 13 = January 2004, etc.) However, the algorithm only adds 12 months per year for years not ending in 1, 2, or 3. In other words, it only supports years from 2003-2010 and becomes unreliable for anything outside that range.
- Dates are based on when the Construction Set was launched, not the date the file was saved. Typically, dates will be the same throughout an entire file, but if version control is in use, different groups may have different dates.
- If version control is enabled, the high word marks ownership; otherwise, it's 0000. The low byte is the user id that last had the form checked out and the high byte is the user id that currently has the form checked out. Values of 0 mean no one has the object checked out currently.
|
Top Groups[edit]
In Oblivion.esm, the top, or highest level groups are stored in the following order:
- GMST, GLOB, CLAS, FACT, HAIR, EYES, RACE, SOUN, SKIL, MGEF, SCPT, LTEX, ENCH, SPEL, BSGN, ACTI, APPA, ARMO, BOOK, CLOT, CONT, DOOR, INGR, LIGH, MISC, STAT, GRAS, TREE, FLOR, FURN, WEAP, AMMO, NPC_, CREA, LVLC, SLGM, KEYM, ALCH, SBSP, SGST, LVLI, WTHR, CLMT, REGN, CELL, WRLD, DIAL, QUST, IDLE, PACK, CSTY, LSCR, LVSP, ANIO, WATR, EFSH.
The game expects certain groups to appear before others, and can crash if it encounters references to records that haven't been loaded yet.
All top groups contain records matching their label (e.g., the GMST top group contains GMST records). For most top groups, only the matching record types are present. However, in the CELL, WRLD and DIAL top groups, each main record can be followed by one or more child groups which contain additional records of a different type. Structure and ordering of those are as follows...
Hierarchical Top Groups[edit]
DIAL Top Group |
|
CELL Top Group |
- Interior Cell Block
- Interior Cell Sub-Block
- CELL
- Cell Childen
- Persistent children
- Visible distant children
- Temp Children
|
WRLD Top Group |
- WRLD
- World Children
- ROAD
- CELL
- Cell Children
- Persistent Children
- Visible Distant Children
- Temp Children
- Exterior World Block
- Exterior World Sub-block
- CELL
- Cell Childen
- Persistent Children
- Visible Distant Children
- Temp Children
- LAND
- PGRD
- REFR, ACHR, ACRE
|
Records[edit]
Type/Size |
Info |
char[4] |
Record type |
uint32 |
Size of data field. |
uint32 |
Flags...
Flag |
Meaning |
0x00000001 |
ESM file. (TES4.HEDR record only.) |
0x00000020 |
Deleted |
0x00000200 |
Casts shadows |
0x00000400 |
Quest item / Persistent reference |
0x00000800 |
Initially disabled |
0x00001000 |
Ignored |
0x00008000 |
Visible when distant |
0x00020000 |
Dangerous / Off limits (Interior cell) |
0x00040000 |
Data is compressed |
0x00080000 |
Can't wait |
|
formid |
Form ID: Unique record identifier within the file.
- The TES4 record always has a Form ID of 0.
- Some GMST records have Form IDs of 0.
|
uint32 |
Version Control Info (see the same entry in Groups)
- Only ESM files have version control information on a per-record basis; for ESPs, it's typically per-group, although there seem to be occasional exceptions. For most records, it will be set to 0.
|
uint8[dataSize] |
Data
- For uncompressed records, this is a sequence of fields.
- Compressed data is the same, except that the fields are compressed using ZLIB level 6, and stored into the data field like so...
Name |
Type/Size |
Info |
uint32 |
Size of decompressed data. |
uint8[dataSize-4] |
Compressed collection of fields. |
|
Type/Size |
Info |
char[4] |
Field type. |
uint16 |
Size of data field.
- If the previous field has the type XXXX, then dataSize of the current field will be 0 and the size of the data is in fact the 32 bit quantity stored in the XXXX field. This happens once in Oblivion.esm.
|
uint8[dataSize] |
Data.
- Format depends on record and field type.
|