The purpose of MNF files are as indexes to the sub-files stored within DAT files. There are 3 known MNF files:
-
- \game\client\game.mnf
- \depot\eso.mnf
- \vo_en\esoaudioen.mnf
Each of these are matched with one or more DAT files with the similar name (game0000.dat, eso0000.dat, esoaudioen0000.dat).
File FormatEdit
The MNF files have a short header followed by one or more blocks of compressed data.
[File Header (0x0F bytes)] [Block 0..N] [Block Data...]
Byte data is little endian unless noted.
File HeaderEdit
Version 2Edit
This format of the file header occurs in all MNF files before update 25. The MNF file header appears to be a fixed size of 15 (0x0F) bytes and contains a few fields:
byte MAGIC_DWORD[4] = "MES2" /* MES MAGIC WORD */ word MES_VERSION = 0x0002 /* MES FORMAT VERSION */ byte FileCount /* NUMBER OF DAT FILES THAT CORRESPOND TO THE MNF FILE */ dword MNF_TYPE = 0x0000001 (eso.mnf) || 0x0000006 (game.mnf) dword DataSize /* DataSize is the total amount of data that follows the file header */
Version 3Edit
This format of the file header occurs in all MNF files on and after update 25. The MNF file header is now a variable size:
byte MAGIC_DWORD[4] = "MES2" word MES_VERSION = 0x0003 dword FileCount word FileTypes[FileCount] /* Eso.mnf has increasing numbers here 01..FD, 01, Game.mnf has 06 */ word Unknown1 = 0x0000 dword DataSize /* DataSize is the total amount of data that follows the file header */
Data BlocksEdit
One or more data blocks follow the MNF file header in a contiguous manner. The block format is identified by a 2 byte block id at the start of the block data. At the moment there are two known block types.
Block Type 3Edit
This is the most common block type found in all three MNF files. Note that the block and data header information is all stored in big endian bit order. This block is composed of a short fixed size header followed by three data blocks with the same overall format:
[Block Header (0x12 bytes)] [Data Block 3.1] [Data Header (8 bytes)] [Data (...)] [Data Block 3.2] [Data Header (8 bytes)] [Data (...)] [Data Block 3.3] [Data Header (8 bytes)] [Data (...)]
The block header is a fixed size of 18 (0x12) bytes:
word BlockID = 0x0003 dword FieldSize = 0x00000004 /* Size of each field of a record - Not used by the game */ dword RecordCountB1 dword RecordCountB2 dword RecordCountB3
The record counts are the number of records found in each of the three data blocks that follow (see below). If all three record counts are zero the block contains no data and should not be processed (started happening in the ZOSFT file in the Thieves Guild DLC update).
Data HeaderEdit
Each of the three data blocks are preceded by an 8 byte header:
dword UncompressedSize dword CompressedSize
The CompressedSize is the number of data in bytes that follows the data header. The data is assumed to be in the zLib format.
Data Block 3.1Edit
This is the first of three data blocks in a type 3 block in a MNF file. The data is compressed in a zLib format and once uncompressed it is composed of multiple fixed sized records of 4 bytes with the format:
dword Unknown1 Uncompressed_Size = RecordCountB1 * FieldSize
Data Block 3.2Edit
This is the second of three data blocks in a type 3 block in a MNF file. The data is compressed in a zLib format and once uncompressed it is composed of multiple fixed sized records of 8 bytes with the format:
dword Unknown1 dword Unknown2
The number of records is equal to RecordCountB2 found in the block header.
Uncompressed_Size = RecordCountB2 * 2 * FieldSize
Data Block 3.3Edit
This is the third of three data blocks in a type 3 block in a MNF file. The data is compressed in a zLib format and once uncompressed it is composed of multiple fixed sized records of 20 (0x14) bytes with the format (in MNF version 2 and lower):
dword UncompressedSize dword CompressedSize dword FileHash dword FileOffset byte CompressType byte ArchiveIndex word Unknown
In version 3 (and higher) MNF files the CompressType and ArchiveIndex fields are swapped:
dword UncompressedSize dword CompressedSize dword FileHash dword FileOffset byte ArchiveIndex byte CompressType word Unknown
The number of records is equal to RecordCountB3 found in the block header.
Uncompressed_Size = RecordCountB3 * 5 * FieldSize
The size fields are simply the size of the file when compressed and uncompressed. The compressed size are the number of bytes that need to be read from the appropriate DAT file. The FileOffset is the offset from the beginning of a DAT file where the file data begins. The CompressType is one of:
-
- 0 = Uncompressed
- 1 = zLib
- 2 = Snappy
The ArchiveIndex specifies which data file to find it in (i.e., ArchiveIndex=23 in eso.mnf means look in eso0023.dat). The last two bytes are unknown but take on a variety of purposes.
Block Type 0Edit
This block type is only found as the first block in eso.mnf. Like the other block the header information here is all stored in big endian bit order. This block is composed of a short fixed size header followed by two data blocks with the same format:
[Block Header (0x04 bytes)] [Data Header (0x04 bytes)] [Data] [Data Header (0x04 bytes)] [Data]
The block header format is a small 4 bytes:
word BlockID = 0x0000 word Unknown1 = 0x0000
In newer version of the client, the uncompressed size present in BlockId 3 is not present in BlockId 0, so the data header looks like:
dword CompressedSize
The CompressedSize is the number of data in bytes that follows the data header. The data for this block is unknown (appears to be compressed but unknown format).
In eso.mnf, there is a version 3 block following the version 0 block.
Extra DataEdit
The file eso.mnf has an unknown block of 00s at the end of the file 2497716 (0x261CB8) bytes in size.
NotesEdit
-
- eso.mnf
-
- RecordCount1 = 527361
- RecordCount2 = 258797
- RecordCount3 = 313810
- Block 1 Size = 2109444 (/4 = 527361)
- Block 2 Size = 2510480 (/8 = 313810)
- Block 3 Size = 6276200 (/20 = 313810)
- Non-Zero entries in Block 3 = 313364
- game.mnf
-
- RecordCount1 = 8241
- RecordCount2 = 3094
- RecordCount3 = 3094
- Block 1 Size = 32964 (/4 = 8241)
- Block 2 Size = 24752 (/8 = 3094)
- Block 3 Size = 61880 (/20 = 3094)
- Non-Zero entries in Block 3 = 3094
- esoaudioen.mnf
-
- RecordCount1 = 263681
- RecordCount2 = 120345
- RecordCount3 = 120345
- Block 1 Size = 1054724 (/4 = 263681)
- Block 2 Size = 962760 (/8 = 120345)
- Block 3 Size = 2406900 (/20 = 120345)
- Non-Zero entries in Block 3 = 120345
- The patch for the 8/02/2014 beta does not appear to have changed the MNF format.
Misc StuffEdit
Most of the below is slightly incorrect or out of date but kept here until the required information is merged to the main article above.
[MNF] - have 3 compressed blocks
0x4 - szID (always MES2) 0x15 - unknown (unique id?) 0x4 - szFilesCount (endian BIG) 0x4 - szFilesCount (endian BIG) 0x4 - szBlockSize (endian BIG) 0x4 - szBlockZSize (endian BIG) [........Block........] 0x4 - szBlockSize (endian BIG) 0x4 - szBlockZSize (endian BIG) [........Block........] 0x4 - szBlockSize (endian BIG) 0x4 - szBlockZSize (endian BIG)
Blocks 1 unknown tables
Block 2: 8 bytes per file? [Block 3] - Endian Little 0x4 - szFileSize 0x4 - szFileZSize 0x4 - szUnknown01 (Hash?) 0x4 - szOffset 0x1 - szComType(0 - Not Compressed, 1 - Zlib, 2 - Snappy) 0x1 - szArchiveNum 0x2 - szUnknown02 [........Block........]
Hash Init: 0xA8396u Use Hash.cpp from RDF-3X project (https://code.google.com/p/rdf3x/downloads/list) Notes on Hash from Scott Ewing: > Long story short, I don't think the hash function listed by the .mnf page is actually being used on filenames. That said, I did confirm that that hash function > *does* exist in the code, and that it is using the same 0xA8396u value as a hash input. Any uses of it have a static 'length' parameter as part of their input, > however, usually sitting at 8 or 4. Any combinations of length params and existing file names don't seem to be generating valid hashes.
unknown1 num_dat_archives EOF_offset dummy unknown2 FILENAME [02 00] [XX] 00 00 00 00 [XX XX XX XX] 00 03 [00 00 00] 04 [XX XX XX XX] eso.mnf: [02 00] [D0] 00 00 00 00 [7B FB 4F 00] 00 03 [00 00 00] 04 [00 08 0C 01] esoaudioen.mnf: [02 00] [04] 00 00 00 00 [2B 09 1E 00] 00 03 [00 00 00] 04 [00 04 06 01] game.mnf: [02 00] [01] 00 00 00 00 [3A A2 00 00] 00 03 [00 00 00] 04 [00 00 10 19] unknown1: This always has to be 2 for some reason. The files don't open otherwise. num_dat_archives: number of DAT files EOF_offset (little endian): After reading these 4 bytes, add EOF_offset to current offset to reach the end of the third block. This is EOF except for eso.mnf which has extra data. unknown2 (big endian): This is 4120*(2^n)+1, with n ranging from 0 to 2. It seems n is also the number of the DAT archive containing the ZOSFT, but maybe this is just a coincidence.
More detailed stuff for reference:
====================== FILE STRUCTURE ANALYSIS ====================== ------ DAT: Decompiled ------ 0x4 - ID string ('2SEP'/'PES2') 0x2 - always 1 (little endian) 0x4 - always 0 0x4 - always 0E (14 in decimal). Seems to be the absolute offset of the data start ------ MNF: Decompiled ------ 0x4 - ID string ('2SEM'/'MES2') 0x2 - always 2 (little endian) 0x1 - number of DAT files 0x4 - always 0 0x4 - EOF_offset: offset after block 3 ------ ZOSFT ---------------- 0x5 - ID string ('ZOSFT') 0x4 - always 06 00 10 00 0x6 - unknown 0x4 - number of records, little endian for(i=0; i<3; i++) { 0x4 - always 03 00 04 00 0x6 - unknown 0x4 - number of records 0x4 - number of records 0x4 - block size 0x4 - block zsize [block] 0x4 - block size 0x4 - block zsize [block] 0x4 - block size 0x4 - block zsize [block] } 0x4 - bytes until end of filetable [filenames: 0-terminated strings] 0x5 - ZOSFT ====================== ZLIB BLOCKS ANALYSIS ====================== ------ MNF ------------------ BLOCK 1 for(i=0; i<filecount; i++) { (0x4 - dummy)* - 00 00 00 00, 0 or more times 0x4 (little endian) - FILE_ID = 2147483648 of 00 00 00 80 + i (0x4 - dummy)* - 00 00 00 00, 0 or more times } BLOCK 2 for(i=0; i<filecount; i++) { 0x4 (little endian) - file_number 0x2 (little endian) - 00 00 or 01 00 in game, but can be anything in ESO 0x1 (little endian) - 00 80 or 00 00 in game, but can be 00 60, 00 40 or 00 20 in ESO as well! } BLOCK 3 for(i=0; i<filecount; i++) { 0x4 - file_size 0x4 - file_zsize 0x4 - filename_hash (http://rdf3x.googlecode.com/svn/trunk/infra/util/Hash.cpp - http://rdf3x.googlecode.com/svn/trunk/include/infra/util/Hash.hpp - init by 0xA8396u) 0x4 - file_offset 0x1 - file_comtype (0 - Not Compressed, 1 - Zlib, 2 - Snappy) 0x1 - archive_number 0x2 - unknown } ------ ZOSFT ---------------- - blocks 1_1, 2_1 or 3_1 is list of FILE IDs - 1772 filenames in game.zosft, EC600000! BLOCK 1_1 for(i=0; i<record_count; i++) { 0x4 (little endian) - FILE_ID = 2147483648 or 00 00 00 80 + i (0x4 - dummy)* - 00 00 00 00, 0 or more times } BLOCK 1_2 for(i=0; i<record_count; i++) { 0x8 (little endian) - complex_number } BLOCK 1_3 (simple count to record_count in game, but not in eso) for(i=1; i<=record_count; i++) { 0x4 (little endian) - file_number (unique) } BLOCK 2_1 for(i=0; i<record_count; i++) { 0x4 (little endian) - FILE_ID = 2147483648 or 00 00 00 80 + i (0x4 - dummy)* - 00 00 00 00, 0 or more times } BLOCK 2_2 same as block 1_3 BLOCK 2_3 = the most important! -------------- link to MNF block 2 for(i=0; i<record_count; i++) { 0x4 (little endian) - file_number (same as in block 1_3 and 2_2) 0x4 - filename_offset (relative, after number indicating bytes until end of filetable) 0x8 - complex_number (same as in block 1_2) } BLOCK 3: important for eso.mnf? BLOCK 3_1: Empty in game.mnf BLOCK 3_2: Not in game.mnf BLOCK 3_3: Not in game.mnf