EX files begin with a header with the following structure.
Offset | Size (Bytes) | Description |
---|---|---|
0x0 | 4 | Magic: ‘HEAD’ |
0x4 | 4 | 0xC (?) |
0x8 | 4 | Magic: ‘EXTF’ |
0xC | 4 | 0x1 (?) |
0x10 | 4 | Block count |
0x14 | 4 | Magic: ‘DATA’ |
0x18 | 4 | Compressed data size (bytes) |
0x1C | 4 | Uncompressed data size (bytes) |
0x20 | Varies | Encrypted & compressed data |
The compressed data is encrypted with a dictionary. It can be decrypted as follows,
uint8_t ex_decode_table[256];
void decode(uint8_t *compressed_data, size_t size)
{
// initialize table
for (int i = 0; i < 256; i++) {
int tmpfor = i;
int tmp = tmpfor;
tmp = (tmp & 0x55) + ((tmp >> 1) & 0x55);
tmp = (tmp & 0x33) + ((tmp >> 2) & 0x33);
tmp = (tmp & 0x0F) + ((tmp >> 4) & 0x0F);
if ((tmp & 0x01) == 0) {
tmpfor = ((tmpfor << (8 - tmp)) | (tmpfor >> tmp)) & 0xFF;
} else {
tmpfor = ((tmpfor >> (8 - tmp)) | (tmpfor << tmp)) & 0xFF;
}
ex_decode_table[i] = tmpfor;
}
// decode data
for (size_t i = 0; i < size; i++) {
compressed_data[i] = ex_decode_table[i];
}
}
Once the data is decrypted, it can be uncompressed using zlib.
The uncompressed data is a list of blocks. Each block has a name, data type and associated data. The number of blocks in the list is given in the header described above.
Size (Bytes) | Description |
---|---|
4 | Data type |
4 | Block size |
Varies | Name (ex-string) |
Varies | Value (depends on data type) |
Strings in EX files are encoded as pascal strings. Sometimes (not always!) they are zero-padded so that the length is a multiple of 4. I will refer to such padded strings as ‘ex-strings’ throughout this document.
Size (Bytes) | Description |
---|---|
4 | Size |
Varies | Data |
EX files can contain the following data types:
Value | Name | Description |
---|---|---|
1 | EX_INT | 32-bit integer |
2 | EX_FLOAT | 32-bit float |
3 | EX_STRING | Pascal string (32-bit size followed by string data) |
4 | EX_TABLE | Table |
5 | EX_LIST | List |
6 | EX_TREE | Tree |
A table is a list of fields, followed by the table data.
Size (Bytes) | Description |
---|---|
4 | Field count |
Varies | Field descriptors |
4 | Column count |
4 | Row count |
Varies | Rows |
Starting in Evenicle, the order of ‘Column count’ and ‘Row count’ is reversed.
Size (Bytes) | Description | Notes |
---|---|---|
4 | Data type | |
Varies | Name (ex-string) | |
4 | Has value? | |
4 | Is index? | If 1, then this field should be indexed |
Varies | Value (depends on data type) | Only present if ‘Has value?’ is 1 |
4 (0) | Subfield count | Only present if data type is EX_TABLE |
Varies | Subfields | Only present if data type is EX_TABLE |
If a field’s data type is EX_TABLE, then ‘Subfield count’ and ‘Subfields’ are present. ‘Subfields’ is a nested array of field descriptors.
A row is a list of cells. The number of cells contained in each row is given by the ‘Column count’ of the table, and the number of rows in a table is given by the ‘Row count’. A cell is a simple type-value pair:
Size (Bytes) | Description |
---|---|
4 | Data type |
Varies | Value (depends on data type) |
Size (Bytes) | Description |
---|---|
4 | Item count |
Varies | List items |
Size (Bytes) | Description |
---|---|
4 | Data type |
4 | Data size |
Varies | Value (depends on data type) |
Trees are encoded as a recursive node structure:
Size (Bytes) | Description |
---|---|
Varies | Node name (ex-string) |
4 | Is leaf? |
Varies | Node data |
If ‘Is leaf?’ is 1, then ‘Node data’ contains a leaf node descriptor. If ‘Is leaf?’ is 0, then ‘Node data’ contains an intermediate node descriptor.
Size (Bytes) | Description |
---|---|
4 | Data type |
4 | Data size |
Varies | Leaf name (ex-string) |
Varies | Value (depends on data type) |
4 | Reserved? (always 0) |
Size (Bytes) | Description |
---|---|
4 | Child count |
Varies | Children |
Each entry in ‘Children’ is a full tree descriptor.