ME3Explorer Wiki
Advertisement

Unlike the first two installments in the trilogy, Mass Effect 3 stores its DLC content inside a compressed, proprietary archive called the SFAR. SFARs use both LZMA and LZX compression.

File Structure[]

The SFAR file is divided into four main chunks: header, file table, block size table, and blocks. This article will deal with the SFAR file specifications, for information about serializing code, consult the forum's research thread on SFAR file format [1].

Schematic representation of the SFAR file structure

Schematic representation of the SFAR file structure

Header

The Header contains information about the various chunks that comprise the file.

Bytes Type Description
4 uint Magic (0x53464152 = RAFS = SFAR reverse)
4 uint Version (always 0x00010000)
4 uint Data offset
4 uint Entry offset (always 0x20); start of File Table
4 uint File count
4 uint Block-Size Table offset
4 uint Max. block size (always 0x00010000)
4 char[ ] Compression scheme (None = 0x6E6F6E65; LZMA = 0x6C7A6D61; LZX = 0x6C7A7820)

File Table

The File Table is a list of file entries. The number of entries is defined in the file count field of the Header. Each entry struct is 0x1E (30) bytes long, with the following spec:

Bytes Type Description
4 uint HashA
4 uint HashB
4 uint HashC
4 uint HashD
4 uint Block-Size Table index
4 uint Uncompressed size
4 byte Uncompressed size Adder
4 uint Data offset
4 byte Data offset Adder

Despite having the uncompressed size and data offset fields in the above table, their actual values need to be calculated as follows:

   RealUncompressedSize = UncompressedSize + UncompressedSizeAdder << 32;
   RealDataOffset = DataOffset + DataOffsetAdder << 32;

If the field Block-Size index == -1 (0xFFFFFFFF), then this entry corresponds to exactly one block in the Blocks chunk. In which case:

   Block Size == RealUncompressedSize
   Block Offset == RealDataOffset

Block-Size Table

Each entry in the Block-Size Table is 2 bytes long (ushort), and this value specifies the Block-Size corresponding to an entry in the File Table.

Bytes Type Description
2 ushort Block size

An entry in the File Table refers to another entry in the Block-Size Table when its Block-Size index > -1 (0xFFFFFFFF). In which case:

   Block Size == Block-Size Table value
   Block Offset == RealDataOffset

Blocks

The Blocks chunk contains the raw files data inside the SFAR. From the previous sections it can be inferred that this data may or may not be compressed.

Locating File Names[]

One of the entries in the File Table contains the following hash:

   0xB5, 0x50, 0x19, 0xCB, 0xF9, 0xD3, 0xDA, 0x65, 0xD5, 0x5B, 0x32, 0x1C, 0x00, 0x19, 0x69, 0x7C

The content of this hash is a text string, with each file's relative path and name inside the SFAR defined in a new line. For example:

   /BIOGame/DLC/DLC_CON_END/PCConsoleTOC.bin
   /BIOGame/DLC/DLC_CON_END/CookedPCConsole/Mount.dlc
   /BIOGame/DLC/DLC_CON_END/CookedPCConsole/Default_DLC_CON_END.bin
   /BIOGame/DLC/DLC_CON_END/CookedPCConsole/ConditionalsDLC_CON_END.cnd
   /BIOGame/DLC/DLC_CON_END/CookedPCConsole/ConditionalsDLC_Shared.cnd
   /BIOGame/DLC/DLC_CON_END/CookedPCConsole/DLC_CON_END_DEU.tlk
   /BIOGame/DLC/DLC_CON_END/CookedPCConsole/DLC_CON_END_ESN.tlk
   /BIOGame/DLC/DLC_CON_END/CookedPCConsole/DLC_CON_END_FRA.tlk

To find the name of an entry in the File Table, each of the above lines have to be computed as an MD5 hash. Each character in a line must be sanitized using the following case statement:

       public static char Sanitize(char c)
       {
           switch ((ushort)c)
           {
               case 0x008C: return (char)0x9C;
               case 0x009F: return (char)0xFF;
               case 0x00D0:
               case 0x00DF:
               case 0x00F0:
               case 0x00F7: return c;
           }
           if ((c >= 'A' && c <= 'Z') || (c >= 'À' && c <= 'Þ'))
               return char.ToLowerInvariant(c);
           return c;
       }

Finally, each sanitized character is casted as byte, added to a byte array, then the whole array encrypted as an MD5 hash.

       public static byte[] ComputeHash(string input)
       {
           byte[] bytes = new byte[input.Length];
           for (int i = 0; i < input.Length; i++)
               bytes[i] = (byte)Sanitize(input[i]);
           var md5 = System.Security.Cryptography.MD5.Create();
           return md5.ComputeHash(bytes);
       }

The resulting hash can then be matched to those of each entry in the File Table to assign the corresponding file name.

References[]

Advertisement