From 88bc7955d8c9f145452d0275144b20ad89dd334d Mon Sep 17 00:00:00 2001 From: d0k3 Date: Fri, 2 Jun 2017 17:02:11 +0200 Subject: [PATCH] Fix NAND offset hardcoding ... SafeB9SInstaller now works with custom NCSDs --- source/common/common.h | 8 +- source/common/unittype.h | 3 +- source/fatfs/diskio.c | 162 ++++++++------------ source/fatfs/fatmbr.c | 32 ++++ source/fatfs/fatmbr.h | 90 +++++++++++ source/installer.c | 107 +++++++------ source/nand/nand.c | 312 ++++++++++++++++++++++++-------------- source/nand/nand.h | 93 +++++++----- source/safety/safewrite.c | 12 ++ source/safety/safewrite.h | 1 + 10 files changed, 528 insertions(+), 292 deletions(-) create mode 100644 source/fatfs/fatmbr.c create mode 100644 source/fatfs/fatmbr.h diff --git a/source/common/common.h b/source/common/common.h index 5397959..0cdaacb 100644 --- a/source/common/common.h +++ b/source/common/common.h @@ -55,11 +55,11 @@ // buffer area defines (big buffer for firm) #define WORK_BUFFER ((u8*) 0x21000000) -#define WORK_BUFFER_SIZE (0x100000) -#define NAND_BUFFER ((u8*) 0x22000000) -#define NAND_BUFFER_SIZE (0x100000) -#define FIRM_BUFFER ((u8*) 0x23000000) +#define WORK_BUFFER_SIZE (0x400000) +#define FIRM_BUFFER ((u8*) 0x21400000) #define FIRM_BUFFER_SIZE (0x400000) +#define NAND_BUFFER ((u8*) 0x21800000) +#define NAND_BUFFER_SIZE (0x100000) inline u32 strchrcount(const char* str, char symbol) { u32 count = 0; diff --git a/source/common/unittype.h b/source/common/unittype.h index cff5f7e..37f3f09 100644 --- a/source/common/unittype.h +++ b/source/common/unittype.h @@ -10,11 +10,12 @@ #define IS_DEVKIT ((*(vu8*) (0x01FFB800+0x19)) != 0x0) // see: https://3dbrew.org/wiki/CONFIG11_Registers +// (also returns true for sighaxed systems, maybe change the name later?) #define IS_A9LH ((*(vu32*) 0x101401C0) == 0) // https://www.3dbrew.org/wiki/CONFIG9_Registers // (actually checks for an unlocked OTP) #define IS_UNLOCKED (!((*(vu8*)0x10000000) & 0x2)) -// A9LH + unlocked = SigHax +// A9LH + unlocked == SigHax #define IS_SIGHAX (IS_A9LH && IS_UNLOCKED) diff --git a/source/fatfs/diskio.c b/source/fatfs/diskio.c index a9f36b9..c264f43 100644 --- a/source/fatfs/diskio.c +++ b/source/fatfs/diskio.c @@ -11,71 +11,34 @@ #include "nand.h" #include "sdmmc.h" + +#define PART_INFO(pdrv) (DriveInfo + pdrv) #define PART_TYPE(pdrv) (DriveInfo[pdrv].type) -#define PART_SUBTYPE(pdrv) (DriveInfo[pdrv].subtype) #define TYPE_NONE 0 -#define TYPE_SYSNAND NAND_SYSNAND +#define TYPE_SYSNAND (1UL<<0) #define TYPE_SDCARD (1UL<<4) -#define SUBTYPE_CTRN 0 -#define SUBTYPE_CTRN_N 1 -#define SUBTYPE_CTRN_NO 2 -#define SUBTYPE_TWLN 3 -#define SUBTYPE_TWLP 4 -#define SUBTYPE_NONE 5 +#define SUBTYPE_CTRN 1 +#define SUBTYPE_TWLN 2 +#define SUBTYPE_TWLP 3 +#define SUBTYPE_NONE 0 typedef struct { BYTE type; BYTE subtype; -} FATpartition; - -typedef struct { DWORD offset; DWORD size; BYTE keyslot; -} SubtypeDesc; +} FATpartition; -FATpartition DriveInfo[4] = { - { TYPE_SDCARD, SUBTYPE_NONE }, // 0 - SDCARD - { TYPE_SYSNAND, SUBTYPE_CTRN }, // 1 - SYSNAND CTRNAND - { TYPE_SYSNAND, SUBTYPE_TWLN }, // 2 - SYSNAND TWLN - { TYPE_SYSNAND, SUBTYPE_TWLP }, // 3 - SYSNAND TWLP +FATpartition DriveInfo[13] = { + { TYPE_SDCARD, SUBTYPE_NONE, 0, 0, 0xFF }, // 0 - SDCARD + { TYPE_SYSNAND, SUBTYPE_CTRN, 0, 0, 0xFF }, // 1 - SYSNAND CTRNAND + { TYPE_SYSNAND, SUBTYPE_TWLN, 0, 0, 0xFF }, // 2 - SYSNAND TWLN + { TYPE_SYSNAND, SUBTYPE_TWLP, 0, 0, 0xFF }, // 3 - SYSNAND TWLP }; -SubtypeDesc SubTypes[5] = { - { 0x05C980, 0x17AE80, 0x04 }, // O3DS CTRNAND - { 0x05C980, 0x20F680, 0x05 }, // N3DS CTRNAND - { 0x05C980, 0x20F680, 0x04 }, // N3DS CTRNAND (downgraded) - { 0x000097, 0x047DA9, 0x03 }, // TWLN - { 0x04808D, 0x0105B3, 0x03 } // TWLP -}; - -static BYTE nand_type_sys = 0; - - - -/*-----------------------------------------------------------------------*/ -/* Get Drive Subtype helper */ -/*-----------------------------------------------------------------------*/ - -static inline SubtypeDesc* get_subtype_desc( - __attribute__((unused)) - BYTE pdrv /* Physical drive number to identify the drive */ -) -{ - BYTE subtype = PART_SUBTYPE(pdrv); - - if (subtype == SUBTYPE_NONE) { - return NULL; - } else if (subtype == SUBTYPE_CTRN) { - if (nand_type_sys != NAND_TYPE_O3DS) - subtype = (nand_type_sys == NAND_TYPE_N3DS) ? SUBTYPE_CTRN_N : SUBTYPE_CTRN_NO; - } - - return &(SubTypes[subtype]); -} - /*-----------------------------------------------------------------------*/ @@ -83,8 +46,8 @@ static inline SubtypeDesc* get_subtype_desc( /*-----------------------------------------------------------------------*/ DSTATUS disk_status ( - __attribute__((unused)) - BYTE pdrv /* Physical drive number to identify the drive */ + __attribute__((unused)) + BYTE pdrv /* Physical drive number to identify the drive */ ) { return RES_OK; @@ -97,17 +60,38 @@ DSTATUS disk_status ( /*-----------------------------------------------------------------------*/ DSTATUS disk_initialize ( - __attribute__((unused)) - BYTE pdrv /* Physical drive number to identify the drive */ + __attribute__((unused)) + BYTE pdrv /* Physical drive number to identify the drive */ ) { - if (pdrv == 0) { // a mounted SD card is the preriquisite for everything else + FATpartition* fat_info = PART_INFO(pdrv); + BYTE type = PART_TYPE(pdrv); + + fat_info->offset = fat_info->size = 0; + fat_info->keyslot = 0xFF; + + if (type == TYPE_SDCARD) { if (sdmmc_sdcard_init() != 0) return STA_NOINIT|STA_NODISK; - } else if (pdrv < 4) { - nand_type_sys = CheckNandType(); - if (!nand_type_sys) return STA_NOINIT|STA_NODISK; + fat_info->size = getMMCDevice(1)->total_size; + } else if (type == TYPE_SYSNAND) { + NandPartitionInfo nprt_info; + if ((fat_info->subtype == SUBTYPE_CTRN) && + (GetNandPartitionInfo(&nprt_info, NP_TYPE_STD, NP_SUBTYPE_CTR, 0) != 0) && + (GetNandPartitionInfo(&nprt_info, NP_TYPE_STD, NP_SUBTYPE_CTR_N, 0) != 0)) { + return STA_NOINIT|STA_NODISK; + } else if ((fat_info->subtype == SUBTYPE_TWLN) && + (GetNandPartitionInfo(&nprt_info, NP_TYPE_FAT, NP_SUBTYPE_TWL, 0) != 0)) { + return STA_NOINIT|STA_NODISK; + } else if ((fat_info->subtype == SUBTYPE_TWLP) && + (GetNandPartitionInfo(&nprt_info, NP_TYPE_FAT, NP_SUBTYPE_TWL, 1) != 0)) { + return STA_NOINIT|STA_NODISK; + } + fat_info->offset = nprt_info.sector; + fat_info->size = nprt_info.count; + fat_info->keyslot = nprt_info.keyslot; } - return RES_OK; + + return RES_OK; } @@ -117,11 +101,11 @@ DSTATUS disk_initialize ( /*-----------------------------------------------------------------------*/ DRESULT disk_read ( - __attribute__((unused)) - BYTE pdrv, /* Physical drive number to identify the drive */ - BYTE *buff, /* Data buffer to store read data */ - DWORD sector, /* Sector address in LBA */ - UINT count /* Number of sectors to read */ + __attribute__((unused)) + BYTE pdrv, /* Physical drive number to identify the drive */ + BYTE *buff, /* Data buffer to store read data */ + DWORD sector, /* Sector address in LBA */ + UINT count /* Number of sectors to read */ ) { BYTE type = PART_TYPE(pdrv); @@ -132,15 +116,12 @@ DRESULT disk_read ( if (sdmmc_sdcard_readsectors(sector, count, buff)) return RES_PARERR; } else { - SubtypeDesc* subtype = get_subtype_desc(pdrv); - BYTE keyslot = subtype->keyslot; - DWORD isector = subtype->offset + sector; - - if (ReadNandSectors(buff, isector, count, keyslot)) + FATpartition* fat_info = PART_INFO(pdrv); + if (ReadNandSectors(buff, fat_info->offset + sector, count, fat_info->keyslot)) return RES_PARERR; } - return RES_OK; + return RES_OK; } @@ -151,11 +132,11 @@ DRESULT disk_read ( #if _USE_WRITE DRESULT disk_write ( - __attribute__((unused)) - BYTE pdrv, /* Physical drive number to identify the drive */ - const BYTE *buff, /* Data to be written */ - DWORD sector, /* Sector address in LBA */ - UINT count /* Number of sectors to write */ + __attribute__((unused)) + BYTE pdrv, /* Physical drive number to identify the drive */ + const BYTE *buff, /* Data to be written */ + DWORD sector, /* Sector address in LBA */ + UINT count /* Number of sectors to write */ ) { BYTE type = PART_TYPE(pdrv); @@ -166,15 +147,12 @@ DRESULT disk_write ( if (sdmmc_sdcard_writesectors(sector, count, (BYTE *)buff)) return RES_PARERR; } else { - SubtypeDesc* subtype = get_subtype_desc(pdrv); - BYTE keyslot = subtype->keyslot; - DWORD isector = subtype->offset + sector; - - if (WriteNandSectors(buff, isector, count, keyslot)) + FATpartition* fat_info = PART_INFO(pdrv); + if (WriteNandSectors(buff, fat_info->offset + sector, count, fat_info->keyslot)) return RES_PARERR; // unstubbed! } - return RES_OK; + return RES_OK; } #endif @@ -186,26 +164,20 @@ DRESULT disk_write ( #if _USE_IOCTL DRESULT disk_ioctl ( - __attribute__((unused)) - BYTE pdrv, /* Physical drive number (0..) */ - __attribute__((unused)) - BYTE cmd, /* Control code */ - __attribute__((unused)) - void *buff /* Buffer to send/receive control data */ + __attribute__((unused)) + BYTE pdrv, /* Physical drive number (0..) */ + __attribute__((unused)) + BYTE cmd, /* Control code */ + __attribute__((unused)) + void *buff /* Buffer to send/receive control data */ ) { - BYTE type = PART_TYPE(pdrv); - switch (cmd) { case GET_SECTOR_SIZE: *((DWORD*) buff) = 0x200; return RES_OK; case GET_SECTOR_COUNT: - if (type == TYPE_SDCARD) { // SD card - *((DWORD*) buff) = getMMCDevice(1)->total_size; - } else if (type != TYPE_NONE) { // NAND - *((DWORD*) buff) = get_subtype_desc(pdrv)->size; - } + *((DWORD*) buff) = PART_INFO(pdrv)->size; return RES_OK; case GET_BLOCK_SIZE: *((DWORD*) buff) = 0x2000; @@ -215,6 +187,6 @@ DRESULT disk_ioctl ( return RES_OK; } - return RES_PARERR; + return RES_PARERR; } #endif diff --git a/source/fatfs/fatmbr.c b/source/fatfs/fatmbr.c new file mode 100644 index 0000000..a3bfacb --- /dev/null +++ b/source/fatfs/fatmbr.c @@ -0,0 +1,32 @@ +#include "fatmbr.h" + +u32 ValidateMbrHeader(MbrHeader* mbr) { + if (mbr->magic != FATMBR_MAGIC) return 1; // check magic + u32 sector = 1; // check partitions + for (u32 i = 0; i < 4; i++) { + MbrPartitionInfo* partition = mbr->partitions + i; + if (!partition->count && i) continue; + else if (!partition->count) return 1; // first partition can't be empty + if ((partition->type != 0x1) && (partition->type != 0x4) && (partition->type != 0x6) && + (partition->type != 0xB) && (partition->type != 0xC) && (partition->type != 0xE)) + return 1; // bad / unknown filesystem type + if (partition->sector < sector) return 1; // overlapping partitions + sector = partition->sector + partition->count; + } + return 0; +} + +u32 ValidateFatHeader(void* fat) { + if (getle16((u8*) fat + 0x1FE) != FATMBR_MAGIC) return 1; // check magic + Fat32Header* fat32 = (Fat32Header*) fat; + if (strncmp(fat32->fs_type, "FAT32 ", 8) == 0) + return 0; // is FAT32 header + Fat16Header* fat16 = (Fat16Header*) fat; + if ((strncmp(fat16->fs_type, "FAT16 ", 8) == 0) || + (strncmp(fat16->fs_type, "FAT12 ", 8) == 0) || + (strncmp(fat16->fs_type, "FAT ", 8) == 0)) + return 0; // is FAT16 / FAT12 header + if ((getle64(fat16->fs_type) == 0) && (fat16->sct_size == 0x200)) + return 0; // special case for public.sav + return 1; // failed, not a FAT header +} diff --git a/source/fatfs/fatmbr.h b/source/fatfs/fatmbr.h new file mode 100644 index 0000000..6a39e59 --- /dev/null +++ b/source/fatfs/fatmbr.h @@ -0,0 +1,90 @@ +#pragma once + +#include "common.h" + +#define FATMBR_MAGIC 0xAA55 // little endian! + +typedef struct { + u8 status; // 0x80 + u8 chs_start[3]; // 0x01 0x01 0x00 + u8 type; // 0x0C + u8 chs_end[3]; // 0xFE 0xFF 0xFF + u32 sector; // 0x2000 (4MB offset, 512 byte sectors) + u32 count; +} __attribute__((packed)) MbrPartitionInfo; + +typedef struct { + char text[446]; + MbrPartitionInfo partitions[4]; + u16 magic; // 0xAA55 +} __attribute__((packed)) MbrHeader; + +typedef struct { // unused + u32 signature0; // 0x41615252 + u8 reserved0[480]; + u32 signature1; // 0x61417272 + u32 clr_free; // 0xFFFFFFFF + u32 clr_next; // 0xFFFFFFFF + u8 reserved1[14]; + u16 magic; // 0xAA55 +} __attribute__((packed)) FileSystemInfo; + +typedef struct { + u8 jmp[3]; // 0x90 0x00 0xEB + char oemname[8]; // "anything" + u16 sct_size; // 0x0200 + u8 clr_size; // 0x40 -> 32kB clusters with 512byte sectors + u16 sct_reserved; // 0x20 + u8 fat_n; // 0x02 + u16 reserved0; // root entry count in FAT16 + u16 reserved1; // partition size when <= 32MB + u8 mediatype; // 0xF8 + u16 reserved2; // FAT size in sectors in FAT16 + u16 sct_track; // 0x3F + u16 sct_heads; // 0xFF + u32 sct_hidden; // same as partition offset in MBR + u32 sct_total; // same as partition size in MBR + u32 fat_size; // roundup((((sct_total - sct_reserved) / clr_size) * 4) / sct_size) + u16 flags; // 0x00 + u16 version; // 0x00 + u32 clr_root; // 0x02 + u16 sct_fsinfo; // 0x01 + u16 sct_backup; // 0x06 + u8 reserved3[12]; + u8 ndrive; // 0x80 + u8 head_cur; // 0x00 + u8 boot_sig; // 0x29 + u32 vol_id; // volume id / 0x00 + char vol_label[11]; // "anything " + char fs_type[8]; // "FAT32 " + u8 reserved4[420]; + u16 magic; // 0xAA55 +} __attribute__((packed)) Fat32Header; + +typedef struct { // this struct is not tested enough! + u8 jmp[3]; // 0x90 0x00 0xEB + char oemname[8]; // "anything" + u16 sct_size; // 0x0200 + u8 clr_size; // 0x20 (???) -> 16kB clusters with 512byte sectors + u16 sct_reserved; // 0x01 + u8 fat_n; // 0x02 + u16 root_n; // 0x0200 + u16 reserved0; // partition size when <= 32MB + u8 mediatype; // 0xF8 + u16 fat_size; // roundup((((sct_total - sct_reserved) / clr_size) * 2) / sct_size) + u16 sct_track; // 0x3F + u16 sct_heads; // 0xFF + u32 sct_hidden; // same as partition offset in MBR + u32 sct_total; // same as partition size in MBR + u8 ndrive; // 0x80 + u8 head_cur; // 0x00 + u8 boot_sig; // 0x29 + u32 vol_id; // volume id / 0x00 + char vol_label[11]; // "anything " + char fs_type[8]; // "FAT16 " + u8 reserved4[448]; + u16 magic; // 0xAA55 +} __attribute__((packed)) Fat16Header; + +u32 ValidateMbrHeader(MbrHeader* mbr); +u32 ValidateFatHeader(void* fat); diff --git a/source/installer.c b/source/installer.c index a60b258..cabfedd 100644 --- a/source/installer.c +++ b/source/installer.c @@ -1,6 +1,7 @@ #include "installer.h" #include "safewrite.h" #include "validator.h" +#include "unittype.h" #include "nand.h" #include "ui.h" #include "qff.h" @@ -9,16 +10,12 @@ #define COLOR_STATUS(s) ((s == STATUS_GREEN) ? COLOR_BRIGHTGREEN : (s == STATUS_YELLOW) ? COLOR_BRIGHTYELLOW : (s == STATUS_RED) ? COLOR_RED : COLOR_DARKGREY) #define MIN_SD_FREE (16 * 1024 * 1024) // 16MB -#define FIRM_NAND_OFFSET 0x0B130000 -#define FIRM_NAND_SIZE 0x800000 -#define FIRM0_NAND_OFFSET FIRM_NAND_OFFSET -#define FIRM1_NAND_OFFSET (FIRM_NAND_OFFSET + (FIRM_NAND_SIZE/2)) #define NAME_SIGHAXFIRM (IS_DEVKIT ? INPUT_PATH "/" NAME_FIRM "_dev.firm" : INPUT_PATH "/" NAME_FIRM ".firm") #define NAME_SIGHAXFIRMSHA (IS_DEVKIT ? INPUT_PATH "/" NAME_FIRM "_dev.firm.sha" : INPUT_PATH "/" NAME_FIRM ".firm.sha") #define NAME_SECTOR0x96 (IS_DEVKIT ? INPUT_PATH "/secret_sector_dev.bin" : INPUT_PATH "/secret_sector.bin") -#define NAME_FIRMBACKUP INPUT_PATH "/firm0firm1.bak" -#define NAME_SECTORBACKUP INPUT_PATH "/sector0x96.bak" +#define NAME_FIRMBACKUP INPUT_PATH "/firm%lu_enc.bak" +#define NAME_SECTORBACKUP INPUT_PATH "/sector0x96_enc.bak" #define STATUS_GREY -1 #define STATUS_GREEN 0 @@ -73,6 +70,7 @@ u32 ShowInstallerStatus(void) { u32 SafeB9SInstaller(void) { UINT bt; + u32 ret = 0; // initialization ShowString("Initializing, please wait..."); @@ -169,6 +167,20 @@ u32 SafeB9SInstaller(void) { snprintf(msgCrypto, 64, "checking..."); statusCrypto = STATUS_YELLOW; ShowInstallerStatus(); + u32 n_firms = 0; + for (; n_firms < 8; n_firms++) { + NandPartitionInfo np_info; + if (GetNandPartitionInfo(&np_info, NP_TYPE_FIRM, NP_SUBTYPE_CTR, n_firms) != 0) break; + if ((firm_size > np_info.count * 0x200) || (np_info.count * 0x200 > WORK_BUFFER_SIZE)) { + n_firms = 0; + break; + } + } + if (!n_firms) { + snprintf(msgCrypto, 64, "FIRM partition fail"); + statusCrypto = STATUS_RED; + return 1; + } if (!CheckFirmCrypto()) { snprintf(msgCrypto, 64, "FIRM crypto fail"); statusCrypto = STATUS_RED; @@ -198,25 +210,24 @@ u32 SafeB9SInstaller(void) { snprintf(msgBackup, 64, "FIRM backup..."); statusBackup = STATUS_YELLOW; ShowInstallerStatus(); - FIL fp; - u32 ret = 0; - if (f_open(&fp, NAME_FIRMBACKUP, FA_READ|FA_WRITE|FA_CREATE_ALWAYS) != FR_OK) { - snprintf(msgBackup, 64, "FIRM backup fail"); - statusBackup = STATUS_RED; - return 1; - } ShowProgress(0, 0, "FIRM backup"); - for (u32 pos = 0; (pos < FIRM_NAND_SIZE) && (ret == 0); pos += WORK_BUFFER_SIZE) { - UINT bytes = min(WORK_BUFFER_SIZE, FIRM_NAND_SIZE - pos); - snprintf(msgBackup, 64, "FIRM backup (%luMB/%luMB)", - pos / (1024*1024), (u32) FIRM_NAND_SIZE / (1024 * 1024)); + for (u32 i = 0; i < n_firms; i++) { + NandPartitionInfo np_info; + ret = GetNandPartitionInfo(&np_info, NP_TYPE_FIRM, NP_SUBTYPE_CTR, i); + if (ret != 0) break; + u32 fsize = np_info.count * 0x200; + u32 foffset = np_info.sector * 0x200; + char bakname[64]; + snprintf(bakname, 64, NAME_FIRMBACKUP, i); + snprintf(msgBackup, 64, "FIRM backup (%lu/%lu)", i, n_firms); ShowInstallerStatus(); - if ((ReadNandBytes(WORK_BUFFER, FIRM_NAND_OFFSET + pos, bytes, 0xFF) != 0) || - (SafeWriteFile(&fp, WORK_BUFFER, pos, bytes) != 0)) + if ((ReadNandBytes(WORK_BUFFER, foffset, fsize, 0xFF) != 0) || + (SafeQWriteFile(bakname, WORK_BUFFER, fsize) != 0)) { ret = 1; - ShowProgress(pos + bytes, FIRM_NAND_SIZE, "FIRM backup"); + break; + } + ShowProgress(i + 1, n_firms, "FIRM backup"); } - f_close(&fp); if (ret != 0) { snprintf(msgBackup, 64, "FIRM backup fail"); statusBackup = STATUS_RED; @@ -250,20 +261,21 @@ u32 SafeB9SInstaller(void) { #ifndef NO_WRITE ShowProgress(0, 0, "FIRM install"); do { - ret = SafeWriteNand(FIRM_BUFFER, FIRM0_NAND_OFFSET, firm_size, 0x06); + for (u32 i = 0; i < n_firms; i++) { + NandPartitionInfo np_info; + ret = GetNandPartitionInfo(&np_info, NP_TYPE_FIRM, NP_SUBTYPE_CTR, i); + if (ret != 0) break; + ret = SafeWriteNand(FIRM_BUFFER, np_info.sector * 0x200, firm_size, np_info.keyslot); + if (ret != 0) break; + ShowProgress(i+1, n_firms, "FIRM install"); + snprintf(msgInstall, 64, "FIRM install (%li/8)", i+1); + ShowInstallerStatus(); + } if (ret != 0) break; - ShowProgress(1, 2, "FIRM install (1/2)"); - snprintf(msgInstall, 64, "FIRM install (1/2)"); - ShowInstallerStatus(); - ret = SafeWriteNand(FIRM_BUFFER, FIRM1_NAND_OFFSET, firm_size, 0x06); - if (ret != 0) break; - ShowProgress(1, 2, "FIRM install (2/2)"); - snprintf(msgInstall, 64, "FIRM install (2/2)"); - ShowInstallerStatus(); if ((IS_A9LH && !IS_SIGHAX)) { snprintf(msgInstall, 64, "0x96 revert..."); ShowInstallerStatus(); - ret = SafeWriteNand(secret_sector, 0x96 * 0x200, 0x200, IS_O3DS ? 0xFF : 0x11); + ret = SafeWriteNand(secret_sector, SECTOR_SECRET * 0x200, 0x200, IS_O3DS ? 0xFF : 0x11); if (ret == 0) snprintf(msgA9lh, 64, "uninstalled"); } } while (false); @@ -281,11 +293,11 @@ u32 SafeB9SInstaller(void) { ShowInstallerStatus(); } #elif !defined FAIL_TEST - snprintf(msgInstall, 64, "test mode, not done"); + snprintf(msgInstall, 64, "no write mode"); statusInstall = STATUS_YELLOW; return 0; #else - snprintf(msgInstall, 64, "fail test mode..."); + snprintf(msgInstall, 64, "emergency mode"); statusInstall = STATUS_YELLOW; ShowInstallerStatus(); #endif @@ -299,23 +311,24 @@ u32 SafeB9SInstaller(void) { snprintf(msgBackup, 64, "FIRM restore..."); statusBackup = STATUS_YELLOW; ShowInstallerStatus(); - if (f_open(&fp, NAME_FIRMBACKUP, FA_READ|FA_OPEN_EXISTING) != FR_OK) { - snprintf(msgBackup, 64, "FIRM restore fail"); - statusBackup = STATUS_RED; - return 1; - } ShowProgress(0, 0, "FIRM restore"); - for (u32 pos = 0; (pos < FIRM_NAND_SIZE) && (ret == 0); pos += WORK_BUFFER_SIZE) { - UINT bytes = min(WORK_BUFFER_SIZE, FIRM_NAND_SIZE - pos); - snprintf(msgBackup, 64, "FIRM restore (%luMB/%luMB)", - pos / (1024*1024), (u32) FIRM_NAND_SIZE / (1024 * 1024)); + for (u32 i = 0; i < n_firms; i++) { + NandPartitionInfo np_info; + ret = GetNandPartitionInfo(&np_info, NP_TYPE_FIRM, NP_SUBTYPE_CTR, i); + if (ret != 0) break; + u32 fsize = np_info.count * 0x200; + u32 foffset = np_info.sector * 0x200; + char bakname[64]; + snprintf(bakname, 64, NAME_FIRMBACKUP, i); + snprintf(msgBackup, 64, "FIRM restore (%lu/%lu)", i, n_firms); ShowInstallerStatus(); - if ((f_read(&fp, WORK_BUFFER, bytes, &bt) != FR_OK) || (bt != bytes) || - (WriteNandBytes(WORK_BUFFER, FIRM_NAND_OFFSET + pos, bytes, 0xFF) != 0)) + if ((f_qread(bakname, WORK_BUFFER, 0, fsize, &bt) != FR_OK) || (bt != fsize) || + (WriteNandBytes(WORK_BUFFER, foffset, fsize, 0xFF) != 0)) { ret = 1; - ShowProgress(pos + bytes, FIRM_NAND_SIZE, "FIRM restore"); + break; + } + ShowProgress(i + 1, n_firms, "FIRM restore"); } - f_close(&fp); if (ret != 0) { snprintf(msgBackup, 64, "FIRM restore fail"); statusBackup = STATUS_RED; @@ -326,7 +339,7 @@ u32 SafeB9SInstaller(void) { ShowInstallerStatus(); u8 sector_backup[0x200]; if ((f_qread(NAME_SECTORBACKUP, sector_backup, 0, 0x200, &bt) != FR_OK) || (bt != 0x200) || - (WriteNandSectors(sector_backup, 0x96, 1, 0xFF) != 0)) { + (WriteNandSectors(sector_backup, SECTOR_SECRET, 1, 0xFF) != 0)) { snprintf(msgBackup, 64, "0x96 restore fail"); statusBackup = STATUS_RED; return 1; diff --git a/source/nand/nand.c b/source/nand/nand.c index 8551823..aae8af9 100644 --- a/source/nand/nand.c +++ b/source/nand/nand.c @@ -2,14 +2,35 @@ #include "keydb.h" #include "aes.h" #include "sha.h" +#include "fatmbr.h" +#include "unittype.h" #include "sdmmc.h" -#include "qff.h" + + +#define KEY95_SHA256 ((IS_DEVKIT) ? slot0x11Key95dev_sha256 : slot0x11Key95_sha256) + +// see: https://www.3dbrew.org/wiki/NCSD#NCSD_header +static const u32 np_keyslots[9][4] = { // [NP_TYPE][NP_SUBTYPE] + { 0xFF, 0xFF, 0xFF, 0xFF }, // none + { 0xFF, 0x03, 0x04, 0x05 }, // standard + { 0xFF, 0x03, 0x04, 0x05 }, // FAT (custom, not in NCSD) + { 0xFF, 0xFF, 0x06, 0xFF }, // FIRM + { 0xFF, 0xFF, 0x07, 0xFF }, // AGBSAVE + { 0xFF, 0xFF, 0xFF, 0xFF }, // NCSD (custom) + { 0xFF, 0xFF, 0xFF, 0xFF }, // D0K3 (custom) + { 0xFF, 0xFF, 0xFF, 0x11 }, // SECRET (custom) + { 0xFF, 0xFF, 0xFF, 0xFF } // BONUS (custom) +}; static u8 slot0x05KeyY[0x10] = { 0x00 }; // need to load this from FIRM0 / external file static const u8 slot0x05KeyY_sha256[0x20] = { // hash for slot0x05KeyY (16 byte) 0x98, 0x24, 0x27, 0x14, 0x22, 0xB0, 0x6B, 0xF2, 0x10, 0x96, 0x9C, 0x36, 0x42, 0x53, 0x7C, 0x86, 0x62, 0x22, 0x5C, 0xFD, 0x6F, 0xAE, 0x9B, 0x0A, 0x85, 0xA5, 0xCE, 0x21, 0xAA, 0xB6, 0xC8, 0x4D }; +static const u8 slot0x24KeyY_sha256[0x20] = { // hash for slot0x24KeyY (16 byte) + 0x5F, 0x04, 0x01, 0x22, 0x95, 0xB2, 0x23, 0x70, 0x12, 0x40, 0x53, 0x30, 0xC0, 0xA7, 0xBF, 0x7C, + 0xD4, 0x40, 0x92, 0x25, 0xD1, 0x9D, 0xA2, 0xDE, 0xCD, 0xC7, 0x12, 0x97, 0x08, 0x46, 0x54, 0xB7 +}; static const u8 slot0x11Key95_sha256[0x20] = { // slot0x11Key95 hash (first 16 byte of sector0x96) 0xBA, 0xC1, 0x40, 0x9C, 0x6E, 0xE4, 0x1F, 0x04, 0xAA, 0xC4, 0xE2, 0x09, 0x5C, 0xE9, 0x4F, 0x78, @@ -20,32 +41,6 @@ static const u8 slot0x11Key95dev_sha256[0x20] = { // slot0x11Key95 hash (first 1 0x97, 0x0E, 0x52, 0x29, 0x63, 0x19, 0x47, 0x51, 0x15, 0xD8, 0x02, 0x7A, 0x22, 0x0F, 0x58, 0x15, 0xD7, 0x6C, 0xE9, 0xAD, 0xE7, 0xFE, 0x9A, 0x25, 0x4E, 0x4A, 0x0C, 0x82, 0x67, 0xB5, 0x4A, 0x7B }; - -static const u8 nand_magic_n3ds[0x60] = { // NCSD NAND header N3DS magic - 0x4E, 0x43, 0x53, 0x44, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x04, 0x03, 0x03, 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x02, 0x02, 0x03, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x05, 0x00, 0x00, 0x88, 0x05, 0x00, 0x80, 0x01, 0x00, 0x00, - 0x80, 0x89, 0x05, 0x00, 0x00, 0x20, 0x00, 0x00, 0x80, 0xA9, 0x05, 0x00, 0x00, 0x20, 0x00, 0x00, - 0x80, 0xC9, 0x05, 0x00, 0x80, 0xF6, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 -}; - -static const u8 nand_magic_o3ds[0x60] = { // NCSD NAND header O3DS magic - 0x4E, 0x43, 0x53, 0x44, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x04, 0x03, 0x03, 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x05, 0x00, 0x00, 0x88, 0x05, 0x00, 0x80, 0x01, 0x00, 0x00, - 0x80, 0x89, 0x05, 0x00, 0x00, 0x20, 0x00, 0x00, 0x80, 0xA9, 0x05, 0x00, 0x00, 0x20, 0x00, 0x00, - 0x80, 0xC9, 0x05, 0x00, 0x80, 0xAE, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 -}; - -/*static const u8 twl_mbr[0x42] = { // encrypted version inside the NCSD NAND header (@0x1BE) - 0x00, 0x04, 0x18, 0x00, 0x06, 0x01, 0xA0, 0x3F, 0x97, 0x00, 0x00, 0x00, 0xA9, 0x7D, 0x04, 0x00, - 0x00, 0x04, 0x8E, 0x40, 0x06, 0x01, 0xA0, 0xC3, 0x8D, 0x80, 0x04, 0x00, 0xB3, 0x05, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x55, 0xAA -};*/ static u8 CtrNandCtr[16]; static u8 TwlNandCtr[16]; @@ -54,23 +49,24 @@ static u8 OtpSha256[32] = { 0 }; u32 LoadKeyYFromP9(u8* key, const u8* keyhash, u32 offset, u32 keyslot) { - static u32 offsetA9l = 0x066A00; // fixed offset, this only has to work for FIRM90 / FIRM81 + static const u32 offsetA9l = 0x066A00; // fixed offset, this only has to work for FIRM90 / FIRM81 + static const u32 sector_firm0 = 0x058980; // standard firm0 sector (this only has to work in A9LH anyways) u8 ctr0x15[16] __attribute__((aligned(32))); u8 keyY0x15[16] __attribute__((aligned(32))); u8 keyY[16] __attribute__((aligned(32))); u8 header[0x200]; // check arm9loaderhax - if (!IS_A9LH || (offset < (offsetA9l + 0x0800))) return 1; + if (!IS_A9LH || IS_SIGHAX || (offset < (offsetA9l + 0x0800))) return 1; // section 2 (arm9loader) header of FIRM // this is @0x066A00 in FIRM90 & FIRM81 - ReadNandBytes(header, (SECTOR_FIRM0 * 0x200) + offsetA9l, 0x200, 0x06); + ReadNandBytes(header, (sector_firm0 * 0x200) + offsetA9l, 0x200, 0x06); memcpy(keyY0x15, header + 0x10, 0x10); // 0x15 keyY memcpy(ctr0x15, header + 0x20, 0x10); // 0x15 counter // read and decrypt the encrypted keyY - ReadNandBytes(keyY, (SECTOR_FIRM0 * 0x200) + offset, 0x10, 0x06); + ReadNandBytes(keyY, (sector_firm0 * 0x200) + offset, 0x10, 0x06); setup_aeskeyY(0x15, keyY0x15); use_aeskey(0x15); ctr_decrypt_byte(keyY, keyY, 0x10, offset - (offsetA9l + 0x800), AES_CNT_CTRNAND_MODE, ctr0x15); @@ -92,21 +88,25 @@ bool InitNandCrypto(void) { // part #0: KeyX / KeyY for secret sector 0x96 // on a9lh this MUST be run before accessing the SHA register in any other way - if (IS_A9LH) { // for a9lh + if (IS_UNLOCKED) { // if OTP is unlocked + // see: https://www.3dbrew.org/wiki/OTP_Registers + sha_quick(OtpSha256, (u8*) 0x10012000, 0x90, SHA256_MODE); + } else if (IS_A9LH) { // for a9lh // store the current SHA256 from register memcpy(OtpSha256, (void*) REG_SHAHASH, 32); - if (!CheckSector0x96Crypto()) { // 3dssafe users be damned (no offense meant to mashers) - u8 __attribute__((aligned(32))) otp0x90[0x90]; - u8 __attribute__((aligned(32))) otp_key[0x10]; - u8 __attribute__((aligned(32))) otp_iv[0x10]; - memcpy(otp0x90, (u8*) 0x01FFB800, 0x90); - if ((LoadKeyFromFile(otp_key, 0x11, 'N', "OTP") == 0) && - (LoadKeyFromFile(otp_iv, 0x11, 'I', "IVOTP") == 0)) { - setup_aeskey(0x11, otp_key); - use_aeskey(0x11); - cbc_encrypt(otp0x90, otp0x90, 0x90 / 0x10, AES_CNT_TITLEKEY_ENCRYPT_MODE, otp_iv); - sha_quick(OtpSha256, otp0x90, 0x90, SHA256_MODE); - } + } + if (!CheckSector0x96Crypto()) { // if all else fails... + u8 __attribute__((aligned(32))) otp0x90[0x90]; + u8 __attribute__((aligned(32))) otp_key[0x10]; + u8 __attribute__((aligned(32))) otp_iv[0x10]; + memcpy(otp0x90, (u8*) 0x01FFB800, 0x90); + if ((LoadKeyFromFile(otp_key, 0x11, 'N', "OTP") == 0) && + ((LoadKeyFromFile(otp_iv, 0x11, 'I', "IVOTP") == 0) || + (LoadKeyFromFile(otp_iv, 0x11, 'I', "OTP") == 0))) { + setup_aeskey(0x11, otp_key); + use_aeskey(0x11); + cbc_encrypt(otp0x90, otp0x90, 0x90 / 0x10, AES_CNT_TITLEKEY_ENCRYPT_MODE, otp_iv); + sha_quick(OtpSha256, otp0x90, 0x90, SHA256_MODE); } } @@ -124,7 +124,7 @@ bool InitNandCrypto(void) // part #2: TWL KEY // see: https://www.3dbrew.org/wiki/Memory_layout#ARM9_ITCM - if (IS_A9LH) { // only for a9lh + if (IS_A9LH) { // only for a9lh (and sighax, for now) u32* TwlCustId = (u32*) (0x01FFB808); u8 TwlKeyX[16] __attribute__((aligned(32))); u8 TwlKeyY[16] __attribute__((aligned(32))); @@ -148,14 +148,21 @@ bool InitNandCrypto(void) use_aeskey(0x03); } - // part #3: CTRNAND N3DS KEY + // part #3: CTRNAND N3DS KEY / AGBSAVE CMAC KEY // thanks AuroraWright and Gelex for advice on this // see: https://github.com/AuroraWright/Luma3DS/blob/master/source/crypto.c#L347 - if (IS_A9LH) { // only for a9lh + if (IS_A9LH && !IS_SIGHAX) { // only on A9LH, not required on sighax // keyY 0x05 is encrypted @0x0EB014 in the FIRM90 // keyY 0x05 is encrypted @0x0EB24C in the FIRM81 if ((LoadKeyYFromP9(slot0x05KeyY, slot0x05KeyY_sha256, 0x0EB014, 0x05) != 0) && - (LoadKeyYFromP9(slot0x05KeyY, slot0x05KeyY_sha256, 0x0EB24C, 0x05) != 0)) {}; + (LoadKeyYFromP9(slot0x05KeyY, slot0x05KeyY_sha256, 0x0EB24C, 0x05) != 0)) + LoadKeyFromFile(slot0x05KeyY, 0x05, 'Y', NULL); + + // keyY 0x24 is encrypted @0x0E62DC in the FIRM90 + // keyY 0x24 is encrypted @0x0E6514 in the FIRM81 + if ((LoadKeyYFromP9(NULL, slot0x24KeyY_sha256, 0x0E62DC, 0x24) != 0) && + (LoadKeyYFromP9(NULL, slot0x24KeyY_sha256, 0x0E6514, 0x24) != 0)) + LoadKeyFromFile(NULL, 0x24, 'Y', NULL); } return true; @@ -164,17 +171,11 @@ bool InitNandCrypto(void) bool CheckSlot0x05Crypto(void) { // step #1 - check the slot0x05KeyY SHA-256 - u8 shasum[32]; - sha_quick(shasum, slot0x05KeyY, 16, SHA256_MODE); - if (memcmp(shasum, slot0x05KeyY_sha256, 32) == 0) + if (sha_cmp(slot0x05KeyY_sha256, slot0x05KeyY, 16, SHA256_MODE) == 0) return true; - // step #2 - check actual CTRNAND magic - const u8 magic[8] = {0xE9, 0x00, 0x00, 0x43, 0x54, 0x52, 0x20, 0x20}; - const u32 sector = 0x05CAD7; - u8 buffer[0x200]; - ReadNandSectors(buffer, sector, 1, 0x05); - if (memcmp(buffer, magic, 8) == 0) + // step #2 - check actual presence of CTRNAND FAT + if (GetNandPartitionInfo(NULL, NP_TYPE_STD, NP_SUBTYPE_CTR_N, 0) == 0) return true; // failed if we arrive here @@ -184,19 +185,20 @@ bool CheckSlot0x05Crypto(void) bool CheckSector0x96Crypto(void) { u8 buffer[0x200]; - ReadNandSectors(buffer, 0x96, 1, 0x11); - return (sha_cmp((IS_DEVKIT) ? slot0x11Key95dev_sha256 : slot0x11Key95_sha256, buffer, 16, SHA256_MODE) == 0); + ReadNandSectors(buffer, SECTOR_SECRET, 1, 0x11); + return (sha_cmp(KEY95_SHA256, buffer, 16, SHA256_MODE) == 0); } bool CheckFirmCrypto(void) { // check the FIRM magic - const u8 magic[8] = {'F', 'I', 'R', 'M'}; - const u32 sectors[] = { SECTOR_FIRM0, SECTOR_FIRM1 }; - u8 buffer[0x200]; - for (u32 i = 0; i < sizeof(sectors) / sizeof(u32); i++) { - ReadNandSectors(buffer, sectors[i], 1, 0x06); - if (memcmp(buffer, magic, sizeof(magic)) == 0) return true; + const u8 magic[8] = {'F', 'I', 'R', 'M'}; + for (u32 i = 0; i < 8; i++) { + NandPartitionInfo np_info; + u8 buffer[0x200]; + if ((GetNandPartitionInfo(&np_info, NP_TYPE_FIRM, NP_SUBTYPE_CTR, i) != 0) || + (ReadNandSectors(buffer, np_info.sector, 1, np_info.keyslot) != 0)) break; + if (memcmp(buffer, magic, sizeof(magic)) == 0) return true; // success } // failed if we arrive here @@ -205,17 +207,17 @@ bool CheckFirmCrypto(void) void CryptNand(void* buffer, u32 sector, u32 count, u32 keyslot) { - u32 mode = (sector >= SECTOR_TWL + SIZE_TWL) ? AES_CNT_CTRNAND_MODE : AES_CNT_TWLNAND_MODE; + u32 mode = (keyslot != 0x03) ? AES_CNT_CTRNAND_MODE : AES_CNT_TWLNAND_MODE; // somewhat hacky u8 ctr[16] __attribute__((aligned(32))); u32 blocks = count * (0x200 / 0x10); // copy NAND CTR and increment it - memcpy(ctr, (sector >= SECTOR_TWL + SIZE_TWL) ? CtrNandCtr : TwlNandCtr, 16); + memcpy(ctr, (keyslot != 0x03) ? CtrNandCtr : TwlNandCtr, 16); // hacky again add_ctr(ctr, sector * (0x200 / 0x10)); // decrypt the data use_aeskey(keyslot); - ctr_decrypt(buffer, buffer, blocks, mode, ctr); + ctr_decrypt((void*) buffer, (void*) buffer, blocks, mode, ctr); } void CryptSector0x96(void* buffer, bool encrypt) @@ -228,16 +230,16 @@ void CryptSector0x96(void* buffer, bool encrypt) // decrypt the sector use_aeskey(0x11); - ecb_decrypt(buffer, buffer, 0x200 / AES_BLOCK_SIZE, mode); + ecb_decrypt((void*) buffer, (void*) buffer, 0x200 / AES_BLOCK_SIZE, mode); } -int ReadNandBytes(void* buffer, u32 offset, u32 count, u32 keyslot) +int ReadNandBytes(void* buffer, u64 offset, u64 count, u32 keyslot) { - u8* buffer8 = (u8*) buffer; if (!(offset % 0x200) && !(count % 0x200)) { // aligned data -> simple case // simple wrapper function for ReadNandSectors(...) - return ReadNandSectors(buffer8, offset / 0x200, count / 0x200, keyslot); + return ReadNandSectors(buffer, offset / 0x200, count / 0x200, keyslot); } else { // misaligned data -> -___- + u8* buffer8 = (u8*) buffer; u8 l_buffer[0x200]; int errorcode = 0; if (offset % 0x200) { // handle misaligned offset @@ -264,13 +266,13 @@ int ReadNandBytes(void* buffer, u32 offset, u32 count, u32 keyslot) } } -int WriteNandBytes(const void* buffer, u32 offset, u32 count, u32 keyslot) +int WriteNandBytes(const void* buffer, u64 offset, u64 count, u32 keyslot) { - u8* buffer8 = (u8*) buffer; if (!(offset % 0x200) && !(count % 0x200)) { // aligned data -> simple case // simple wrapper function for WriteNandSectors(...) - return WriteNandSectors(buffer8, offset / 0x200, count / 0x200, keyslot); + return WriteNandSectors(buffer, offset / 0x200, count / 0x200, keyslot); } else { // misaligned data -> -___- + u8* buffer8 = (u8*) buffer8; u8 l_buffer[0x200]; int errorcode = 0; if (offset % 0x200) { // handle misaligned offset @@ -302,24 +304,25 @@ int WriteNandBytes(const void* buffer, u32 offset, u32 count, u32 keyslot) } int ReadNandSectors(void* buffer, u32 sector, u32 count, u32 keyslot) -{ +{ + u8* buffer8 = (u8*) buffer; if (!count) return 0; // <--- just to be safe - int errorcode = sdmmc_nand_readsectors(sector, count, buffer); + int errorcode = sdmmc_nand_readsectors(sector, count, buffer8); if (errorcode) return errorcode; - if ((keyslot == 0x11) && (sector == 0x96)) CryptSector0x96(buffer, false); - else if (keyslot < 0x40) CryptNand(buffer, sector, count, keyslot); + if ((keyslot == 0x11) && (sector == SECTOR_SECRET)) CryptSector0x96(buffer8, false); + else if (keyslot < 0x40) CryptNand(buffer8, sector, count, keyslot); return 0; } int WriteNandSectors(const void* buffer, u32 sector, u32 count, u32 keyslot) { + u8* buffer8 = (u8*) buffer; // buffer must not be changed, so this is a little complicated - const u8* buffer8 = (u8*) buffer; for (u32 s = 0; s < count; s += (NAND_BUFFER_SIZE / 0x200)) { u32 pcount = min((NAND_BUFFER_SIZE/0x200), (count - s)); memcpy(NAND_BUFFER, buffer8 + (s*0x200), pcount * 0x200); - if ((keyslot == 0x11) && (sector == 0x96)) CryptSector0x96(NAND_BUFFER, true); + if ((keyslot == 0x11) && (sector == SECTOR_SECRET)) CryptSector0x96(NAND_BUFFER, true); else if (keyslot < 0x40) CryptNand(NAND_BUFFER, sector + s, pcount, keyslot); int errorcode = sdmmc_nand_writesectors(sector + s, pcount, NAND_BUFFER); if (errorcode) return errorcode; @@ -328,43 +331,132 @@ int WriteNandSectors(const void* buffer, u32 sector, u32 count, u32 keyslot) return 0; } -u32 CheckNandHeader(void* header) +// shamelessly stolen from myself +// see: https://github.com/d0k3/GodMode9/blob/master/source/game/ncsd.c#L4 +u32 ValidateNandNcsdHeader(NandNcsdHeader* header) { - // TWL MBR check - ignored - /*u8 header_dec[0x200]; - memcpy(header_dec, header, 0x200); - CryptNand(header_dec, 0, 1, 0x03); - if (memcmp(header_dec + 0x1BE, twl_mbr, sizeof(twl_mbr)) != 0) - return 0; // header does not belong to console */ + u8 zeroes[16] = { 0 }; + if ((memcmp(header->magic, "NCSD", 4) != 0) || // check magic number + (memcmp(header->partitions_fs_type, zeroes, 8) == 0) || header->mediaId) // prevent detection of cart NCSD images + return 1; - // header type check - u8* header_enc = header; - if (memcmp(header_enc + 0x100, nand_magic_n3ds, sizeof(nand_magic_n3ds) == 0) == 0) - return (IS_O3DS) ? 0 : NAND_TYPE_N3DS; - else if (memcmp(header_enc + 0x100, nand_magic_o3ds, sizeof(nand_magic_o3ds) == 0) == 0) - return NAND_TYPE_O3DS; + u32 data_units = 0; + u32 firm_count = 0; + for (u32 i = 0; i < 8; i++) { + NandNcsdPartition* partition = header->partitions + i; + u8 np_type = header->partitions_fs_type[i]; + if ((i == 0) && !partition->size) return 1; // first content must be there + else if (!partition->size) continue; + if (!np_type) return 1; // partition must have a type + if (partition->offset < data_units) + return 1; // overlapping partitions, failed + data_units = partition->offset + partition->size; + if (np_type == NP_TYPE_FIRM) firm_count++; // count firms + } + if (data_units > header->size) return 1; + if (!firm_count) return 1; // at least one firm is required + + return 0; +} + +u32 GetNandNcsdMinSizeSectors(NandNcsdHeader* ncsd) +{ + u32 nand_minsize = 0; + for (u32 prt_idx = 0; prt_idx < 8; prt_idx++) { + u32 prt_end = ncsd->partitions[prt_idx].offset + ncsd->partitions[prt_idx].size; + if (prt_end > nand_minsize) nand_minsize = prt_end; + } + + return nand_minsize; +} + +u32 GetNandMinSizeSectors(void) +{ + NandNcsdHeader ncsd; + if ((ReadNandSectors((u8*) &ncsd, 0, 1, 0xFF) != 0) || + (ValidateNandNcsdHeader(&ncsd) != 0)) return 0; + + return GetNandNcsdMinSizeSectors(&ncsd); +} + +u32 GetNandSizeSectors(void) +{ + return getMMCDevice(0)->total_size; +} + +u32 GetNandNcsdPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 index, NandNcsdHeader* ncsd) +{ + // safety / set keyslot + if ((type == NP_TYPE_FAT) || (type > NP_TYPE_BONUS) || (subtype > NP_SUBTYPE_CTR_N)) return 1; + info->keyslot = np_keyslots[type][subtype]; + + // full (minimum) NAND "partition" + if (type == NP_TYPE_NONE) { + info->sector = 0x00; + info->count = GetNandNcsdMinSizeSectors(ncsd); + return 0; + } + + // special, custom partition types, not in NCSD + if (type >= NP_TYPE_NCSD) { + if (type == NP_TYPE_NCSD) { + info->sector = 0x00; // hardcoded + info->count = 0x01; + } else if (type == NP_TYPE_D0K3) { + info->sector = SECTOR_D0K3; // hardcoded + info->count = SECTOR_SECRET - info->sector; + } else if (type == NP_TYPE_SECRET) { + info->sector = SECTOR_SECRET; + info->count = 0x01; + } else if (type == NP_TYPE_BONUS) { + info->sector = GetNandNcsdMinSizeSectors(ncsd); + info->count = 0x00; // placeholder, actual size needs info from NAND chip + } else return 1; + return 0; + } + + u32 prt_idx = 8; + for (prt_idx = 0; prt_idx < 8; prt_idx++) { + if ((ncsd->partitions_fs_type[prt_idx] != type) || + (ncsd->partitions_crypto_type[prt_idx] != subtype)) continue; + if (index == 0) break; + index--; + } + + if (prt_idx >= 8) return 1; // not found + info->sector = ncsd->partitions[prt_idx].offset; + info->count = ncsd->partitions[prt_idx].size; return 0; } -u32 CheckNandType(void) +u32 GetNandPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 index) { - if (ReadNandSectors(NAND_BUFFER, 0, 1, 0xFF) != 0) - return 0; - if (memcmp(NAND_BUFFER + 0x100, nand_magic_n3ds, 0x60) == 0) { - return (IS_O3DS) ? 0 : NAND_TYPE_N3DS; - } else if (memcmp(NAND_BUFFER + 0x100, nand_magic_o3ds, 0x60) == 0) { - u8 magic[8] = {0xE9, 0x00, 0x00, 0x43, 0x54, 0x52, 0x20, 0x20}; - if (ReadNandSectors(NAND_BUFFER, 0x5CAE5, 1, 0x04) != 0) - return 0; - return ((IS_O3DS) || (memcmp(magic, NAND_BUFFER, 8) == 0)) ? - NAND_TYPE_O3DS : NAND_TYPE_NO3DS; + // workaround for info == NULL + NandPartitionInfo dummy; + if (!info) info = &dummy; + + // find type & subtype in NCSD header + u8 header[0x200]; + ReadNandSectors(header, 0x00, 1, 0xFF); + NandNcsdHeader* ncsd = (NandNcsdHeader*) header; + if ((ValidateNandNcsdHeader(ncsd) != 0) || + ((type == NP_TYPE_FAT) && (GetNandNcsdPartitionInfo(info, NP_TYPE_STD, subtype, 0, ncsd) != 0)) || + ((type != NP_TYPE_FAT) && (GetNandNcsdPartitionInfo(info, type, subtype, index, ncsd) != 0))) + return 1; // not found + + if (type == NP_TYPE_BONUS) { // size of bonus partition + info->count = GetNandSizeSectors() - info->sector; + } else if (type == NP_TYPE_FAT) { // FAT type specific stuff + ReadNandSectors(header, info->sector, 1, info->keyslot); + MbrHeader* mbr = (MbrHeader*) header; + if ((ValidateMbrHeader(mbr) != 0) || (index >= 4) || + (mbr->partitions[index].sector == 0) || (mbr->partitions[index].count == 0) || + (mbr->partitions[index].sector + mbr->partitions[index].count > info->count)) + return 1; + info->sector += mbr->partitions[index].sector; + info->count = mbr->partitions[index].count; } return 0; } - -u64 GetNandSizeSectors(void) -{ - return getMMCDevice(0)->total_size; -} diff --git a/source/nand/nand.h b/source/nand/nand.h index 0dab597..3119681 100644 --- a/source/nand/nand.h +++ b/source/nand/nand.h @@ -1,39 +1,59 @@ #pragma once #include "common.h" -#include "unittype.h" -#define NAND_MIN_SECTORS ((!IS_O3DS) ? NAND_MIN_SECTORS_N3DS : NAND_MIN_SECTORS_O3DS) - -#define NAND_SYSNAND (1<<0) -#define NAND_ZERONAND (1<<3) -#define NAND_TYPE_O3DS (1<<4) -#define NAND_TYPE_N3DS (1<<5) -#define NAND_TYPE_NO3DS (1<<6) - -// minimum size of NAND memory -#define NAND_MIN_SECTORS_O3DS 0x1D7800 -#define NAND_MIN_SECTORS_N3DS 0x26C000 - -// start sectors of partitions -#define SECTOR_TWL 0x000000 +// hardcoded start sectors +#define SECTOR_D0K3 0x000001 #define SECTOR_SECRET 0x000096 -#define SECTOR_TWLN 0x000097 -#define SECTOR_TWLP 0x04808D -#define SECTOR_AGBSAVE 0x058800 -#define SECTOR_FIRM0 0x058980 -#define SECTOR_FIRM1 0x05A980 -#define SECTOR_CTR 0x05C980 -// sizes of partitions (in sectors) -#define SIZE_TWL 0x058800 -#define SIZE_TWLN 0x047DA9 -#define SIZE_TWLP 0x0105B3 -#define SIZE_AGBSAVE 0x000180 -#define SIZE_FIRM0 0x002000 -#define SIZE_FIRM1 0x002000 -#define SIZE_CTR_O3DS 0x17AE80 -#define SIZE_CTR_N3DS 0x20F680 +// filenames for sector 0x96 +#define SECTOR_NAME "sector0x96.bin" +#define SECRET_NAME "secret_sector.bin" + +// 0x110...0x118 in the NAND NCSD header +// see: https://www.3dbrew.org/wiki/NCSD#NCSD_header +#define NP_TYPE_NONE 0 +#define NP_TYPE_STD 1 +#define NP_TYPE_FAT 2 // this is of our own making +#define NP_TYPE_FIRM 3 +#define NP_TYPE_AGB 4 +#define NP_TYPE_NCSD 5 // this is of our own making +#define NP_TYPE_D0K3 6 // my own partition ^_^ +#define NP_TYPE_SECRET 7 // this is of our own making +#define NP_TYPE_BONUS 8 // this is of our own making + +// 0x118...0x120 in the NAND NCSD header +// see: https://www.3dbrew.org/wiki/NCSD#NCSD_header +#define NP_SUBTYPE_NONE 0 +#define NP_SUBTYPE_TWL 1 +#define NP_SUBTYPE_CTR 2 +#define NP_SUBTYPE_CTR_N 3 + + +typedef struct { + u32 sector; + u32 count; + u32 keyslot; +} __attribute__((packed)) NandPartitionInfo; + +typedef struct { + u32 offset; + u32 size; +} __attribute__((packed)) NandNcsdPartition; + +// see: https://www.3dbrew.org/wiki/NCSD#NCSD_header +typedef struct { + u8 signature[0x100]; + u8 magic[4]; + u32 size; + u64 mediaId; // this is zero + u8 partitions_fs_type[8]; + u8 partitions_crypto_type[8]; + NandNcsdPartition partitions[8]; + u8 unknown[0x5E]; + u8 twl_mbr[0x42]; +} __attribute__((packed)) NandNcsdHeader; + bool InitNandCrypto(void); bool CheckSlot0x05Crypto(void); @@ -42,11 +62,14 @@ bool CheckFirmCrypto(void); void CryptNand(void* buffer, u32 sector, u32 count, u32 keyslot); void CryptSector0x96(void* buffer, bool encrypt); -int ReadNandBytes(void* buffer, u32 offset, u32 count, u32 keyslot); -int WriteNandBytes(const void* buffer, u32 offset, u32 count, u32 keyslot); +int ReadNandBytes(void* buffer, u64 offset, u64 count, u32 keyslot); +int WriteNandBytes(const void* buffer, u64 offset, u64 count, u32 keyslot); int ReadNandSectors(void* buffer, u32 sector, u32 count, u32 keyslot); int WriteNandSectors(const void* buffer, u32 sector, u32 count, u32 keyslot); -u64 GetNandSizeSectors(void); -u32 CheckNandHeader(void* header); -u32 CheckNandType(void); +u32 ValidateNandNcsdHeader(NandNcsdHeader* header); +u32 GetNandNcsdMinSizeSectors(NandNcsdHeader* ncsd); +u32 GetNandMinSizeSectors(void); +u32 GetNandSizeSectors(void); +u32 GetNandNcsdPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 index, NandNcsdHeader* ncsd); +u32 GetNandPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 index); diff --git a/source/safety/safewrite.c b/source/safety/safewrite.c index 0b35373..0710610 100644 --- a/source/safety/safewrite.c +++ b/source/safety/safewrite.c @@ -18,6 +18,18 @@ u32 SafeWriteFile(FIL* file, void* buff, FSIZE_t ofs, UINT btw) { return (sha_cmp(sha_in, buff, btw, SHA256_MODE) == 0) ? 0 : 1; } +// safe file quick writer function +// same as SafeWriteFile(), but also handles filec reate, too, same warnings apply +u32 SafeQWriteFile(const TCHAR* path, void* buff, UINT btw) { + FIL fp; + u32 ret = 0; + if (f_open(&fp, path, FA_READ|FA_WRITE|FA_CREATE_ALWAYS) != FR_OK) + return 1; + ret = SafeWriteFile(&fp, buff, 0, btw); + f_close(&fp); + return ret; +} + // safe NAND writer function, warnings: // (1) contents of buffer may change on corruption // (2) uses SHA register diff --git a/source/safety/safewrite.h b/source/safety/safewrite.h index 6369cd9..02a40a4 100644 --- a/source/safety/safewrite.h +++ b/source/safety/safewrite.h @@ -4,4 +4,5 @@ #include "ff.h" u32 SafeWriteFile(FIL* file, void* buff, FSIZE_t ofs, UINT btw); +u32 SafeQWriteFile(const TCHAR* path, void* buff, UINT btw); u32 SafeWriteNand(void* buff, u32 ofs, u32 btw, u32 keyslot);