Mod files for CS4 (TES4 Construction Set) are essentially collections of records, which are further divided into subrecords. 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 subrecords 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 are new (compared to Tes3), and seem to have been introduced largely to improve scanning of files, since they make it easier to skip over blocks of records that the reading program is not 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.)
Name |
Type/Size |
Info |
---|
type |
char[4] |
Always "GRUP" |
groupSize |
ulong |
Size of the entire group, including the group header (20 bytes).
- This is in contrast to records and subrecords, whose sizes does not include their header sizes.
|
label |
ubyte[4] |
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. If you subsequently save, the group will be written without the ignore markings. 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.
|
groupType |
long |
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 |
|
stamp |
ulong |
Date stamp, presumably of the last file modification. Stamp uses the MS-DOS date format.
- For a given esp, the stamp is the same for all groups, and seems to increase with date (and possibly time).
- Oblivion.esm stamps vary. Possibly due to a merging process?
|
Top GroupsEdit
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 GroupsEdit
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
|
Name |
Type/Size |
Info |
---|
type |
char[4] |
Record type |
dataSize |
ulong |
Size of data field. |
flags |
ulong |
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 |
formid |
Record identifier.
- Tes4 does not have a FormId.
- Some GMST records do not have a FormID.
|
Version Control Info |
ulong |
used for revision control by the CS (only if enabled)
- Low word is a timestamp: low byte is the day of the month, high byte is a month number, starting with 1 = Jan. 2003
The CS's algorithm for producing the timestamps is a bit strange, though, so that the month of December for a given year actually comes before January of that year, instead of January of the next year. One can only assume it was a bug ...
- High word marks ownership: low byte is user id that last had the form checked out, high byte is user id (if any) that currently has the form checked out
|
data |
ubyte[dataSize] |
Data
- For uncompressed records, this is a sequence of subrecords.
- Compressed data is the same, except that the subrecords are compressed using ZLIB level 6, and stored into the data field like so...
Name |
Type/Size |
Info |
---|
decompSize |
ulong |
Size of decompressed data. |
compData |
ubyte[dataSize-4] |
Compressed collection of subrecords. |
|
SubrecordsEdit
Name |
Type/Size |
Info |
---|
subType |
char[4] |
Subrecord type. |
dataSize |
ushort |
Size of data field.
- If the previous subrecord has the type XXXX, then dataSize of the current subrecord 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.
|
data |
ubyte[dataSize] |
Data.
- Format depends on record and subrecord type.
|
There are a number of subrecord formats that are appear in several places.
- Variable Length String (terminated)
- Holds a variable length string including the nul (0x00) terminator. This means that an empty string would have a subrecord size of 1. Most variable string subrecords use this type.
- Variable Length String (not terminated)
- Holds a variable length string but does not include the nul (0x00) terminator. Only seen in SCTX subrecords in scripts so far.
- byte/word/dword/int64/float
- Basic data types which are used in a variety of places. The subrecord size is the same size of the native type.