XCI: Difference between revisions

Basic description of HFS0 table (details incoming, also see https://gist.github.com/SciresM/46bade173227e24e1fef532a73a320a7
mNo edit summary
 
(44 intermediate revisions by 9 users not shown)
Line 1: Line 1:
=Header=
This is the format used for storing the contents of a Nintendo Switch Gamecard.
The header is 0x200-bytes, at Gamecard+0.


= Structure =
{| class="wikitable" border="1"
|-
! Offset
! Size
! Description
|-
| 0x0
| 0x1000
| [[#CardKeyArea]]
|-
| 0x1000
| 0x200
| [[#CardHeader]]
|-
| 0x1200
| 0x200
| [11.0.0+] [[#NewCardHeader]]
|-
| 0x1400
| 0x400
| [11.0.0+] [[#NewCardHeaderCertArea]]
|-
| 0x1800
| 0x100
| [11.0.0+] NewCardHeaderCertAreaModulus
|-
| 0x1900
| 0x6700
| Reserved
|-
| 0x8000
| 0x8000
| [[#CertArea]]
|-
| 0x10000
| Variable
| [[#NormalArea]]
|-
| Variable
| Variable
| [[#RomArea]]
|-
| Invalid
| Invalid
| BackupArea
|}
== CardKeyArea ==
This region cannot be read directly once written to the Gamecard. Therefore, it is hidden away during read/write operations on the raw Gamecard data.
{| class="wikitable" border="1"
|-
! Offset
! Size
! Description
|-
| 0x0
| 0x200
| [[#InitialData]]
|-
| 0x200
| 0xD00
| [[#TitleKeyArea]]
|-
| 0xF00
| 0x100
| Reserved
|}
=== InitialData ===
This region is used for challenge–response authentication when changing to the Gamecard's secure mode.
[[Filesystem_services|FS]] calculates a SHA-256 hash over the whole 0x200 bytes and compares it with the hash stored at offset 0x160 in the [[#CardHeader]].
{| class="wikitable" border="1"
|-
! Offset
! Size
! Description
|-
| 0x0
| 0x8
| Package ID from [[#CardHeader]] at offset 0x110
|-
| 0x8
| 0x8
| Reserved
|-
| 0x10
| 0x10
| Challenge–response authentication data
|-
| 0x20
| 0x10
| Challenge–response authentication MAC
|-
| 0x30
| 0xC
| Challenge–response authentication Nonce
|-
| 0x3C
| 0x1C4
| Reserved (must be empty)
|}
=== TitleKeyArea ===
This region is stored encrypted and contains the title keys used by the [[#InitialData]].
{| class="wikitable" border="1"
|-
! Offset
! Size
! Description
|-
| 0x0
| 0x8
| TitleKey1
|-
| 0x8
| 0x8
| TitleKey2
|-
| 0x10
| 0xCF0
| Reserved
|}
== CardHeader ==
{| class="wikitable" border="1"
{| class="wikitable" border="1"
|-
|-
Line 10: Line 138:
| 0x0
| 0x0
| 0x100
| 0x100
| RSA-2048 signature, presumably.
| RSA-2048 PKCS #1 signature over the header (data from 0x100 to 0x200)
|-
|-
| 0x100
| 0x100
| 0x4
| 0x4
| Magicnum "HEAD"
| Magic ("HEAD")
|-
|-
| 0x104
| 0x104
| 0x4
| 0x4
| Offset of Secure partition (Size of non-secure data?), in Media Units
| RomAreaStartPageAddress (in Gamecard page units, which are 0x200 bytes)
|-
|-
| 0x108
| 0x108
| 0x4
| 0x4
| 0xFFFFFFFF
| BackupAreaStartPageAddress (always 0xFFFFFFFF)
|-
|-
| 0x10C
| 0x10C
| 0x1
| TitleKeyDecIndex (high nibble) and KekIndex (low nibble)
|-
| 0x10D
| 0x1
| [[#RomSize]]
|-
| 0x10E
| 0x1
| [[#Version]]
|-
| 0x10F
| 0x1
| [[#Flags]]
|-
| 0x110
| 0x8
| PackageId (used for challenge–response authentication)
|-
| 0x118
| 0x4
| ValidDataEndAddress (in Gamecard page units, which are 0x200 bytes)
|-
| 0x11C
| 0x4
| Reserved
|-
| 0x120
| 0x10
| Iv (reversed)
|-
| 0x130
| 0x8
| PartitionFsHeaderAddress
|-
| 0x138
| 0x8
| PartitionFsHeaderSize
|-
| 0x140
| 0x20
| PartitionFsHeaderHash (SHA-256 hash of the [[#PartitionFsHeader]])
|-
| 0x160
| 0x20
| InitialDataHash (SHA-256 hash of the [[#InitialData]])
|-
| 0x180
| 0x4
| [[#SelSec]]
|-
| 0x184
| 0x4
| SelT1Key (always 2)
|-
| 0x188
| 0x4
| SelKey (always 0)
|-
| 0x18C
| 0x4
| LimArea (in Gamecard page units, which are 0x200 bytes)
|-
| 0x190
| 0x70
| [[#CardHeaderEncryptedData]]
|}
=== RomSize ===
[[Filesystem_services|FS]] retrieves this data as [[Filesystem_services#GameCardSize|GameCardSize]].
{| class="wikitable" border="1"
|-
! Value
! Description
|-
| 0xFA
| 1GB
|-
| 0xF8
| 2GB
|-
| 0xF0
| 4GB
|-
| 0xE0
| 8GB
|-
| 0xE1
| 16GB
|-
| 0xE2
| 32GB
|}
=== Version ===
{| class="wikitable" border="1"
|-
! Value
! Description
|-
| 0
| Default
|-
| 1
|
|-
| 2
|
|-
| 3
| [20.0.0+] T2Supported
|}
=== Flags ===
[[Filesystem_services|FS]] retrieves this data as [[Filesystem_services#GameCardAttribute|GameCardAttribute]].
{| class="wikitable" border="1"
|-
! Bits
! Description
|-
| 0
| AutoBoot
|-
| 1
| HistoryErase
|-
| 2
| [4.0.0+] RepairTool
|-
| 3
| [9.0.0+] DifferentRegionCupToTerraDevice
|-
| 4
| [9.0.0+] DifferentRegionCupToGlobalDevice
|-
| 7
| [11.0.0+] CardHeaderSignKey
|}
=== SelSec ===
{| class="wikitable" border="1"
|-
! Value
! Description
|-
| 1
| T1
|-
| 2
| T2
|}
=== CardHeaderEncryptedData ===
This region is stored encrypted (AES-128-CBC).
{| class="wikitable" border="1"
|-
! Offset
! Size
! Description
|-
| 0x0
| 0x8
| [[#FwVersion]]
|-
| 0x8
| 0x4
| [[#AccCtrl1]]
|-
| 0xC
| 0x4
| Wait1TimeRead (always 0x1388)
|-
| 0x10
| 0x4
| Wait2TimeRead (always 0)
|-
| 0x14
| 0x4
| Wait1TimeWrite (always 0)
|-
| 0x18
| 0x4
| Wait2TimeWrite (always 0)
|-
| 0x1C
| 0x4
| FwMode (the current SdkAddonVersion)
|-
| 0x20
| 0x4
| UppVersion
|-
| 0x24
| 0x1
| [9.0.0+] [[#CompatibilityType]]
|-
| 0x25
| 0x3
| Reserved
|-
| 0x28
| 0x8
| UppHash (SHA-256 hash of the [[#UpdatePartition]])
|-
| 0x30
| 0x8
| UppId (always 0x0100000000000816)
|-
| 0x38
| 0x38
| Reserved
|}
==== FwVersion ====
{| class="wikitable" border="1"
|-
! Value
! Description
|-
| 0
| Development
|-
| 1
| Retail
|-
| 2
| [4.0.0+] Retail
|-
| 3
| [11.0.0+] Development
|-
| 4
| [11.0.0+] Retail
|-
| 5
| [12.0.0+] Retail
|}
==== AccCtrl1 ====
{| class="wikitable" border="1"
|-
! Value
! Description
|-
| 0x00A10011
| 25MHz
|-
| 0x00A10010
| 50MHz
|}
==== CompatibilityType ====
{| class="wikitable" border="1"
|-
! Value
! Description
|-
| 0
| Normal
|-
| 1
| Terra
|}
== NewCardHeader ==
{| class="wikitable" border="1"
|-
! Offset
! Size
! Description
|-
| 0x0
| 0x100
| RSA-2048 PKCS #1 signature over the header (data from 0x100 to 0x200)
|-
| 0x100
| 0x4
| Magic ("HEAD")
|-
| 0x104
| 0x4
| RomAreaStartPageAddress (in Gamecard page units, which are 0x200 bytes)
|-
| 0x108
| 0x4
| 0x4
| ?
| BackupAreaStartPageAddress (always 0xFFFFFFFF)
|-
| 0x10C
| 0x1
| TitleKeyDecIndex (high nibble) and KekIndex (low nibble)
|-
| 0x10D
| 0x1
| [[#RomSize]]
|-
| 0x10E
| 0x1
| [[#Version]]
|-
| 0x10F
| 0x1
| [[#Flags]]
|-
|-
| 0x110
| 0x110
| 0x8
| 0x8
| ?
| PackageId (used for challenge–response authentication)
|-
|-
| 0x118
| 0x118
| 0x8
| 0x4
| Size of the Gamecart, in Media Units
| ValidDataEndAddress (in Gamecard page units, which are 0x200 bytes)
|-
| 0x11C
| 0x1
| [20.0.0+] CardHeaderSignKeyIndex ([1.0.0-19.0.1] Reserved)
|-
| 0x11D
| 0x1
| [18.0.0+] [[#Flags2]] ([1.0.0-17.0.1] Reserved)
|-
| 0x11E
| 0x2
| [19.0.0+] [[#NumberOfApplicationIds]] ([1.0.0-18.1.0] Reserved)
|-
|-
| 0x120
| 0x120
| 0x10
| 0x10
| ?
| Iv (reversed)
|-
|-
| 0x130
| 0x130
| 0x8
| 0x8
| Offset of HFS0 FS partition
| PartitionFsHeaderAddress
|-
|-
| 0x138
| 0x138
| 8
| 0x8
| HFS0 Header size
| PartitionFsHeaderSize
|-
|-
| 0x140
| 0x140
| 0x20
| 0x20
| SHA256 hash of the HFS0 Header
| PartitionFsHeaderHash (SHA-256 hash of the [[#PartitionFsHeader]])
|-
|-
| 0x160
| 0x160
| 0x20
| 0x20
| SHA256 hash of ?
| InitialDataHash (SHA-256 hash of the [[#InitialData]])
|-
|-
| 0x180
| 0x180
| 0x4
| 0x4
| 1?
| [[#SelSec]]
|-
|-
| 0x184
| 0x184
| 0x4
| 0x4
| 2?
| SelT1Key (always 2)
|-
|-
| 0x188
| 0x188
| 0x4
| 0x4
| 0?
| SelKey (always 0)
|-
|-
| 0x18C
| 0x18C
| 0x4
| 0x4
| Offset of Secure partition (Size of non-secure data?), in Media Units, again.
| LimArea (in Gamecard page units, which are 0x200 bytes)
|-
|-
| 0x190
| 0x190
| 0x70
| 0x70
| Encrypted data/hashes of some kind
| [[#NewCardHeaderEncryptedData]]
|}
 
=== Flags2 ===
[[Filesystem_services|FS]] retrieves this data as [[Filesystem_services#GameCardAttribute2|GameCardAttribute2]].
 
=== NumberOfApplicationIds ===
This is the number of entries in the ApplicationIdList located right before ValidDataEndAddress.
 
=== NewCardHeaderEncryptedData ===
This region is stored encrypted (AES-128-CBC).
 
{| class="wikitable" border="1"
|-
! Offset
! Size
! Description
|-
| 0x0
| 0x8
| [[#FwVersion]]
|-
| 0x8
| 0x4
| [[#AccCtrl1]]
|-
| 0xC
| 0x4
| Wait1TimeRead (always 0x1388)
|-
| 0x10
| 0x4
| Wait2TimeRead (always 0)
|-
| 0x14
| 0x4
| Wait1TimeWrite (always 0)
|-
| 0x18
| 0x4
| Wait2TimeWrite (always 0)
|-
| 0x1C
| 0x4
| FwMode (the current SdkAddonVersion)
|-
| 0x20
| 0x4
| UppVersion
|-
| 0x24
| 0x1
| [[#CompatibilityType]]
|-
| 0x25
| 0x3
| Reserved
|-
| 0x28
| 0x8
| UppHash (SHA-256 hash of the [[#UpdatePartition]])
|-
| 0x30
| 0x8
| UppId (always 0x0100000000000816)
|-
| 0x38
| 0x8
| Reserved
|-
| 0x40
| 0x20
| RelatedCardHeaderHash (SHA-256 hash of [[#CardHeader]])
|-
| 0x60
| 0x10
| Reserved
|}
|}


== NewCardHeaderCertArea ==
{| class="wikitable" border="1"
|-
! Offset
! Size
! Description
|-
| 0x0
| 0x100
| RSA-2048 PKCS #1 signature over the data from 0x100 to 0x400
|-
| 0x100
| 0x4
| Magic
|-
| 0x104
| 0x4
| Version
|-
| 0x108
| 0x8
|
|-
| 0x110
| 0x1
| CardHeaderSignKeyIndex
|-
| 0x111
| 0x1F
| Reserved
|-
| 0x130
| 0x100
| Modulus
|-
| 0x230
| 0x3
| PublicExponent
|-
| 0x233
| 0x1CD
| Reserved
|}


= Cert =
== CertArea ==
This is the Gamecard's unique certificate.


This is for the CERT, located at Gamecard + 0x7000 (always?). This matches exactly the output from fsp-srv IDeviceOperator cmd 206 "GetGameCardDeviceCertificate".
[[Filesystem_services|FS]] retrieves this data with [[Filesystem_services#GetGameCardDeviceCertificate|GetGameCardDeviceCertificate]].


{| class="wikitable" border="1"
{| class="wikitable" border="1"
Line 90: Line 653:
| 0x0
| 0x0
| 0x100
| 0x100
| RSA-2048 signature, presumably.
| RSA-2048 PKCS #1 signature over the data from 0x100 to 0x200
|-
|-
| 0x100
| 0x100
| 0x4
| 0x4
| Magicnum "CERT"
| Magic ("CERT")
|-
| 0x104
| 0x4
| Version
|-
| 0x108
| 0x1
| KekIndex
|-
| 0x109
| 0x7
| Reserved
|-
|-
| 0x110
| 0x110
| 0x10
| 0x10
| ?
| T1CardDeviceId
|-
| 0x120
| 0x10
| Iv
|-
| 0x130
| 0x10
| HwKey (encrypted)
|-
| 0x140
| 0xC0
| Reserved (encrypted)
|-
| 0x200
| 0x7E00
| Reserved
|}
 
== NormalArea ==
This region contains all non-secure partitions of the Gamecard file system.
 
{| class="wikitable" border="1"
|-
! Offset
! Size
! Description
|-
| Variable
| Variable
| [[#PartitionFsHeader|RootPartitionHeader]]
|-
| Variable
| Variable
| [[#PartitionFsHeader|UpdatePartitionHeader]]
|-
| Variable
| Variable
| [[#UpdatePartition|UpdatePartition]]
|-
| Variable
| Variable
| [4.0.0+] [[#PartitionFsHeader|LogoPartitionHeader]]
|-
| Variable
| Variable
| [4.0.0+] [[#LogoPartition|LogoPartition]]
|-
| Variable
| Variable
| [[#PartitionFsHeader|NormalPartitionHeader]]
|-
| Variable
| Variable
| [[#NormalPartition|NormalPartition]]
|}
 
=== UpdatePartition ===
This partition contains .cnmt.nca + .nca files for the entire system update required to play the game. Launch day carts contain a full copy of 1.0 ncas, newer carts contain newer sysupdate NCAs etc.
 
=== NormalPartition ===
This partition contains the .cnmt.nca and the game icondata nca. This is presumably for future compatibility so that if a future update changes the cryptographic protocol for the secure partition. Game icon data can still be shown in the home menu on old firmwares.
 
[4.0.0+] This partition is now empty.
 
=== LogoPartition ===
[4.0.0+] This partition now contains the contents of the [[#NormalPartition]].
 
== RomArea ==
This region contains all secure partitions of the Gamecard file system.
 
{| class="wikitable" border="1"
|-
! Offset
! Size
! Description
|-
|-
| 0x12A
| Variable
| 0xD6
| Variable
| Encrypted data. Some kind of key?
| [[#PartitionFsHeader|SecurePartitionHeader]]
|-
| Variable
| Variable
| [[#SecurePartition|SecurePartition]]
|}
|}


The data between the CERT and the start of the HFS0 is all 0xFF.
=== SecurePartition ===
This partition contains an identical copy of the .cnmt.nca and game icondata nca, as well as all other ncas required for the game.
 
== PartitionFs ==
This is the Gamecard file system which starts with magicnum "HFS0".
 
=== PartitionFsHeader ===
The "SHA-256 File System" or "HFS0" starts at offset 0x10000 in the Gamecard. The first 0x200 bytes act as a global header and represent the root partition which points to the other partitions ("normal", "logo", "update" and "secure").


= HFS0 =
A hash for this header is stored at offset 0x140 in the [[#CardHeader]].
This is the FS which has magicnum "HFS0" at header+0.


{| class="wikitable" border="1"
{| class="wikitable" border="1"
Line 118: Line 778:
| 0x0
| 0x0
| 0x4
| 0x4
| HFS0 Magic
| Magic ("HFS0")
|-
|-
| 0x4
| 0x4
| 0x4
| 0x4
| Number of files
| FileCount
|-
|-
| 0x8
| 0x8
| 0x4
| 0x4
| Size of the string table
| StringTableSize
|-
|-
| 0xC
| 0xC
| 0x4
| 0x4
| Zero/Reserved
| Reserved
|-
|-
| 0x10
| 0x10
| X
| X
| File Entry Table
| [[#FileEntryTable]]
|-
|-
| 0x10 + X
| 0x10 + X
| Y
| Y
| String Table
| StringTable
|-
|-
| 0x10 + X + Y
| 0x10 + X + Y
| Z
| Z
| Raw File Data
| RawFileData
|}
 
==== FileEntryTable ====
{| class="wikitable" border="1"
|-
! Offset
! Size
! Description
|-
| 0x0
| 0x8
| Offset of file in Data
|-
| 0x8
| 0x8
| Size of file in Data
|-
| 0x10
| 0x4
| Offset of filename in String Table
|-
| 0x14
| 0x4
| Size of Hashed region of file (for HFS0s, this is the size of the pre-filedata portion, for NCAs this is usually 0x200)
|-
| 0x18
| 8
| Zero/Reserved
|-
| 0x20
| 0x20
| SHA-256 hash of the first (size of hashed region) bytes of filedata
|}
|}
The string table is 00-padded to align the start of raw filedata with a sector/media unit boundary (usually?).