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):
Usage | Media | Container format | CMAC type | cmac keyslot |
---|---|---|---|---|
Savegames | Gamecard | DISA | CTR-NOR0 | 0x19 / 0x33 |
Savegames | SD | DISA | CTR-SIGN | 0x30 |
System SaveData | NAND | DISA | CTR-SYS0 | 0x30 |
Private Extdata | SD | DIFF | CTR-EXT0 | 0x30 |
Shared Extdata | NAND | DIFF | CTR-EXT0 | 0x30 |
Title Database | SD | DIFF | CTR-9DB0 | 0x30 |
Title Database | NAND | DIFF | CTR-9DB0 | 0x0B |
Encryption #
DISA and DIFF formats don’t have their own encryption specification. They follow the encryption method defined by their media:
- Gamecard savegames have special wear leveling + encryption layers. See Savegames for detail.
- Files on SD follow the general SD filesystem encryption rule.
- Files on NAND are in cleartext, after decrypting the NAND partition encryption.
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.
Offset | Length | Description | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
0x00 | 8 | Magic "CTR-NOR0" | |||||||||
0x8 | 0x20 | SHA-256 of the following 0x108-byte block
|
CTR-SIGN #
This CMAC type is used for SD savegames. It is 0x30-byte long.
Offset | Length | Description | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
0x00 | 8 | Magic "CTR-SIGN" | |||||||||
0x08 | 8 | Title ID | |||||||||
0x10 | 0x20 | SHA-256 of the following 0x108-byte block
|
CTR-SYS0 #
This CMAC type is used for NAND system save. It is 0x110-byte long.
Offset | Length | Description |
---|---|---|
0x00 | 8 | Magic “CTR-SYS0” |
0x08 | 8 | Save ID. The higher word is always zero |
0x10 | 0x100 | Copy of the DISA header |
CTR-EXT0 #
This CMAC type is used for extdata. It is 0x11C-byte long.
Offset | Length | Description |
---|---|---|
0x00 | 8 | Magic “CTR-EXT0” |
0x08 | 8 | Extdata ID |
0x10 | 4 | 0 for Quota.dat, 1 otherwise |
0x14 | 4 | ID in the device file name. 0 for Quota.dat |
0x18 | 4 | ID in the device directory name that the file is in. 0 for Quota.dat |
0x1C | 0x100 | Copy of the DIFF header |
CTR-9DB0 #
This CMAC type is used for title database. It is 0x10C-byte long.
Offset | Length | Description |
---|---|---|
0x00 | 8 | Magic “CTR-9DB0” |
0x08 | 4 | Database ID. Each .db file has its own ID |
0x0C | 0x100 | Copy of the DIFF header |
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 #
Offset | Length | Description |
---|---|---|
0x00 | 4 | Magic “DISA” |
0x04 | 4 | Magic 0x40000 |
0x08 | 4 | Partition count, 1 or 2 |
0x0C | 4 | Padding |
0x10 | 8 | Secondary partition table offset |
0x18 | 8 | Primary partition table offset |
0x20 | 8 | Partition table size |
0x28 | 8 | Partition A descriptor offset in the partition table |
0x30 | 8 | Partition A descriptor size |
0x38 | 8 | Partition B descriptor offset in the partition table |
0x40 | 8 | Partition B descriptor size |
0x48 | 8 | Partition A offset |
0x50 | 8 | Partition A size |
0x58 | 8 | Partition B offset |
0x60 | 8 | Partition B size |
0x68 | 1 | Active partition table, 0 = primary, 1 = secondary |
0x69 | 3 | Padding |
0x6C | 0x20 | SHA-256 over the active partition table |
0x8C | 0x74 | Unused |
Note:
- When the partition count is 1, there is no partition B and all of its related fields are zero.
DIFF header #
Offset | Length | Description |
---|---|---|
0x00 | 4 | Magic “DIFF” |
0x04 | 4 | Magic 0x30000 |
0x08 | 8 | Secondary partition table/descriptor offset |
0x10 | 8 | Primary partition table/descriptor offset |
0x18 | 8 | Partition table/descriptor size |
0x20 | 8 | Partition (A) offset |
0x28 | 8 | Partition (A) size |
0x30 | 4 | Active partition descriptor, 0 = primary, 1 = secondary |
0x34 | 0x20 | SHA-256 over the active partition table/descriptor |
0x54 | 8 | Unique identifier |
0x5C | 0xA4 | Unused, 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.
Offset | Length | Description |
---|---|---|
0x00 | 4 | Magic “DIFI” |
0x04 | 4 | Magic 0x10000 |
0x08 | 8 | IVFC descriptor offset |
0x10 | 8 | IVFC descriptor size |
0x18 | 8 | DPFS descriptor offset |
0x20 | 8 | DPFS descriptor size |
0x28 | 8 | Partition hash offset |
0x30 | 8 | Partition hash size |
0x38 | 1 | If none zero, enable external IVFC level 4. |
0x39 | 1 | DPFS tree level 1 selector |
0x3A | 2 | Padding |
0x3C | 8 | External 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.
Offset | Length | Description |
---|---|---|
0x00 | 4 | Magic “IVFC” |
0x04 | 4 | Magic 0x20000 |
0x08 | 8 | Master hash size = partition master hash size in DIFI header |
0x10 | 8 | IVFC level 1 offset |
0x18 | 8 | IVFC level 1 size |
0x20 | 4 | IVFC level 1 block size in log2 |
0x24 | 4 | Padding |
0x28 | 8 | IVFC level 2 offset |
0x30 | 8 | IVFC level 2 size |
0x38 | 4 | IVFC level 2 block size in log2 |
0x3C | 4 | Padding |
0x40 | 8 | IVFC level 3 offset |
0x48 | 8 | IVFC level 3 size |
0x50 | 4 | IVFC level 3 block size in log2 |
0x54 | 4 | Padding |
0x58 | 8 | IVFC level 4 offset (unused if external IVFC level 4 enabled) |
0x60 | 8 | IVFC level 4 size |
0x68 | 4 | IVFC level 4 block size in log2 |
0x6C | 4 | Padding |
0x70 | 8 | IVFC 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.
Offset | Length | Description |
---|---|---|
0x00 | 4 | Magic “DPFS” |
0x04 | 4 | Magic 0x10000 |
0x08 | 8 | DPFS level 1 offset |
0x10 | 8 | DPFS level 1 size |
0x18 | 4 | DPFS level 1 block size in log2 (unused?) |
0x1C | 4 | Padding |
0x20 | 8 | DPFS level 2 offset |
0x28 | 8 | DPFS level 2 size |
0x30 | 4 | DPFS level 2 block size in log2 |
0x34 | 4 | Padding |
0x38 | 8 | DPFS level 3 offset |
0x40 | 8 | DPFS level 3 size |
0x48 | 4 | DPFS level 3 block size in log2 |
0x4C | 4 | Padding |
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 location0x1234 / 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 thek
-th chunk in levelx
, wherek = 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.