How I manually resized TWLNAND on my 3DS

In early 2024, I tried to resize the partitions on my New 3DS device, specifically to increase the space for TWLNAND, the space that hold DSiWare. I wanted to store more DSiWare than there was available space.

This isn’t a tutorial! This is only my experience trying to do so. You should not attempt this without ntrboot because this can actually brick your console if done improperly.

Also, I only finished this post many months after I had done it, so some of the details are a bit fuzzy, but it wasn’t a difficult process overall. At least the resizing part wasn’t…

What to resize and shift around

What defines the size of TWLNAND, and how can I increase the size of it? The Nintendo 3DS NAND is split up into 5 different partitions, with a custom NCSD header. In order they are:

  • TWLNAND
  • AGBSAVE (Game Boy Advance VC save data)
  • FIRM0
  • FIRM1
  • CTRNAND

For compatibility reasons with the Nintendo DSi, TWLNAND is the first partition. So when I resize it, I have to shift forward everything else by the same amount.

TWLNAND is itself partitioned into “twln” (holding DSiWare games and data) and “twlp” (camera photos). It starts at the first block of the NAND, meaning it takes up space within the NCSD header. The last 0x42 bytes of the header are dedicated to the TWL MBR. And because “twln” is the first partition within TWLNAND, this also means “twlp” will have to be pushed forward.

Let’s make a custom NCSD header

If I want to increase the size of TWLNAND, I must create a custom NCSD header with my own partitioning. My own pyctr library makes this easy.

I took the partition data from the stock header and adjusted offsets and sizes by a fixed amount. I increased TWLNAND by about 350 MiB. That means I had to shift forward AGBSAVE, FIRM0, FIRM1, and CTRNAND by this amount, and shrink CTRNAND by it. I included here the script I made to create a new NCSD header that resizes TWLNAND and shifts forward every other partition by the same amount.

from pyctr.type.nand import NANDNCSDHeader, NCSDPartitionInfo, SIGHAX_SIGS

diff = 367001600  # 350 MiB

custom_partition_table = {
    0: NCSDPartitionInfo(fs_type=1, encryption_type=1, offset=0, size=185597952 + diff, base_file='twl'),
    1: NCSDPartitionInfo(fs_type=4, encryption_type=2, offset=185597952 + diff, size=196608, base_file='agb'),
    2: NCSDPartitionInfo(fs_type=3, encryption_type=2, offset=185794560 + diff, size=4194304, base_file='firm'),
    3: NCSDPartitionInfo(fs_type=3, encryption_type=2, offset=189988864 + diff, size=4194304, base_file='firm'),
    4: NCSDPartitionInfo(fs_type=1, encryption_type=3, offset=194183168 + diff, size=1106051072 - diff, base_file='ctr_new')
}

a = NANDNCSDHeader(
    # sighax has to be used here as the official signature will be invalidated
    signature=SIGHAX_SIGS['retail'],
    image_size=1342177280,
    actual_image_size=1300234240,
    partition_table=custom_partition_table,
    unknown=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
    # this will be overwritten when twlnand is formatted
    twl_mbr_encrypted=b'\0' * 0x42
)

with open('nand_hdr-custom.bin', 'wb') as f:
    f.write(bytes(a))

Build the new NAND

Once this custom header is written, a NAND image needs to be built. My ninfs tool can mount an image, even one with blank partitions, so I can then format TWLNAND and CTRNAND. For this, one can use standard tools, and on macOS, I used diskutil.

I first attached the raw disk images. Then I used diskutil command to make twln use as much space within TWLNAND as it could, while twlp remained the same as before:1

diskutil partitionDisk -noEFI disk# 3 MBR \
        free free 76288B \
        'MS-DOS' twln R \
        'MS-DOS' twlp 34301440B

Formatting CTRNAND is simple since it is a single partition.

diskutil partitionDisk -noEFI disk# 1 MBR 'MS-DOS' ctrn R

Now that TWLNAND and CTRNAND are formatted, they can be mounted and all the data from a previous NAND image can be copied like normal. The same applies to AGBSAVE, FIRM0, and FIRM1.

Now that hopefully the NAND image has all the same files as before, I can restore it to my console. There’s a slight catch, in that because partitions are not in the same places, GodMode9’s safe restore cannot be used. An explicitly unsafe restore must be performed by manually injecting the new NAND image into the system.2

An unexpected problem…

The NAND restore was a success! My console booted to the HOME Menu like normal, and GodMode9 could see the new space. “SYSNAND TWLN” is now 493.9 MB instead of the stock 143.6 MB.

Screenshot of a list of drives in GodMode9. The relevant portion is that "SYSNAND TWLN" is 493.9 MB.
GodMode9 drive view. SYSNAND TWLN is 493.9 MB.

There was however one problem, the system software did not recognize the additional space. It still read about 1,000 open blocks.

Screenshot of the Nintendo eShop download screen. The software to be downloaded is not named. There are 1,042 open blocks, and 224 blocks are required to download.
Nintendo eShop download screen.

This was a real problem… How would I use this free space if the system doesn’t think there’s more of it? Oh I know, I’ll dump all my eShop titles to CIA and use FBI to install them!

Installing all my DSiWare manually

I used cleaninty to download all my purchases. Once I had all of them, I stuck them in a folder and let FBI do the rest. Only, it turns out it couldn’t do it either; after a few installed, it started failing. I forgot the exact error code, but the AM service (which actually does the installing) was rejecting it because it also thought TWLNAND was full.

There is actually a third option here, and that is GodMode9. It can also install CIAs, and since it directly interacts with twln, it doesn’t have to deal with AM being upset. But I ran into two issues here.

The first was that CIAs created with cleaninty can only be installed within the 3DS “operating system”. This is because the encrypted title key in the ticket is itself encrypted with a per-console key.3 This leaves GodMode9 unable to decrypt the titles.

Okay, so I decided I’d take the easy but mildly tedious approach of:

  1. install as much as I can with FBI
  2. dump the installed DSiWare to CIA again with GodMode9
  3. delete all the installed titles
  4. repeat from step 1 until all software has been processed

There, now I’ve got all my DSiWare ready. So I’ll just install of it, right? No, because the second issue GodMode9 for some reason starts to fail once I’ve installed around 5 titles or so. Thankfully, this one wasn’t that tough to deal with, as the error went away on a reboot. So the loop here was:

  1. install 5 titles
  2. reboot GodMode9
  3. repeat until all software was installed

Finally after all that, I had finally gotten all my DSiWare installed, which was exactly 40 titles and added up to 231.6 MB in CIA files.

Considering that AM didn’t like it when I tried to force through more data than it wanted, I wondered what it would think about me subverting its space enforcement…

Screenshot of Nintendo DSiWare Management within System Settings. System Memory Open Blocks reads -1,073.
Nintendo DSiWare Management section of System Settings.

I think this counts as a pretty cursed image if you ask me.

This has been my setup since March 1, 2024, and my console has worked pretty well ever since. I just realized while writing this though that having exactly 40 DSiWare from the eShop means that I can’t use it with other homebrew DSiWare like TWiLight Menu++ or GodMode9i. So I might have to figure out some stuff to delete after all…

Notes

  1. Something to keep in mind for specifically New 3DS is the secret key sector (sector0x96). twln needs to be put after it so it doesn’t overwrite it. In this command, the free space is 0x95 (76288 // 512) sectors – this doesn’t include the sector containing the MBR, so this means there would be 0x96 sectors until twln starts at sector 0x97, preserving the secret key sector. ↩︎
  2. Those who are very paranoid could try out their NAND alterations with EmuNAND. Though this would not be an excuse to restore this to SysNAND without ntrboot as it’s still a big risky change to make to one’s console. ↩︎
  3. This is why any “legit CIAs” made by GodMode9 of eShop-downloaded software cannot be installed with FBI or any other 3DS-mode software installer. ↩︎

3 thoughts on “How I manually resized TWLNAND on my 3DS

  1. Could you create like a universal-updater application that would allow users to do this to their systems without having to do it custom themselves? I would love something like this!!!

    Like

    • In theory it is possible, but a lot of work would have to be done to make it work safely. Since this requires potentially destructive NAND actions, it’s highly risky.

      Like

      • Oh ok that sounds about right. It does seem risky but if you could make something like that or make some sort of guide on how to that would be really cool!

        Like

Leave a comment