Abrir menu principal

UESPWiki β

ESO Mod:MNF File Format

< File Formats

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.mnf, eso0000.mnf, esoaudioen.mnf).


File Format

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...]

File Header

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 */

Data Blocks

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 3

This is the most 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 Header

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.1

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.2

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.3

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:

   dword UncompressedSize
   dword CompressedSize
   dword FileHash
   dword FileOffset
   dword FileInfo = (Unknown (word) << 24) + (ArchiveIndex (byte) << 16) + (CompressType (byte)) 

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 0

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 (0x08 bytes)]
    [Data]
    [Data Header (0x08 bytes)]
    [Data]

The block header format is a small 4 bytes:

    word  BlockID = 0x0000
    word  Unknown1 = 0x0000

Like in block 3 the data is preceded by a short header:

    dword UncompressedSize
    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).

Extra Data

The file eso.mnf has an unknown block of 00s at the end of the file 2497716 (0x261CB8) bytes in size.

Notes

  • 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 Stuff

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)


             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