-
Notifications
You must be signed in to change notification settings - Fork 9
NeFS Format 2.0
The NeFS archive is broken into multiple sections:
Part | Description |
---|---|
Header Intro | Archive overview; encryption and verification info |
Header Intro Table of Contents | Offsets to other header parts |
Header Part 1 | Item metadata: offsets to additional metadata and to the actual compressed data within the archive |
Header Part 2 | Item metadata: details about item's extracted state (e.g. a file's extracted size, filename, etc.) |
Header Part 3 | Table of filename and directory name strings |
Header Part 4 | Compressed sizes/offsets for the chunks of data for each item in the archive |
Header Part 5 | Archive information (size, name, data offset) |
Header Part 6 | Item metadata: Additional item attributes |
Header Part 7 | Item metadata: Navigation to next item in directory |
Header Part 8 | Unknown data |
Compressed file data | The chunks of compressed data for the files in the archive |
An "item" is either a file or directory within the archive. Directories don't have any compressed data, but have metadata entries in the header just like files do.
An archive can be a single file that contains both the header and data, or the header and data can be split into different files (for example, game.dat files hold item data, but the headers are stored in the game executable).
The header intro contains size, encryption, and verification information.
Start* | End* | Length | Description | Notes |
---|---|---|---|---|
0x00 | 0x03 | 0x04 | Magic # | 4E 65 46 53 (ASCII: "NeFS"); 0x5346654E |
0x04 | 0x23 | 0x20 | Expected hash SHA-256 value | |
0x24 | 0x063 | 0x40 | An 256-bit key for AES ECB encryption, stored as a hex ascii string | |
0x64 | 0x67 | 0x04 | Header size | |
0x68 | 0x6B | 0x04 | NeFS version | Major.Minor.Patch probably |
0x6C | 0x6F | 0x04 | Number of items in the archive | |
0x70 | 0x77 | 0x08 | Unknown | Potentially constant 7F 55 12 00 7A 6C 69 62; last four bytes are "zlib" |
0x78 | 0x7F | 0x08 | Unknown |
*Offsets are absolute.
For encrypted headers, this section is encrypted using an RSA-1024 scheme.
- The first 0x80 bytes are "encrypted" with an RSA private key. Technically this is considered a decrypt operation:
- valid data -> decrypt w/ private key -> scrambled data
- To "decrypt", an RSA-1024 public key is used. Technically this is considered an encrypt operation:
- scrambled data -> encrypt with public key -> valid data
- No padding is used. Exponent is 65537.
- For DiRT Rally 2, the public key is stored in the main game executable.
- The rest of the header is encrypted using AES-256. The key is stored in the header intro.
The expected hash value is a SHA-256 hash of the following data:
- Offset: 0x00; Length: 0x04 bytes
- Offset: 0x24; Length: (Header Size - 0x24)
In other words, this is a hash of the header, without the expected hash.
Size of the archive's header. Used in expected header hash calculation.
NOTE: The end of the header (computed using the Header Size value) seems to fall inside of Header Part 8, which is unknown data.
The table of contents contains offsets to other header parts.
Absolute | Relative | Length | Description | Notes |
---|---|---|---|---|
0x80 | 0x00 | 0x02 | Number of volumes | Usage unknown |
0x82 | 0x02 | 0x02 | Hash block size | Usage unknown. This is the low 16-bits of a 32-bit value. |
0x84 | 0x04 | 0x04 | Offset to Header Part 1 | Compressed item metadata |
0x88 | 0x08 | 0x04 | Offset to Header Part 6 | Additional item metadata |
0x8C | 0x0C | 0x04 | Offset to Header Part 2 | Extracted item metadata |
0x90 | 0x10 | 0x04 | Offset to Header Part 7 | Sibling item links |
0x94 | 0x14 | 0x04 | Offset to Header Part 3 | Filename strings table |
0x98 | 0x18 | 0x04 | Offset to Header Part 4 | Chunk sizes/offsets |
0x9C | 0x1C | 0x04 | Offset to Header Part 5 | Contains size of the archive file, plus other unknown data |
0xA0 | 0x20 | 0x04 | Offset to Header Part 8 | Unknown |
0xA4 | 0x24 | 0x5C | Unknown | Padding? |
- Contains entries for each item (file or directory) in the archive.
- Items are not guaranteed to be sorted by id.
- The number of entries in part 1 is equal to the number of items specified in the header intro.
- It is possible to have duplicates (multiple entries with the same id).
Each entry has the following layout:
Start* | Length | Description | Notes |
---|---|---|---|
0x00 | 0x08 | Offset to compressed data | |
0x08 | 0x04 | Index Part 2 | Used to index into parts 2 and 7 |
0x0c | 0x04 | Index Part 4 | Used to index into part 4 |
0x10 | 0x04 | Item id |
*Offsets are relative to the start of the item's Part 1 entry.
- Entries are ordered by a depth-first traversal of the directory structure with children ordered alphabetically.
- The number of entries are not guaranteed to be the same as part 1.
Each entry has the following layout:
Start* | Length | Description | Notes |
---|---|---|---|
0x00 | 0x04 | Directory id (id of the item that is the parent of this item) | |
0x04 | 0x04 | Id of the first child of this item | Only directories can have children. If the item has no children, the id is the same as the item's id. The first child id is based on the children being sorted by id, not file name. |
0x08 | 0x04 | Offset into Header Part 3 where this item's filename/directory name string starts | |
0x0c | 0x04 | Extracted size of file | Will be 0 for directories |
0x10 | 0x04 | Item id |
*Offsets are relative to the start of the item's Part 2 entry.
Contains array of null-terminated strings. These are the filenames/directory names of the items in the archive. Each item's Header Part 2 entry has the appropriate offset into this array.
Contains an array of cumulative chunk sizes for each file in the archive.
When a file is compressed to be put in the archive:
- It is split into chunks of size 0x10000.
- Each chunk is compressed using Deflate compression.
- The compressed chunks are then put into the archive.
So an entry for an item in Header Part 4 looks like this (where n is the number of chunks):
Start* | Length | Description |
---|---|---|
0x00 | 0x04 | Size of compressed chunk 1 |
0x04 | 0x04 | Size of compressed chunk 1 + 2 |
((n - 1) * 0x04) | 0x04 | Size of compressed chunk 1 + 2 + ... + n |
*Offsets are relative to the start of the item's Part 4 entry.
The last value in the entry will be the total size of the compressed data in the archive for that item.
Part 5 has the following layout:
Start* | Length | Description |
---|---|---|
0x00 | 0x08 | Total size of NeFS archive |
0x08 | 0x04 | Offset into part 3 for name of archive |
0x0C | 0x04 | Offset to first item data |
*Offsets are relative to the start of Header Part 5.
- Contains the same number of entries as part 1.
- Entries are ordered the same as part 1.
Each entry has the following layout:
Start* | Length | Description |
---|---|---|
0x00 | 0x02 | Volume (?) |
0x02 | 0x01 | Flags (bitfield) |
0x03 | 0x01 | Unknown |
*Offsets are relative to the start of the item's Part 6 entry.
Value | Description |
---|---|
0x1 | IsZlib |
0x2 | IsAes |
0x4 | IsDirectory |
0x8 | IsDuplicated |
0x10 | Unknown |
0x20 | Unknown |
0x40 | Unknown |
0x80 | Unknown |
- Contains the same number of entries as part 2.
- Entries are ordered the same as part 2.
Each entry has the following layout:
Start* | Length | Description | Notes |
---|---|---|---|
0x00 | 0x04 | Sibling item id | Id of the next item in the same directory. If this is the last item in the directory, the sibling id is equal to this item id. The sibling id is based on the children being ordered by id, not file name. |
0x04 | 0x04 | Item id |
*Offsets are relative to the start of the item's Part 7 entry.
Unknown data. Seems to be ignored by the game (at least for car nefs). NeFS Edit is currently writing this data as 0. It falls between Header Part 7 and the start of the first item's compressed data. It straddles the "Header Size" barrier that is used when performing a hash check of the header. So I'm not sure how much of this is actually part of the header or not. Needs investigated.
Contains the compressed chunks of data for each file in the archive.