DISA and DIFF

DISA and DIFF

This page describes DISA and DIFF format as the underlying container of Savegames, Extdata and Title Database. For further format specification of the inner data, please refer to their own page.

All data in this page is little-endian. All “unused / padding” fields can contain uninitialized data unless otherwise specified.

Overview #

DISA and DIFF are two container formats. They are very similar and are used for various purposes in 3DS. Here is a summary table of their usage, the CMAC type and the AES key slot used (the meaning of these is explained in the next section):

UsageMediaContainer formatCMAC typecmac keyslot
SavegamesGamecardDISACTR-NOR00x19 / 0x33
SavegamesSDDISACTR-SIGN0x30
System SaveDataNANDDISACTR-SYS00x30
Private ExtdataSDDIFFCTR-EXT00x30
Shared ExtdataNANDDIFFCTR-EXT00x30
Title DatabaseSDDIFFCTR-9DB00x30
Title DatabaseNANDDIFFCTR-9DB00x0B

Encryption #

DISA and DIFF formats don’t have their own encryption specification. They follow the encryption method defined by their media:

Format #

A DISA / DIFF file consists of the following components:

  • 0x100-byte AES CMAC
  • 0x100-byte Header
  • Secondary partition table
    • Contains one or two partition descriptors, depending on the number of partitions.
  • Primary partition table
    • Same layout as the secondary one
  • Partition A
  • Partition B (optional)
    • can only exist for DISA.

AES CMAC #

The AES CMAC is located at the beginning of the DISA / DIFF image, and it is 0x10 long. The rest 0xF0 bytes before the header are unused.

The key used for the AES CMAC is generated by the hardware key engine. See the keyslot it uses in the table above.

The data being authenticated by the AES CMAC is a 0x20-byte SHA-256 hash of a data block. The data block has different content formats among CMAC types. All types of data block contain a copy or a hash of the header, which is the start of the the rest of the verification chain, so the AES CMAC effectively authenticates the whole save image. Each type of data block is explained below.

CTR-NOR0 #

This CMAC type is used for gamecard savegames. It is 0x28-byte long.

OffsetLengthDescription
0x008Magic "CTR-NOR0"
0x80x20SHA-256 of the following 0x108-byte block

OffsetLengthDescription
0x008Magic "CTR-SAV0"
0x080x100Copy of the DISA header

CTR-SIGN #

This CMAC type is used for SD savegames. It is 0x30-byte long.

OffsetLengthDescription
0x008Magic "CTR-SIGN"
0x088Title ID
0x100x20SHA-256 of the following 0x108-byte block

OffsetLengthDescription
0x008Magic "CTR-SAV0"
0x080x100Copy of the DISA header

CTR-SYS0 #

This CMAC type is used for NAND system save. It is 0x110-byte long.

OffsetLengthDescription
0x008Magic “CTR-SYS0”
0x088Save ID. The higher word is always zero
0x100x100Copy of the DISA header

CTR-EXT0 #

This CMAC type is used for extdata. It is 0x11C-byte long.

OffsetLengthDescription
0x008Magic “CTR-EXT0”
0x088Extdata ID
0x1040 for Quota.dat, 1 otherwise
0x144ID in the device file name. 0 for Quota.dat
0x184ID in the device directory name that the file is in. 0 for Quota.dat
0x1C0x100Copy of the DIFF header

CTR-9DB0 #

This CMAC type is used for title database. It is 0x10C-byte long.

OffsetLengthDescription
0x008Magic “CTR-9DB0”
0x084Database ID. Each .db file has its own ID
0x0C0x100Copy of the DIFF header

The header located at offset 0x100 defines the rest components of the file (partitions and their tables). All offsets in the header are relative to the beginning of the DISA/DIFF file, except for partition descriptor offsets, which are relative to the beginning of the (active) partition table. DISA and DIFF have different header format.

DISA header #

OffsetLengthDescription
0x004Magic “DISA”
0x044Magic 0x40000
0x084Partition count, 1 or 2
0x0C4Padding
0x108Secondary partition table offset
0x188Primary partition table offset
0x208Partition table size
0x288Partition A descriptor offset in the partition table
0x308Partition A descriptor size
0x388Partition B descriptor offset in the partition table
0x408Partition B descriptor size
0x488Partition A offset
0x508Partition A size
0x588Partition B offset
0x608Partition B size
0x681Active partition table, 0 = primary, 1 = secondary
0x693Padding
0x6C0x20SHA-256 over the active partition table
0x8C0x74Unused

Note:

  • When the partition count is 1, there is no partition B and all of its related fields are zero.

DIFF header #

OffsetLengthDescription
0x004Magic “DIFF”
0x044Magic 0x30000
0x088Secondary partition table/descriptor offset
0x108Primary partition table/descriptor offset
0x188Partition table/descriptor size
0x208Partition (A) offset
0x288Partition (A) size
0x304Active partition descriptor, 0 = primary, 1 = secondary
0x340x20SHA-256 over the active partition table/descriptor
0x548Unique identifier
0x5C0xA4Unused, might contain leftover data

Note:

  • Since DIFF can only contain one partition, a partition table can only have one partition descriptor, so they become the same concept here.
  • See Extdata for its usage of the unique identifier field. For title database files, this field is zero.

Partition table & partition descriptor #

There are two partition tables, but only one of them is active. When operating on a DISA / DIFF file, 3DS FS alternately activate one of the two tables, presumably for data backup or atomic file writing. A newly created DISA / DIFF file may have entirely uninitialized data in the inactive partition table.

One partition table contains one or two partition descriptors , each of which describes the layout of one partition. A partition descriptor contains the following components:

  • DIFI header
  • IVFC descriptor
  • DPFS descriptor
  • Partition master hash

DIFI header #

The DIFI header locates at the beginning of a partition descriptor. This header defines the rest components of the partition descriptor (IVFC descriptor, DPFS descriptor and partition master hash). All offsets are relative to the beginning of the partition descriptor, except for External IVFC level 4 offset, which is relative to the beginning of the partition.

OffsetLengthDescription
0x004Magic “DIFI”
0x044Magic 0x10000
0x088IVFC descriptor offset
0x108IVFC descriptor size
0x188DPFS descriptor offset
0x208DPFS descriptor size
0x288Partition hash offset
0x308Partition hash size
0x381If none zero, enable external IVFC level 4.
0x391DPFS tree level 1 selector
0x3A2Padding
0x3C8External IVFC level 4 offset (zero if external IVFC level 4 disabled)

Note:

  • The meaning of fields after 0x38 are explained in the section #Partition

IVFC descriptor #

This header defines each level of IVFC tree (explained in the section #Partition). All offsets are relative to the beginning of DPFS level 3.

OffsetLengthDescription
0x004Magic “IVFC”
0x044Magic 0x20000
0x088Master hash size = partition master hash size in DIFI header
0x108IVFC level 1 offset
0x188IVFC level 1 size
0x204IVFC level 1 block size in log2
0x244Padding
0x288IVFC level 2 offset
0x308IVFC level 2 size
0x384IVFC level 2 block size in log2
0x3C4Padding
0x408IVFC level 3 offset
0x488IVFC level 3 size
0x504IVFC level 3 block size in log2
0x544Padding
0x588IVFC level 4 offset (unused if external IVFC level 4 enabled)
0x608IVFC level 4 size
0x684IVFC level 4 block size in log2
0x6C4Padding
0x708IVFC descriptor size? The value is usually 0x78

DPFS descriptor #

This header defines each level of DPFS tree (explained in the section #Partition). All offsets are relative to the beginning of the partition.

OffsetLengthDescription
0x004Magic “DPFS”
0x044Magic 0x10000
0x088DPFS level 1 offset
0x108DPFS level 1 size
0x184DPFS level 1 block size in log2 (unused?)
0x1C4Padding
0x208DPFS level 2 offset
0x288DPFS level 2 size
0x304DPFS level 2 block size in log2
0x344Padding
0x388DPFS level 3 offset
0x408DPFS level 3 size
0x484DPFS level 3 block size in log2
0x4C4Padding

Partition master hash #

This is a SHA-256 hash list over IVFC level 1. See #IVFC tree for explanation.

Partition #

A partition can have two types of layout. This is determined by the field DIFF + 0x38 (Enable external IVFC level 4).

The layout type 0 (external IVFC level 4 disabled) contains

  • DPFS level 1
  • DPFS level 2
  • DPFS level 3, and inside
    • IVFC level 1
    • IVFC level 2
    • IVFC level 3
    • IVFC level 4 (the actual content data)

The layout type 1 (external IVFC level 4 enabled) contains

  • DPFS level 1
  • DPFS level 2
  • DPFS level 3, and inside
    • IVFC level 1
    • IVFC level 2
    • IVFC level 3
  • IVFC level 4 (the actual content data, note that this is out side DPFS level 3)

DPFS tree #

Everything inside the DPFS tree comes in pairs, and at one time only one of a pair is active. The tree is probably designed for atomic writing: for a file writing operation, it writes to the inactive part, then commits the data by switching a bit to activate it.

Each level of DPFS tree consists of a pair of chunks. The size of one chunk is defined as it in the DPFS descriptor, so the total size of a level is actually twice as large as the size recorded in the descriptor. For level 1 and 2, each chunk is a bit array, in which each bit corresponds to a block in the next level (the block size of the next level is also defined in the DPFS descriptor). This bit indicates which one of the pair in the next level is active for this block: 0 means the first one and 1 means the second one. The active chunk of level 1 is selected by DPFS tree level 1 selector in the DIFI header. The bit array is encoded in u32 array, with MSB as the first bit of each 32 bits.

To access data in level 3, one needs to check the bits in level 1 and level 2 to know which chunk of level 3 is active for the accessed location. For example, for a following configuration:

  • Level 1: size = 4 bytes = 32 bits
  • Level 2: size = 0x380 bytes = 0x1C00 bits, block size = 0x80 bytes
  • Level 3: size = 0x1B7F000, block size = 0x1000, block size = 0x1000 bytes

if one want to read byte at 0x1234567 of level 3, the following calculation is performed:

  • get level 2 bit index 0x1234567 / 0x1000 = 0x1234, and its byte location 0x1234 / 8 = 0x246
  • get level 1 bit index 0x246 / 0x80 = 4
  • get level1_selector from DIFI header
  • read level2_selector = Level1[level1_selector].bits[4];
  • read level3_selector = Level2[level2_selector].bits[0x1234];
  • read data = Level3[level3_selector].bytes[0x1234567] as the final data
  • in the code above Levelx[k] means the k-th chunk in level x, where k = 0, 1. .bits[n] is expanded to (.u32_array[n / 32] >> (31 - n % 32)) & 1 as the bit array is encoded in u32 array.

Effectively, the active data is scattered among the two level 3 chunk. One can assemble the whole active level 3 image following the same rule.

IVFC tree #

The IVFC tree is used for data verification. It is very similar to the IVFC tree in RomFS, except that it has an additional level here. For level 1, 2 and 3, each level is a list of SHA-256 hash, of which each corresponds to a block of the next level which is zero-padded to align the block size (the block size of the next level is defined in the IVFC descriptor).

The partition master hash in the partition descriptor can be seen as IVFC level 0, which hashes level 1 following the same rule. The master hash is usually 0x20 long, consisting only one hash. This is because most DISA / DIFF files are not large enough to have multiple hashes on the top level, which isn’t the case for some title database files.

However, not all data is hashed - only ranges that have been written with valid data are properly hashed.

Level 4 is the actual content of the partition, which is what the container format essentially contains.

Extracting content from a DISA / DIFF container #

  • Find the active partition table and the partition(s).
  • Unwrap DPFS tree of partition(s) by reconstructing active data.
  • Unwrap IVFC tree. Either take out level 4 directly, or, better, verify all the hashes and poison the data that is not properly hashed.
  • The IVFC level 4 is the inner content of the file. The format of it varies among different usage. Refer their own page for further extraction.

Chain of trust #

  • AES CMAC verifies the header.
  • The header verifies the active partition table via the table hash.
  • In the partition table, each descriptor verifies level 1 of its IVFC tree via the master hash.
  • Each IVFC level verifies the next level, until the level 4, which is the inner content.

Summary diagram #

File:Disa-diff.png