nand - NAND images

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

This module is best combined with pyfatfs for interacting with the FAT filesystems inside TWL NAND and CTR NAND. pyfatfs is not a dependency on pyctr so your application must include it manually.

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', *, 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.

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_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_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.

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

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

Writes to the first 0x1BE (before the TWL MBR) are silently discarded to avoid writing a corrupted NCSD header.

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.