Open main menu

UESPWiki β

ESO Mod:MNF File Format

Mod / Elder Scrolls Online: ESO Mod: 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.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