Line 1: |
Line 1: |
− | This page describes the format and encryption of extdata, "extra data" stored on [[SD_Filesystem|SD card]] and [[Flash_Filesystem|NAND]]. | + | This page describes the format and encryption of extdata, "extra data" stored on SD card and NAND, at: |
− | At:
| |
− | * nand/data/<ID>/extdata/<ExtdataID-High>
| |
− | * sdmc/Nintendo 3DS/<ID0>/<ID1>/extdata/<ExtdataID-High>
| |
| | | |
− | (ExtdataID-High is always 00000000 for SD, and always 00048000 for NAND) Some titles can have Quota.dat stored in these directories. The directory-name for these directories is the ExtdataID-Low. Then there's a sub-directory 00000000, which contains the actual extdata. Size and number of files in this dir varies per title.
| + | * <code>nand/data/<ID>/extdata/<ExtdataID-High></code> |
− | NAND stores the shared extdata and is structured exactly the same way, see [[Flash Filesystem]].
| + | * <code>sdmc/Nintendo 3DS/<ID0>/<ID1>/extdata/<ExtdataID-High></code> |
| | | |
− | Extdata image 00000001 contains a VSXE partition for the FST, the actual file data is stored in the subsequent extdata images.
| + | ExtdataID-High is always 00000000 for SD, and always 00048000 for NAND. Regular apps can only mount SD extdata using the same extdataID which is stored in the CXI exheader. Therefore, regular apps which have the exheader extdataID set to zero can't use extdata. This restriction doesn't apply for shared extdata with extdataID high bitmask 0x48000 stored on NAND. System apps with a certain access right can mount arbitrary extdata. All NAND extdata is shared extdata, while all SD extdata is normal extdata. |
| | | |
− | Regular apps can only mount SD extdata using the same extdataID which is stored in the [[NCCH#CXI|CXI]] exheader. Therefore, regular apps which have the exheader extdataID set to zero can't use extdata. This restriction doesn't apply for shared extdata with extdataID high bitmask 0x48000 stored on NAND. System apps with a certain access right can mount arbitrary extdata.
| + | All data in this page is little-endian. All "unused / padding" fields can contain uninitialized data unless otherwise specified. |
− | All NAND extdata is shared extdata, while all SD extdata is normal extdata. Thus, normal extdata doesn't exist on NAND, and shared extdata doesn't exist on SD. The extdataID high excluding that bitmask is always zero for shared extdata.
| |
| | | |
− | === Format ===
| + | = Format = |
| | | |
− | All extdata is stored in [[DISA and DIFF|DIFF container files]] (follow this link for the container format description). The format description below is for the inner content of the containers.
| + | To avoid confusion, the terms '''device directory / file''' and '''virtual directory / file''' are used with the following meanings: |
| | | |
− | === Filesystem ===
| + | * '''Device directory / file''' are the real directory / file stored on SD / NAND that can be seen under path <code>nand/data/<ID>/extdata/</code> or <code>sdmc/Nintendo 3DS/<ID0>/<ID1>/extdata/</code>. |
| + | * '''Virtual directory / file''' are directory / file stored inside extdata virtual file system, which can be seen by applications in the mounted extdata archives. |
| | | |
− | Title extdata contains a series of extdata images which comprise an independent file system. The first image (00000001) contains the VSXE (FST) partition, and subsequent images each containing a single file. Other extdata images, such as Quota.dat and [[Title Database|database extdata]], exist independent of a FS.
| + | An extdata consists of several device directories and files, which forms a file system consisting of multiple virtual directories and files. |
| | | |
− | ==== VSXE ==== | + | An extdata with ID <code>ExtdataId</code> has the following device files: |
− | {| class="wikitable" | + | |
| + | * <code>.../extdata/<ExtdataID-High>/<ExtdataId-Low>/Quota.dat</code> (optional) |
| + | * <code>.../extdata/<ExtdataID-High>/<ExtdataId-Low>/<SubDirID>/<SubFileID></code> |
| + | |
| + | Note: |
| + | |
| + | * All device files are [[DISA and DIFF|DIFF containers]]. '''All format description below is about the inner content of the containers'''. Please unwrap these files first according to the DIFF format description before reading them using the extdata format description below. |
| + | * <code>Quota.dat</code> is only observed existing for NAND shared extdata. |
| + | * <code><SubDirID></code> and <code><SubFileID></code> are 8-digit hex strings. |
| + | * Device file with <code>SubDirID = SubFileID = 00000000</code> doesn't exist. Other ID combinations can exists. |
| + | * Device file with <code>SubDirID = 00000000</code> and <code>SubFileID = 00000001</code> is the VSXE metadata file and must exist. |
| + | * Other files, besides <code>Quota.dat</code> and <code>00000000/00000001</code>, are normal sub files, are these device files one-to-one correspond to virtual files. |
| + | * <code>SubDirID = 00000000</code> is usually the only one device directory that can be seen. See [[#Device Directory Capacity]] for more information. |
| + | |
| + | == Quota File == |
| + | |
| + | The inner data of <code>Quota.dat</code> is 0x48 bytes with the following format. The exact function of this file is unclear. |
| + | |
| + | {| class="wikitable" border="1" |
| + | ! Offset |
| + | ! Length |
| + | ! Description |
| + | |- |
| + | | 0x00 |
| + | | 4 |
| + | | Magic "QUOT" |
| + | |- |
| + | | 0x04 |
| + | | 4 |
| + | | Magic 0x30000 |
| + | |- |
| + | | 0x08 |
| + | | 4 |
| + | | 0x1000, block size? |
| + | |- |
| + | | 0x0C |
| + | | 4 |
| + | | Always 126. Probably device directory capacity. See the [[#Device Directory Capacity]] more information. |
| |- | | |- |
− | ! Start | + | | ... |
| + | | |
| + | |
| + | | The meaning of other fields is unknown |
| + | |} |
| + | |
| + | == Device Directory Capacity == |
| + | |
| + | A device directory in an extdata (a <code><SubDirID></code> directory) seems to have a maximum number of device files it can contain. For SD extdata, this maximum number seems to be hard-coded as 126. For NAND extdata, the number is probably indicated by a field in Quota.dat, which is, again, always 126 as observed. 3DS FS tries to put all device files in the device directory <code>00000000</code> if possible, and only when more than 126 files needed to add, a second device directory <code>00000001</code> and so on are created. However, few extdata have such amount of files to store, so the behavior lacks of use cases to confirm. |
| + | |
| + | The number 126 is probably from some kind of other capacity of 128 with <code>"."</code> and <code>".."</code> entries reserved. It is theorized that this is to keep a FAT directory table, with 0x20 bytes for each entry, in one 0x1000 cluster. The motivation is unclear. |
| + | |
| + | == VSXE File System Metadata == |
| + | |
| + | The inner data of <code>00000000/00000001</code> consists of the following components |
| + | |
| + | * VSXE header |
| + | * Directory Hash Table |
| + | * File Hash Table |
| + | * File Allocation Table |
| + | * Data region |
| + | ** Directory Entry Table |
| + | ** File Entry Table |
| + | |
| + | === VSXE Header === |
| + | |
| + | {| class="wikitable" border="1" |
| + | ! Offset |
| ! Length | | ! Length |
| ! Description | | ! Description |
| |- | | |- |
− | | 0x0 | + | | 0x00 |
| | 4 | | | 4 |
− | | Database Magic ("VSXE") | + | | Magic "VSXE" |
| |- | | |- |
− | | 0x4 | + | | 0x04 |
| | 4 | | | 4 |
− | | Magic Number (0x30000) | + | | Magic 0x30000 |
| |- | | |- |
− | | 0x8 | + | | 0x08 |
| | 8 | | | 8 |
− | | Data Table Offset | + | | File system Information offset (0x138) |
| |- | | |- |
| | 0x10 | | | 0x10 |
| | 8 | | | 8 |
− | | File Size, divided by the value at 0x18 | + | | Image size in blocks |
| |- | | |- |
| | 0x18 | | | 0x18 |
− | | 8 | + | | 4 |
− | | Usually 0x1000 | + | | Image block size |
| + | |- |
| + | | 0x1C |
| + | | 4 |
| + | | Padding |
| |- | | |- |
| | 0x20 | | | 0x20 |
Line 61: |
Line 126: |
| | 0x30 | | | 0x30 |
| | 4 | | | 4 |
− | | ID of most recently mounted Extdata image | + | | D of most recently mounted Extdata image |
| |- | | |- |
| | 0x34 | | | 0x34 |
Line 70: |
Line 135: |
| | 0x100 | | | 0x100 |
| | Mount path, from most recently mounted Extdata image | | | Mount path, from most recently mounted Extdata image |
− | |} | + | |- |
| + | | |
| | | |
− | Data Table:
| + | | |
| | | |
− | {| class="wikitable"
| + | | Below is File system Information, which is assumed following the same layout as [[Savegames#SAVE Header |
| |- | | |- |
− | ! Start
| + | | 0x138 |
− | ! Length
| + | | 4 |
− | ! Description
| + | | Unknown |
| |- | | |- |
− | | 0x0 | + | | 0x13C |
− | | 0x38 | + | | 4 |
− | | Unknown | + | | Data region block size |
| |- | | |- |
− | | 0x38 | + | | 0x140 |
| | 8 | | | 8 |
− | | Folder Table Offset | + | | Directory hash table offset |
− | |} | + | |- |
− | | + | | 0x148 |
− | ===== Folder Table =====
| + | | 4 |
− | | + | | Directory hash table bucket count |
− | Header:
| + | |- |
− | {| class="wikitable"
| + | | 0x14C |
| + | | 4 |
| + | | Padding |
| |- | | |- |
− | ! Start
| + | | 0x150 |
− | ! Length
| + | | 8 |
− | ! Description
| + | | File hash table offset |
| |- | | |- |
− | | 0x0 | + | | 0x158 |
− | | 0x4 | + | | 4 |
− | | Equivalent to the Used Folder Entries + 1 | + | | File hash table bucket count |
| |- | | |- |
− | | 0x4 | + | | 0x15C |
| | 4 | | | 4 |
− | | Equivalent to the Maximum Folder Entries + 1 | + | | Padding |
| + | |- |
| + | | 0x160 |
| + | | 8 |
| + | | File allocation table offset |
| |- | | |- |
− | | 0x8 | + | | 0x168 |
− | | 0x20 | + | | 4 |
− | | Unused | + | | File allocation table entry count |
− | |}
| |
− | | |
− | Folder Entry:
| |
− | {| class="wikitable"
| |
| |- | | |- |
− | ! Start
| + | | 0x16C |
− | ! Length
| + | | 4 |
− | ! Description
| + | | Padding |
| |- | | |- |
− | | 0x0 | + | | 0x170 |
− | | 0x4 | + | | 8 |
− | | Parent Folder Index | + | | Data region offset |
| |- | | |- |
− | | 0x4 | + | | 0x178 |
− | | 0x10 | + | | 4 |
− | | Folder Name (ASCII) | + | | Data region block count (= File allocation table entry count) |
| |- | | |- |
− | | 0x14 | + | | 0x17C |
− | | 0x4 | + | | 4 |
− | | Previous Folder's Index | + | | Padding |
| |- | | |- |
− | | 0x18 | + | | 0x180 |
− | | 0x4 | + | | 4 |
− | | Last Folder Index Entry | + | | Directory entry table starting block |
| |- | | |- |
− | | 0x1C | + | | 0x184 |
− | | 0x4 | + | | 4 |
− | | Last File Index Entry | + | | Directory entry table block count |
| |- | | |- |
− | | 0x20 | + | | 0x188 |
− | | 0x4 | + | | 4 |
− | | Unknown | + | | Maximum directory count |
| |- | | |- |
− | | 0x2C | + | | 0x18C |
− | | 0x4 | + | | 4 |
− | | Unknown | + | | Padding |
− | |}
| |
− | * The folder id/index for the current entry is related to it's position in the Folder table. The folder table is accessed like an array of 0x28 byte chunks, with the header consuming index = 0, root directory at index = 1, and the subsequent folder entries following.
| |
− | | |
− | ===== File Table =====
| |
− | | |
− | The location of the File table is calculated by aligning the end offset of the folder table to 0x1000 bytes.
| |
− | | |
− | Header:
| |
− | {| class="wikitable"
| |
| |- | | |- |
− | ! Start
| + | | 0x190 |
− | ! Length
| + | | 4 |
− | ! Description
| + | | File entry table starting block |
| |- | | |- |
− | | 0x0 | + | | 0x194 |
− | | 0x4 | + | | 4 |
− | | Equivalent to the Used File Entries + 1 | + | | File entry table block count |
| |- | | |- |
− | | 0x4 | + | | 0x198 |
| | 4 | | | 4 |
− | | Equivalent to the Maximum File Entries + 1 | + | | Maximum file count |
| |- | | |- |
− | | 0x8 | + | | 0x19C |
− | | 0x28 | + | | 4 |
− | | Unused | + | | Padding |
| |} | | |} |
| | | |
− | Folder Entry:
| + | * All "offsets" are relative to the beginning of VSXE image. All "starting block index" are relative to the beginning of data region. |
− | {| class="wikitable" | + | |
− | |-
| + | === File Allocation Table & Data Region === |
− | ! Start | + | |
| + | These function in the same way as [[Savegames#File Allocation Table|the file allocation in savegames]]. However, the only two "files" allocated in the data region are the directory entry table and file entry table, so the data region is usually pretty small, and the file allocation table is unchanged once created and has no free blocks. Thus, the offset and size of directory / file entry table can be found directly by <code>offset = entry_table_starting block * data_region_block_size + data_region_offset</code> and <code>size = entry_table_block_count * data_region_block_size</code> |
| + | |
| + | === Directory Hash Table & File Hash Table === |
| + | |
| + | These function in the same way as [[Savegames#Directory Hash Table & File Hash Table|those in savegames]] |
| + | |
| + | === Directory Entry Table === |
| + | |
| + | This functions in the same way as [[Savegames#Directory Entry Table|the one in savegames]]. It lists all virtual directories in this extdata. |
| + | |
| + | === File Entry Table === |
| + | |
| + | This functions in a similar way as [[Savegames#File Entry Table|the one in savegames]]. It lists all virtual files in this extdata. However, the format of a (non-dummy) file entry is a little bit modified: |
| + | |
| + | {| class="wikitable" border="1" |
| + | ! Offset |
| ! Length | | ! Length |
| ! Description | | ! Description |
| |- | | |- |
− | | 0x0 | + | | 0x00 |
− | | 0x4 | + | | 4 |
− | | Parent Folder Index | + | | Parent directory index in directory entry table |
| |- | | |- |
− | | 0x4 | + | | 0x04 |
− | | 0x10 | + | | 16 |
− | | File Name (ASCII) | + | | ASCII file name |
| |- | | |- |
| | 0x14 | | | 0x14 |
− | | 0x4 | + | | 4 |
− | | Previous File's Index | + | | Next sibling file index. 0 if this is the last one |
| |- | | |- |
| | 0x18 | | | 0x18 |
− | | 0x4 | + | | 4 |
− | | Unknown | + | | Padding |
| |- | | |- |
| | 0x1C | | | 0x1C |
− | | 0x4 | + | | 4 |
− | | Unknown | + | | <s>First block index in data region</s> '''Always 0x80000000 because unused''' |
| |- | | |- |
| | 0x20 | | | 0x20 |
− | | 0x8 | + | | 8 |
− | | Unique Extdata ID | + | | <s>File size</s> '''Unique identifier''' |
| |- | | |- |
| | 0x28 | | | 0x28 |
− | | 0x4 | + | | 4 |
− | | Unknown | + | | Padding? |
| |- | | |- |
| | 0x2C | | | 0x2C |
− | | 0x4 | + | | 4 |
− | | Unknown | + | | Index of the next file in the same hash table bucket. 0 if this is the last one |
| |} | | |} |
− | * The file id/index for the current entry is related to it's position in the File Table, much like the folder entries in the Folder Table. The file table is accessed like an array of 0x30 byte chunks, with the header consuming index = 0, and the subsequent file entries following. The relationship between the index value of the file entry, and the actual file name of the extdata image that contains it it = index+1. For instance icon (the only file in every extdata), comes right after the header, with an index value of '1', and the icon is stored in extdata image '00000002'.
| |
− |
| |
− | * The Unique Extdata ID, is the same value found in the DIFF header of the referenced extdata image for that file. The value changes most times the file in question is modified. When mounting an extdata image in the VSXE filesystem, if the file's extdata image doesn't have the expected Unique Extdata ID, it won't be mounted.
| |
| | | |
− | ==== VSXE Filesystem structure ====
| + | Each non-dummy file entry corresponds to a device file. The path to the device file is generated by the following computation: |
| | | |
− | When extdata is created, these are *always* created regardless of whether the title actually uses them.
| + | <pre>// See previous section about this capacity |
| + | const uint32_t device_dir_capacity = 126; |
| | | |
− | * /icon This file contains the extdata [[SMDH|icon]] displayed in data management. This icon can only be written to by titles when creating extdata, titles would have to recreate extdata to change the icon. This file can't be read directly, instead it is read via [[FS:ReadExtSaveDataIcon]].
| + | // entry index is the index in the file entry table, with the first dummy entry as |
− | * /user/ Contains the title's actual extdata files.
| + | // index = 0, which is never used for a real file. |
− | * /boss/ Can contain [[SpotPass]] content. SpotPass content can only be downloaded to this /boss directory.
| + | // file_index = 1 is reserved for the VSXE Filesystem Metadata itself, so real files |
| + | // started from file_index = 2. |
| + | uint32_t file_index = entry_index + 1; |
| | | |
− | User extdata and SpotPass extdata use separate [[FS:OpenArchive|mount]] points at /user and /boss. Therefore one mount can't access the other directory, and also can't access /icon.(The title's SpotPass extdata can be mounted by the title itself, if it uses SpotPass)
| + | uint32_t SubDirID = file_index / device_dir_capacity; |
| + | uint32_t SubFileID = file_index % pdevice_dir_capacity; |
| | | |
| + | char extdata_path[...]; // ".../extdata/<ExtdataID-High>/<ExtdataId-Low>" |
| + | char device_path[...]; // output path |
| + | sprintfdevice_path, "%s/%08x/%08x", extdata_path, SubDirID, SubFileID); |
| + | </pre> |
| + | When mounting extdata, the unique identifier is used to match the ID stored in subfile's [[DISA and DIFF#DIFF header|DIFF header]]. If the ID doesn't match, mounting will fail. |
| | | |
− | Other optional but notable directories include:
| + | == Virtual File System Structure == |
− | * /user/ExBanner This directory can optionally store [[Extended_Banner| extended banners]]. When this is available, this banner is displayed instead of the [[CXI]] ExeFS banner. COMMON.bin stores the common exbanner, while <regionlang_code>.bin stores an optional separate region/language specific banner.(regionlang_code can be "JPN_JP", "USA_EN", etc)
| |
| | | |
− | === Extdata without an independent FS ===
| + | When extdata is created, these are ''always'' created regardless of whether the title actually uses them. |
| | | |
− | ==== Quota.dat ====
| + | * <code>/icon</code> This virtual file contains the extdata icon displayed in data management. This icon can only be written to by titles when creating extdata, titles would have to recreate extdata to change the icon. This file can't be read directly, instead it is read via FS:ReadExtSaveDataIcon. |
| + | * <code>/user/</code> This virtual directory contains the title's actual extdata files. |
| + | * <code>/boss/</code> This virtual directory can contain SpotPass content. SpotPass content can only be downloaded to this <code>/boss</code> virtual directory. |
| | | |
− | * This is contained in the Quota.dat extdata image.
| + | User extdata and SpotPass extdata use separate mount points at <code>/user</code> and <code>/boss</code>. Therefore one mount can't access the other virtual directory, and also can't access <code>/icon</code>.(The title's SpotPass extdata can be mounted by the title itself, if it uses SpotPass) |
| | | |
− | {| class="wikitable"
| + | Other optional but notable directories include: |
− | |-
| |
− | ! Start
| |
− | ! Length
| |
− | ! Description
| |
− | |-
| |
− | | 0x0
| |
− | | 4
| |
− | | Magic ("QUOT")
| |
− | |-
| |
− | | 0x4
| |
− | | 4
| |
− | | Magic Number (0x30000)
| |
− | |-
| |
− | | 0x8
| |
− | | 8
| |
− | | Unknown
| |
− | |-
| |
− | | 0x10
| |
− | | 0x38
| |
− | | Unknown
| |
− | |}
| |
− | It's unknown what this is used for.
| |
− | | |
− | ==== Database Extdata ====
| |
| | | |
− | See [[Title Database|here]].
| + | * <code>/user/ExBanner</code> This virtual directory can optionally store extended banners. When this is available, this banner is displayed instead of the CXI ExeFS banner. <code>COMMON.bin</code> stores the common exbanner, while <code><regionlang_code>.bin</code> stores an optional separate region/language specific banner.(regionlang_code can be "JPN_JP", "USA_EN", etc) |
| | | |
− | === SD Extdata ===
| + | == SD Extdata == |
| Usually the ExtdataID low is in the format '00<Unique ID>' | | Usually the ExtdataID low is in the format '00<Unique ID>' |
| | | |
Line 571: |
Line 629: |
| |} | | |} |
| | | |
− | === NAND Shared Extdata ===
| + | == NAND Shared Extdata == |
| {| class="wikitable" border="1" | | {| class="wikitable" border="1" |
| |- | | |- |