nand - NAND images

The nand module enables reading and writing of Nintendo 3DS NAND images.

A basic overview of reading and writing to a NAND image is available here: Example: Read and write NAND partitions.

Getting started

Here’s a quick example to get inside CTRNAND and read files from within it using PyFatBytesIOFS:

from pyctr.type.nand import NAND
from pyfatfs.PyFatFS import PyFatBytesIOFS

with NAND('nand.bin') as nand:
    with PyFatBytesIOFS(fp=nand.open_ctr_partition()) as ctrfat:
        with ctrfat.open('/private/movable.sed', 'rb') as msed:
            msed.read()

A second example trying to load SecureInfo. This one is tricky because some consoles use SecureInfo_A and some use SecureInfo_B, so we have to try both.

from pyctr.type.nand import NAND
from pyfatfs.PyFatFS import PyFatBytesIOFS
from fs.errors import ResourceNotFound

with NAND('nand.bin') as nand:
    with PyFatBytesIOFS(fp=nand.open_ctr_partition()) as ctrfat:
        for l in 'AB':
            path = '/rw/sys/SecureInfo_' + l
            if ctrfat.exists(path):
                with ctrfat.open(path, 'rb') as f:
                    f.read()
                    break

Required files

In most cases users will have a NAND backup with an essentials backup embedded. However there are plenty of cases where this may not occur, so you may need to provide support files in other ways.

The only hard requirement is an OTP. This is found in the essentials backup, but otherwise can be provided as a file, file-like object, and a bytestring.

NAND CID is also useful to have but is not required for most consoles. This also is loaded from the essentials backup if found, otherwise it can be provided like OTP. If it’s not found anywhere, PyCTR will attempt to generate the Counter for both CTR and TWL. The Counter for TWL will not be generated if the TWL MBR is corrupt.

In either case the load priority is first file, then bytestring, then essentials backup.

An external essential.exefs file must manually be loaded with ExeFSReader and then the individual otp and nand_cid read and provided to the NAND initializer.

Dealing with corruption

There are cases where the NAND is corrupt but you still want to read it.

One of the most common kinds of corruption is an invalid TWL MBR. This happens if the NCSD header is replaced with one of another console. This applies mostly to very old pre-sighax NAND backups. If the TWL MBR cannot be decrypted and parsed, but a NAND CID was loaded, PyCTR will use the default partition information. Otherwise, TWL information will be inaccessible.

NAND objects

class pyctr.type.nand.NAND(file, mode='rb', *, fs=None, closefd=None, crypto=None, dev=False, otp=None, otp_file=None, cid=None, cid_file=None, auto_raise_exceptions=True)[source]

Reads a Nintendo 3DS NAND image.

If OTP and CID are not provided, it will attempt to load both from essential.exefs.

If OTP is provided but not CID, it will attempt to generate the Counter for both CTR and TWL.

Parameters:
  • file (FilePathOrObject) – A file path or a file-like object with the CIA data.

  • mode (str) – Mode to open the file with, passed to open. Only used if a file path was given.

  • closefd (bool) – Close the underlying file object when closed. Defaults to True for file paths, and False for file-like objects.

  • crypto (CryptoEngine) – A custom CryptoEngine object to be used. Defaults to None, which causes a new one to be created.

  • dev (bool) – Use devunit keys.

  • otp (bytes) – OTP, used to generate the required encryption keys. Overrides otp_file if both are provided.

  • otp_file (FilePath) – Path to a file containing the OTP.

  • cid (bytes) – NAND CID, used to generate the Counter. Overrides cid_file if both are provided.

  • cid_file (FilePath) – Path to a file containing the NAND CID.

  • auto_raise_exceptions (bool) – Automatically raise an exception if the CTR or TWL partitions are inaccessible. This calls raise_if_ctr_failed() and raise_if_twl_failed() at the end of initialization. Set this to False if you still need access to a NAND even if these sections are unavailable.

  • fs (Optional[FS]) –

open_ctr_partition(partition_index=0)[source]

Opens a raw partition in CTRNAND for reading and writing.

In practice there is only ever one, so this opens it by default.

Parameters:

partition_index (int) – Partition index number.

Returns:

A file-like object.

Return type:

SubsectionIO

open_ctr_fat(partition_index=0)[source]

Opens a FAT filesystem inside a CTR partition for reading and writing.

In practice there is only ever one, so this opens it by default.

Parameters:

partition_index (int) – Partition index number.

Returns:

A FAT filesystem.

Return type:

PyFatBytesIOFS

open_twl_partition(partition_index)[source]

Opens a raw partition in TWLNAND for reading and writing.

0 is TWL NAND and 1 is TWL Photo.

Parameters:

partition_index (int) – Partition index number.

Returns:

A file-like object.

Return type:

SubsectionIO

open_twl_fat(partition_index)[source]

Opens a FAT filesystem inside a TWL partition for reading and writing.

0 is TWL NAND and 1 is TWL Photo.

Parameters:

partition_index – Partition index number.

Returns:

A FAT filesystem.

Return type:

PyFatBytesIOFS

open_raw_section(section)[source]

Opens a raw NCSD section for reading and writing with on-the-fly decryption.

You should use NANDSection to get a specific type of partition. Unless you need to interact with the physical location of partitions, using partition indexes could break for users who have moved them around.

Note

If you are looking to read from TWL NAND or CTR NAND, you may be looking for open_twl_partition() or open_ctr_partition() instead to open the raw MBR partition. This will return NCSD partitions, which for TWL NAND and CTR NAND, include the MBR.

Parameters:

section (Union[NANDSection, int]) – The section to open. Numbers 0 to 7 are specific NCSD partitions. Negative numbers are special sections defined by PyCTR.

Returns:

A file-like object.

Return type:

SubsectionIO

open_bonus_partition()[source]

Opens the GodMode9 bonus partition.

Returns:

A file-like object.

Return type:

SubsectionIO

open_bonus_fat()[source]

Opens the GodMode9 bonus FAT partition.

Returns:

A FAT filesystem.

Return type:

PyFatBytesIOFS

raise_if_ctr_failed()[source]

Raise an error if CTR partitions are inaccessible.

Raises:

InvalidNANDError

raise_if_twl_failed()[source]

Raise an error if TWL partitions are inaccessible.

Raises:

InvalidNANDError

essential

The embedded GodMode9 essentials backup.

This usually contains these files:

  • frndseed

  • hwcal0

  • hwcal1

  • movable

  • nand_cid

  • nand_hdr

  • otp

  • secinfo

Type:

ExeFSReader

ctr_partitions

The list of partitions in the CTR MBR. Always only one in practice, referred to as CTR NAND.

Type:

List[Tuple[int, int]]

twl_partitions

The list of partitions in the TWL MBR. First one is TWL NAND and second is TWL Photo.

Type:

List[Tuple[int, int]]

close()[source]

Close the reader. If closefd is True, the underlying file is also closed.

NAND sections

class pyctr.type.nand.NANDSection[source]

This defines the location of partitions in a NAND.

All the enums here are negative numbers to refer to different types of partitions rather than physical locations when used with NAND.open_raw_section(), because while 99% of users will never alter the partitions, it is still possible to do so and this module will handle those use cases.

Header = -3

NCSD header of the NAND.

TWLMBR = -4

Decrypted TWL MBR.

TWLNAND = -11

TWL NAND region.

Note

Don’t write to the first 0x1BE, this is where the NCSD header is on the raw NAND. Future versions of pyctr may silently discard writes to this region.

If writing to the TWL MBR region (0x1BE-0x200), the NCSD header signature may be invalidated. Use the sighax signature to keep a “valid” header. Also keep a backup of the original NCSD header (this may already be in the essentials backup).

AGBSAVE = -12

AGB_FIRM save region.

FIRM0 = -13

NATIVE_FIRM partition.

FIRM1 = -14

NATIVE_FIRM backup partition.

CTRNAND = -15

CTR NAND region.

Special sections

These are not actual sections of the NAND/NCSD but are included for convenience.

Sector0x96 = -2

New 3DS keyblob.

Note

Reading this decrypted with open_raw_section() is not yet supported. Decrypt it manually if you need access to it.

GM9BonusVolume = -6

Bonus FAT16 volume set up by GodMode9. Only available on non-minsize backups if a console has a larger NAND chip than required. Setting this automatically requires loading from a file, because the only way to know if it’s there is to check at the end of the image.

MinSize = -5

The full NAND image up to the minimum image size.

Exceptions

exception pyctr.type.nand.NANDError[source]

Generic error for NAND operations.

exception pyctr.type.nand.InvalidNANDError[source]

Invalid NAND header exception.

exception pyctr.type.nand.MissingOTPError[source]

OTP wasn’t loaded.

Custom NCSD interaction

These are for those who want to manually interact with the NCSD information.

class pyctr.type.nand.NANDNCSDHeader(signature, image_size, actual_image_size, partition_table, unknown, twl_mbr_encrypted)[source]

This contains all the information in the NCSD header. This is also used for virtual sections in PyCTR.

Parameters:
signature: bytes

RSA signature over the NAND header.

image_size: int

Claimed image size. This does not actually line up with the raw image size in sectors, but is useful to determine Old 3DS vs New 3DS.

actual_image_size: int

Actual image size in bytes. This is the minimum size of a NAND image, but the actual size of the backup may be larger.

partition_table: Dict[int | NANDSection, NCSDPartitionInfo]

Partition information. NANDSection keys (negative ints) are for partition and section types, while positive int keys are for physical locations. This means that, for example, NANDSection.TWLMBR and 0 contain the same partition info.

twl_mbr_encrypted: bytes

TWL MBR in its encrypted form.

unknown: bytes

Unknown padding data. This is preserved so the header can be re-built exactly with a valid signature.

classmethod load(fp)[source]

Load a NAND header from a file-like object. This will also seek to actual_image_size to determine if there is a GodMode9 bonus drive.

Parameters:

fp (BinaryIO) – The file-like object to read from. Must be seekable.

classmethod from_bytes(data)[source]

Load a NAND header from bytes.

Parameters:

data (bytes) – The 512-byte NCSD header.

Raises:

InvalidNANDError

class pyctr.type.nand.NCSDPartitionInfo[source]

Information for a single partition.

fs_type: PartitionFSType | int

Type of filesystem.

encryption_type: PartitionEncryptionType | int

Type of encryption used for the partition.

offset: int

Offset of the partition in bytes.

size: int

Size of the partition in bytes.

Enums

class pyctr.type.nand.PartitionFSType(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)[source]

Type of filesystem in the partition.

Special = -1

Specially defined region from PyCTR.

Nothing = 0

No partition here.

Normal = 1

Used for TWL and CTR parts.

FIRM = 3

Used for FIRM partitions.

AGBFIRMSave = 4

Used for the AGB_FIRM save partition.

class pyctr.type.nand.PartitionEncryptionType(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)[source]

Type of encryption on the partition. In practice this is only really used to determine what keyslot to use for CTRNAND which changes between Old 3DS and New 3DS. It’s not really known what happens if any of the other partitions have the crypt type changed.

NoEncryption = -2

No encryption. (Not an actual FS type, only used for special regions in PyCTR.)

Sector0x96 = -1

New 3DS keyblob. (Not an actual FS type, only used for special handling in PyCTR.)

TWL = 1

Used for the TWL partitions.

CTR = 2

Used for FIRM, CTR on Old 3DS, and AGB_FIRM save partitions.

New3DSCTR = 3

Used for the CTR partitions on New 3DS.