Fix NAND offset hardcoding

... SafeB9SInstaller now works with custom NCSDs
This commit is contained in:
d0k3 2017-06-02 17:02:11 +02:00
parent 73bcc07349
commit 88bc7955d8
10 changed files with 528 additions and 292 deletions

View File

@ -55,11 +55,11 @@
// buffer area defines (big buffer for firm) // buffer area defines (big buffer for firm)
#define WORK_BUFFER ((u8*) 0x21000000) #define WORK_BUFFER ((u8*) 0x21000000)
#define WORK_BUFFER_SIZE (0x100000) #define WORK_BUFFER_SIZE (0x400000)
#define NAND_BUFFER ((u8*) 0x22000000) #define FIRM_BUFFER ((u8*) 0x21400000)
#define NAND_BUFFER_SIZE (0x100000)
#define FIRM_BUFFER ((u8*) 0x23000000)
#define FIRM_BUFFER_SIZE (0x400000) #define FIRM_BUFFER_SIZE (0x400000)
#define NAND_BUFFER ((u8*) 0x21800000)
#define NAND_BUFFER_SIZE (0x100000)
inline u32 strchrcount(const char* str, char symbol) { inline u32 strchrcount(const char* str, char symbol) {
u32 count = 0; u32 count = 0;

View File

@ -10,11 +10,12 @@
#define IS_DEVKIT ((*(vu8*) (0x01FFB800+0x19)) != 0x0) #define IS_DEVKIT ((*(vu8*) (0x01FFB800+0x19)) != 0x0)
// see: https://3dbrew.org/wiki/CONFIG11_Registers // see: https://3dbrew.org/wiki/CONFIG11_Registers
// (also returns true for sighaxed systems, maybe change the name later?)
#define IS_A9LH ((*(vu32*) 0x101401C0) == 0) #define IS_A9LH ((*(vu32*) 0x101401C0) == 0)
// https://www.3dbrew.org/wiki/CONFIG9_Registers // https://www.3dbrew.org/wiki/CONFIG9_Registers
// (actually checks for an unlocked OTP) // (actually checks for an unlocked OTP)
#define IS_UNLOCKED (!((*(vu8*)0x10000000) & 0x2)) #define IS_UNLOCKED (!((*(vu8*)0x10000000) & 0x2))
// A9LH + unlocked = SigHax // A9LH + unlocked == SigHax
#define IS_SIGHAX (IS_A9LH && IS_UNLOCKED) #define IS_SIGHAX (IS_A9LH && IS_UNLOCKED)

View File

@ -11,71 +11,34 @@
#include "nand.h" #include "nand.h"
#include "sdmmc.h" #include "sdmmc.h"
#define PART_INFO(pdrv) (DriveInfo + pdrv)
#define PART_TYPE(pdrv) (DriveInfo[pdrv].type) #define PART_TYPE(pdrv) (DriveInfo[pdrv].type)
#define PART_SUBTYPE(pdrv) (DriveInfo[pdrv].subtype)
#define TYPE_NONE 0 #define TYPE_NONE 0
#define TYPE_SYSNAND NAND_SYSNAND #define TYPE_SYSNAND (1UL<<0)
#define TYPE_SDCARD (1UL<<4) #define TYPE_SDCARD (1UL<<4)
#define SUBTYPE_CTRN 0 #define SUBTYPE_CTRN 1
#define SUBTYPE_CTRN_N 1 #define SUBTYPE_TWLN 2
#define SUBTYPE_CTRN_NO 2 #define SUBTYPE_TWLP 3
#define SUBTYPE_TWLN 3 #define SUBTYPE_NONE 0
#define SUBTYPE_TWLP 4
#define SUBTYPE_NONE 5
typedef struct { typedef struct {
BYTE type; BYTE type;
BYTE subtype; BYTE subtype;
} FATpartition;
typedef struct {
DWORD offset; DWORD offset;
DWORD size; DWORD size;
BYTE keyslot; BYTE keyslot;
} SubtypeDesc; } FATpartition;
FATpartition DriveInfo[4] = { FATpartition DriveInfo[13] = {
{ TYPE_SDCARD, SUBTYPE_NONE }, // 0 - SDCARD { TYPE_SDCARD, SUBTYPE_NONE, 0, 0, 0xFF }, // 0 - SDCARD
{ TYPE_SYSNAND, SUBTYPE_CTRN }, // 1 - SYSNAND CTRNAND { TYPE_SYSNAND, SUBTYPE_CTRN, 0, 0, 0xFF }, // 1 - SYSNAND CTRNAND
{ TYPE_SYSNAND, SUBTYPE_TWLN }, // 2 - SYSNAND TWLN { TYPE_SYSNAND, SUBTYPE_TWLN, 0, 0, 0xFF }, // 2 - SYSNAND TWLN
{ TYPE_SYSNAND, SUBTYPE_TWLP }, // 3 - SYSNAND TWLP { 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]);
}
/*-----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/
@ -101,12 +64,33 @@ DSTATUS disk_initialize (
BYTE pdrv /* Physical drive number to identify the drive */ 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; if (sdmmc_sdcard_init() != 0) return STA_NOINIT|STA_NODISK;
} else if (pdrv < 4) { fat_info->size = getMMCDevice(1)->total_size;
nand_type_sys = CheckNandType(); } else if (type == TYPE_SYSNAND) {
if (!nand_type_sys) return STA_NOINIT|STA_NODISK; 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;
} }
@ -132,11 +116,8 @@ DRESULT disk_read (
if (sdmmc_sdcard_readsectors(sector, count, buff)) if (sdmmc_sdcard_readsectors(sector, count, buff))
return RES_PARERR; return RES_PARERR;
} else { } else {
SubtypeDesc* subtype = get_subtype_desc(pdrv); FATpartition* fat_info = PART_INFO(pdrv);
BYTE keyslot = subtype->keyslot; if (ReadNandSectors(buff, fat_info->offset + sector, count, fat_info->keyslot))
DWORD isector = subtype->offset + sector;
if (ReadNandSectors(buff, isector, count, keyslot))
return RES_PARERR; return RES_PARERR;
} }
@ -166,11 +147,8 @@ DRESULT disk_write (
if (sdmmc_sdcard_writesectors(sector, count, (BYTE *)buff)) if (sdmmc_sdcard_writesectors(sector, count, (BYTE *)buff))
return RES_PARERR; return RES_PARERR;
} else { } else {
SubtypeDesc* subtype = get_subtype_desc(pdrv); FATpartition* fat_info = PART_INFO(pdrv);
BYTE keyslot = subtype->keyslot; if (WriteNandSectors(buff, fat_info->offset + sector, count, fat_info->keyslot))
DWORD isector = subtype->offset + sector;
if (WriteNandSectors(buff, isector, count, keyslot))
return RES_PARERR; // unstubbed! return RES_PARERR; // unstubbed!
} }
@ -194,18 +172,12 @@ DRESULT disk_ioctl (
void *buff /* Buffer to send/receive control data */ void *buff /* Buffer to send/receive control data */
) )
{ {
BYTE type = PART_TYPE(pdrv);
switch (cmd) { switch (cmd) {
case GET_SECTOR_SIZE: case GET_SECTOR_SIZE:
*((DWORD*) buff) = 0x200; *((DWORD*) buff) = 0x200;
return RES_OK; return RES_OK;
case GET_SECTOR_COUNT: case GET_SECTOR_COUNT:
if (type == TYPE_SDCARD) { // SD card *((DWORD*) buff) = PART_INFO(pdrv)->size;
*((DWORD*) buff) = getMMCDevice(1)->total_size;
} else if (type != TYPE_NONE) { // NAND
*((DWORD*) buff) = get_subtype_desc(pdrv)->size;
}
return RES_OK; return RES_OK;
case GET_BLOCK_SIZE: case GET_BLOCK_SIZE:
*((DWORD*) buff) = 0x2000; *((DWORD*) buff) = 0x2000;

32
source/fatfs/fatmbr.c Normal file
View File

@ -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
}

90
source/fatfs/fatmbr.h Normal file
View File

@ -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);

View File

@ -1,6 +1,7 @@
#include "installer.h" #include "installer.h"
#include "safewrite.h" #include "safewrite.h"
#include "validator.h" #include "validator.h"
#include "unittype.h"
#include "nand.h" #include "nand.h"
#include "ui.h" #include "ui.h"
#include "qff.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 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 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_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_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_SECTOR0x96 (IS_DEVKIT ? INPUT_PATH "/secret_sector_dev.bin" : INPUT_PATH "/secret_sector.bin")
#define NAME_FIRMBACKUP INPUT_PATH "/firm0firm1.bak" #define NAME_FIRMBACKUP INPUT_PATH "/firm%lu_enc.bak"
#define NAME_SECTORBACKUP INPUT_PATH "/sector0x96.bak" #define NAME_SECTORBACKUP INPUT_PATH "/sector0x96_enc.bak"
#define STATUS_GREY -1 #define STATUS_GREY -1
#define STATUS_GREEN 0 #define STATUS_GREEN 0
@ -73,6 +70,7 @@ u32 ShowInstallerStatus(void) {
u32 SafeB9SInstaller(void) { u32 SafeB9SInstaller(void) {
UINT bt; UINT bt;
u32 ret = 0;
// initialization // initialization
ShowString("Initializing, please wait..."); ShowString("Initializing, please wait...");
@ -169,6 +167,20 @@ u32 SafeB9SInstaller(void) {
snprintf(msgCrypto, 64, "checking..."); snprintf(msgCrypto, 64, "checking...");
statusCrypto = STATUS_YELLOW; statusCrypto = STATUS_YELLOW;
ShowInstallerStatus(); 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()) { if (!CheckFirmCrypto()) {
snprintf(msgCrypto, 64, "FIRM crypto fail"); snprintf(msgCrypto, 64, "FIRM crypto fail");
statusCrypto = STATUS_RED; statusCrypto = STATUS_RED;
@ -198,25 +210,24 @@ u32 SafeB9SInstaller(void) {
snprintf(msgBackup, 64, "FIRM backup..."); snprintf(msgBackup, 64, "FIRM backup...");
statusBackup = STATUS_YELLOW; statusBackup = STATUS_YELLOW;
ShowInstallerStatus(); 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"); ShowProgress(0, 0, "FIRM backup");
for (u32 pos = 0; (pos < FIRM_NAND_SIZE) && (ret == 0); pos += WORK_BUFFER_SIZE) { for (u32 i = 0; i < n_firms; i++) {
UINT bytes = min(WORK_BUFFER_SIZE, FIRM_NAND_SIZE - pos); NandPartitionInfo np_info;
snprintf(msgBackup, 64, "FIRM backup (%luMB/%luMB)", ret = GetNandPartitionInfo(&np_info, NP_TYPE_FIRM, NP_SUBTYPE_CTR, i);
pos / (1024*1024), (u32) FIRM_NAND_SIZE / (1024 * 1024)); 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(); ShowInstallerStatus();
if ((ReadNandBytes(WORK_BUFFER, FIRM_NAND_OFFSET + pos, bytes, 0xFF) != 0) || if ((ReadNandBytes(WORK_BUFFER, foffset, fsize, 0xFF) != 0) ||
(SafeWriteFile(&fp, WORK_BUFFER, pos, bytes) != 0)) (SafeQWriteFile(bakname, WORK_BUFFER, fsize) != 0)) {
ret = 1; ret = 1;
ShowProgress(pos + bytes, FIRM_NAND_SIZE, "FIRM backup"); break;
}
ShowProgress(i + 1, n_firms, "FIRM backup");
} }
f_close(&fp);
if (ret != 0) { if (ret != 0) {
snprintf(msgBackup, 64, "FIRM backup fail"); snprintf(msgBackup, 64, "FIRM backup fail");
statusBackup = STATUS_RED; statusBackup = STATUS_RED;
@ -250,20 +261,21 @@ u32 SafeB9SInstaller(void) {
#ifndef NO_WRITE #ifndef NO_WRITE
ShowProgress(0, 0, "FIRM install"); ShowProgress(0, 0, "FIRM install");
do { 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; if (ret != 0) break;
ShowProgress(1, 2, "FIRM install (1/2)"); ret = SafeWriteNand(FIRM_BUFFER, np_info.sector * 0x200, firm_size, np_info.keyslot);
snprintf(msgInstall, 64, "FIRM install (1/2)");
ShowInstallerStatus();
ret = SafeWriteNand(FIRM_BUFFER, FIRM1_NAND_OFFSET, firm_size, 0x06);
if (ret != 0) break; if (ret != 0) break;
ShowProgress(1, 2, "FIRM install (2/2)"); ShowProgress(i+1, n_firms, "FIRM install");
snprintf(msgInstall, 64, "FIRM install (2/2)"); snprintf(msgInstall, 64, "FIRM install (%li/8)", i+1);
ShowInstallerStatus(); ShowInstallerStatus();
}
if (ret != 0) break;
if ((IS_A9LH && !IS_SIGHAX)) { if ((IS_A9LH && !IS_SIGHAX)) {
snprintf(msgInstall, 64, "0x96 revert..."); snprintf(msgInstall, 64, "0x96 revert...");
ShowInstallerStatus(); 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"); if (ret == 0) snprintf(msgA9lh, 64, "uninstalled");
} }
} while (false); } while (false);
@ -281,11 +293,11 @@ u32 SafeB9SInstaller(void) {
ShowInstallerStatus(); ShowInstallerStatus();
} }
#elif !defined FAIL_TEST #elif !defined FAIL_TEST
snprintf(msgInstall, 64, "test mode, not done"); snprintf(msgInstall, 64, "no write mode");
statusInstall = STATUS_YELLOW; statusInstall = STATUS_YELLOW;
return 0; return 0;
#else #else
snprintf(msgInstall, 64, "fail test mode..."); snprintf(msgInstall, 64, "emergency mode");
statusInstall = STATUS_YELLOW; statusInstall = STATUS_YELLOW;
ShowInstallerStatus(); ShowInstallerStatus();
#endif #endif
@ -299,23 +311,24 @@ u32 SafeB9SInstaller(void) {
snprintf(msgBackup, 64, "FIRM restore..."); snprintf(msgBackup, 64, "FIRM restore...");
statusBackup = STATUS_YELLOW; statusBackup = STATUS_YELLOW;
ShowInstallerStatus(); 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"); ShowProgress(0, 0, "FIRM restore");
for (u32 pos = 0; (pos < FIRM_NAND_SIZE) && (ret == 0); pos += WORK_BUFFER_SIZE) { for (u32 i = 0; i < n_firms; i++) {
UINT bytes = min(WORK_BUFFER_SIZE, FIRM_NAND_SIZE - pos); NandPartitionInfo np_info;
snprintf(msgBackup, 64, "FIRM restore (%luMB/%luMB)", ret = GetNandPartitionInfo(&np_info, NP_TYPE_FIRM, NP_SUBTYPE_CTR, i);
pos / (1024*1024), (u32) FIRM_NAND_SIZE / (1024 * 1024)); 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(); ShowInstallerStatus();
if ((f_read(&fp, WORK_BUFFER, bytes, &bt) != FR_OK) || (bt != bytes) || if ((f_qread(bakname, WORK_BUFFER, 0, fsize, &bt) != FR_OK) || (bt != fsize) ||
(WriteNandBytes(WORK_BUFFER, FIRM_NAND_OFFSET + pos, bytes, 0xFF) != 0)) (WriteNandBytes(WORK_BUFFER, foffset, fsize, 0xFF) != 0)) {
ret = 1; ret = 1;
ShowProgress(pos + bytes, FIRM_NAND_SIZE, "FIRM restore"); break;
}
ShowProgress(i + 1, n_firms, "FIRM restore");
} }
f_close(&fp);
if (ret != 0) { if (ret != 0) {
snprintf(msgBackup, 64, "FIRM restore fail"); snprintf(msgBackup, 64, "FIRM restore fail");
statusBackup = STATUS_RED; statusBackup = STATUS_RED;
@ -326,7 +339,7 @@ u32 SafeB9SInstaller(void) {
ShowInstallerStatus(); ShowInstallerStatus();
u8 sector_backup[0x200]; u8 sector_backup[0x200];
if ((f_qread(NAME_SECTORBACKUP, sector_backup, 0, 0x200, &bt) != FR_OK) || (bt != 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"); snprintf(msgBackup, 64, "0x96 restore fail");
statusBackup = STATUS_RED; statusBackup = STATUS_RED;
return 1; return 1;

View File

@ -2,14 +2,35 @@
#include "keydb.h" #include "keydb.h"
#include "aes.h" #include "aes.h"
#include "sha.h" #include "sha.h"
#include "fatmbr.h"
#include "unittype.h"
#include "sdmmc.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 u8 slot0x05KeyY[0x10] = { 0x00 }; // need to load this from FIRM0 / external file
static const u8 slot0x05KeyY_sha256[0x20] = { // hash for slot0x05KeyY (16 byte) 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, 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 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) 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, 0xBA, 0xC1, 0x40, 0x9C, 0x6E, 0xE4, 0x1F, 0x04, 0xAA, 0xC4, 0xE2, 0x09, 0x5C, 0xE9, 0x4F, 0x78,
@ -21,32 +42,6 @@ static const u8 slot0x11Key95dev_sha256[0x20] = { // slot0x11Key95 hash (first 1
0xD7, 0x6C, 0xE9, 0xAD, 0xE7, 0xFE, 0x9A, 0x25, 0x4E, 0x4A, 0x0C, 0x82, 0x67, 0xB5, 0x4A, 0x7B 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 CtrNandCtr[16];
static u8 TwlNandCtr[16]; static u8 TwlNandCtr[16];
static u8 OtpSha256[32] = { 0 }; static u8 OtpSha256[32] = { 0 };
@ -54,23 +49,24 @@ static u8 OtpSha256[32] = { 0 };
u32 LoadKeyYFromP9(u8* key, const u8* keyhash, u32 offset, u32 keyslot) 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 ctr0x15[16] __attribute__((aligned(32)));
u8 keyY0x15[16] __attribute__((aligned(32))); u8 keyY0x15[16] __attribute__((aligned(32)));
u8 keyY[16] __attribute__((aligned(32))); u8 keyY[16] __attribute__((aligned(32)));
u8 header[0x200]; u8 header[0x200];
// check arm9loaderhax // 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 // section 2 (arm9loader) header of FIRM
// this is @0x066A00 in FIRM90 & FIRM81 // 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(keyY0x15, header + 0x10, 0x10); // 0x15 keyY
memcpy(ctr0x15, header + 0x20, 0x10); // 0x15 counter memcpy(ctr0x15, header + 0x20, 0x10); // 0x15 counter
// read and decrypt the encrypted keyY // 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); setup_aeskeyY(0x15, keyY0x15);
use_aeskey(0x15); use_aeskey(0x15);
ctr_decrypt_byte(keyY, keyY, 0x10, offset - (offsetA9l + 0x800), AES_CNT_CTRNAND_MODE, ctr0x15); ctr_decrypt_byte(keyY, keyY, 0x10, offset - (offsetA9l + 0x800), AES_CNT_CTRNAND_MODE, ctr0x15);
@ -92,23 +88,27 @@ bool InitNandCrypto(void)
{ {
// part #0: KeyX / KeyY for secret sector 0x96 // part #0: KeyX / KeyY for secret sector 0x96
// on a9lh this MUST be run before accessing the SHA register in any other way // 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 // store the current SHA256 from register
memcpy(OtpSha256, (void*) REG_SHAHASH, 32); memcpy(OtpSha256, (void*) REG_SHAHASH, 32);
if (!CheckSector0x96Crypto()) { // 3dssafe users be damned (no offense meant to mashers) }
if (!CheckSector0x96Crypto()) { // if all else fails...
u8 __attribute__((aligned(32))) otp0x90[0x90]; u8 __attribute__((aligned(32))) otp0x90[0x90];
u8 __attribute__((aligned(32))) otp_key[0x10]; u8 __attribute__((aligned(32))) otp_key[0x10];
u8 __attribute__((aligned(32))) otp_iv[0x10]; u8 __attribute__((aligned(32))) otp_iv[0x10];
memcpy(otp0x90, (u8*) 0x01FFB800, 0x90); memcpy(otp0x90, (u8*) 0x01FFB800, 0x90);
if ((LoadKeyFromFile(otp_key, 0x11, 'N', "OTP") == 0) && if ((LoadKeyFromFile(otp_key, 0x11, 'N', "OTP") == 0) &&
(LoadKeyFromFile(otp_iv, 0x11, 'I', "IVOTP") == 0)) { ((LoadKeyFromFile(otp_iv, 0x11, 'I', "IVOTP") == 0) ||
(LoadKeyFromFile(otp_iv, 0x11, 'I', "OTP") == 0))) {
setup_aeskey(0x11, otp_key); setup_aeskey(0x11, otp_key);
use_aeskey(0x11); use_aeskey(0x11);
cbc_encrypt(otp0x90, otp0x90, 0x90 / 0x10, AES_CNT_TITLEKEY_ENCRYPT_MODE, otp_iv); cbc_encrypt(otp0x90, otp0x90, 0x90 / 0x10, AES_CNT_TITLEKEY_ENCRYPT_MODE, otp_iv);
sha_quick(OtpSha256, otp0x90, 0x90, SHA256_MODE); sha_quick(OtpSha256, otp0x90, 0x90, SHA256_MODE);
} }
} }
}
// part #1: Get NAND CID, set up TWL/CTR counter // part #1: Get NAND CID, set up TWL/CTR counter
u32 NandCid[4]; u32 NandCid[4];
@ -124,7 +124,7 @@ bool InitNandCrypto(void)
// part #2: TWL KEY // part #2: TWL KEY
// see: https://www.3dbrew.org/wiki/Memory_layout#ARM9_ITCM // 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); u32* TwlCustId = (u32*) (0x01FFB808);
u8 TwlKeyX[16] __attribute__((aligned(32))); u8 TwlKeyX[16] __attribute__((aligned(32)));
u8 TwlKeyY[16] __attribute__((aligned(32))); u8 TwlKeyY[16] __attribute__((aligned(32)));
@ -148,14 +148,21 @@ bool InitNandCrypto(void)
use_aeskey(0x03); use_aeskey(0x03);
} }
// part #3: CTRNAND N3DS KEY // part #3: CTRNAND N3DS KEY / AGBSAVE CMAC KEY
// thanks AuroraWright and Gelex for advice on this // thanks AuroraWright and Gelex for advice on this
// see: https://github.com/AuroraWright/Luma3DS/blob/master/source/crypto.c#L347 // 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 @0x0EB014 in the FIRM90
// keyY 0x05 is encrypted @0x0EB24C in the FIRM81 // keyY 0x05 is encrypted @0x0EB24C in the FIRM81
if ((LoadKeyYFromP9(slot0x05KeyY, slot0x05KeyY_sha256, 0x0EB014, 0x05) != 0) && 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; return true;
@ -164,17 +171,11 @@ bool InitNandCrypto(void)
bool CheckSlot0x05Crypto(void) bool CheckSlot0x05Crypto(void)
{ {
// step #1 - check the slot0x05KeyY SHA-256 // step #1 - check the slot0x05KeyY SHA-256
u8 shasum[32]; if (sha_cmp(slot0x05KeyY_sha256, slot0x05KeyY, 16, SHA256_MODE) == 0)
sha_quick(shasum, slot0x05KeyY, 16, SHA256_MODE);
if (memcmp(shasum, slot0x05KeyY_sha256, 32) == 0)
return true; return true;
// step #2 - check actual CTRNAND magic // step #2 - check actual presence of CTRNAND FAT
const u8 magic[8] = {0xE9, 0x00, 0x00, 0x43, 0x54, 0x52, 0x20, 0x20}; if (GetNandPartitionInfo(NULL, NP_TYPE_STD, NP_SUBTYPE_CTR_N, 0) == 0)
const u32 sector = 0x05CAD7;
u8 buffer[0x200];
ReadNandSectors(buffer, sector, 1, 0x05);
if (memcmp(buffer, magic, 8) == 0)
return true; return true;
// failed if we arrive here // failed if we arrive here
@ -184,19 +185,20 @@ bool CheckSlot0x05Crypto(void)
bool CheckSector0x96Crypto(void) bool CheckSector0x96Crypto(void)
{ {
u8 buffer[0x200]; u8 buffer[0x200];
ReadNandSectors(buffer, 0x96, 1, 0x11); ReadNandSectors(buffer, SECTOR_SECRET, 1, 0x11);
return (sha_cmp((IS_DEVKIT) ? slot0x11Key95dev_sha256 : slot0x11Key95_sha256, buffer, 16, SHA256_MODE) == 0); return (sha_cmp(KEY95_SHA256, buffer, 16, SHA256_MODE) == 0);
} }
bool CheckFirmCrypto(void) bool CheckFirmCrypto(void)
{ {
// check the FIRM magic // check the FIRM magic
const u8 magic[8] = {'F', 'I', 'R', 'M'}; const u8 magic[8] = {'F', 'I', 'R', 'M'};
const u32 sectors[] = { SECTOR_FIRM0, SECTOR_FIRM1 }; for (u32 i = 0; i < 8; i++) {
NandPartitionInfo np_info;
u8 buffer[0x200]; u8 buffer[0x200];
for (u32 i = 0; i < sizeof(sectors) / sizeof(u32); i++) { if ((GetNandPartitionInfo(&np_info, NP_TYPE_FIRM, NP_SUBTYPE_CTR, i) != 0) ||
ReadNandSectors(buffer, sectors[i], 1, 0x06); (ReadNandSectors(buffer, np_info.sector, 1, np_info.keyslot) != 0)) break;
if (memcmp(buffer, magic, sizeof(magic)) == 0) return true; if (memcmp(buffer, magic, sizeof(magic)) == 0) return true; // success
} }
// failed if we arrive here // failed if we arrive here
@ -205,17 +207,17 @@ bool CheckFirmCrypto(void)
void CryptNand(void* buffer, u32 sector, u32 count, u32 keyslot) 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))); u8 ctr[16] __attribute__((aligned(32)));
u32 blocks = count * (0x200 / 0x10); u32 blocks = count * (0x200 / 0x10);
// copy NAND CTR and increment it // 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)); add_ctr(ctr, sector * (0x200 / 0x10));
// decrypt the data // decrypt the data
use_aeskey(keyslot); 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) void CryptSector0x96(void* buffer, bool encrypt)
@ -228,16 +230,16 @@ void CryptSector0x96(void* buffer, bool encrypt)
// decrypt the sector // decrypt the sector
use_aeskey(0x11); 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 if (!(offset % 0x200) && !(count % 0x200)) { // aligned data -> simple case
// simple wrapper function for ReadNandSectors(...) // simple wrapper function for ReadNandSectors(...)
return ReadNandSectors(buffer8, offset / 0x200, count / 0x200, keyslot); return ReadNandSectors(buffer, offset / 0x200, count / 0x200, keyslot);
} else { // misaligned data -> -___- } else { // misaligned data -> -___-
u8* buffer8 = (u8*) buffer;
u8 l_buffer[0x200]; u8 l_buffer[0x200];
int errorcode = 0; int errorcode = 0;
if (offset % 0x200) { // handle misaligned offset 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 if (!(offset % 0x200) && !(count % 0x200)) { // aligned data -> simple case
// simple wrapper function for WriteNandSectors(...) // simple wrapper function for WriteNandSectors(...)
return WriteNandSectors(buffer8, offset / 0x200, count / 0x200, keyslot); return WriteNandSectors(buffer, offset / 0x200, count / 0x200, keyslot);
} else { // misaligned data -> -___- } else { // misaligned data -> -___-
u8* buffer8 = (u8*) buffer8;
u8 l_buffer[0x200]; u8 l_buffer[0x200];
int errorcode = 0; int errorcode = 0;
if (offset % 0x200) { // handle misaligned offset if (offset % 0x200) { // handle misaligned offset
@ -303,23 +305,24 @@ int WriteNandBytes(const void* buffer, u32 offset, u32 count, u32 keyslot)
int ReadNandSectors(void* buffer, u32 sector, 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 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 (errorcode) return errorcode;
if ((keyslot == 0x11) && (sector == 0x96)) CryptSector0x96(buffer, false); if ((keyslot == 0x11) && (sector == SECTOR_SECRET)) CryptSector0x96(buffer8, false);
else if (keyslot < 0x40) CryptNand(buffer, sector, count, keyslot); else if (keyslot < 0x40) CryptNand(buffer8, sector, count, keyslot);
return 0; return 0;
} }
int WriteNandSectors(const void* buffer, u32 sector, u32 count, u32 keyslot) 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 // 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)) { for (u32 s = 0; s < count; s += (NAND_BUFFER_SIZE / 0x200)) {
u32 pcount = min((NAND_BUFFER_SIZE/0x200), (count - s)); u32 pcount = min((NAND_BUFFER_SIZE/0x200), (count - s));
memcpy(NAND_BUFFER, buffer8 + (s*0x200), pcount * 0x200); 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); else if (keyslot < 0x40) CryptNand(NAND_BUFFER, sector + s, pcount, keyslot);
int errorcode = sdmmc_nand_writesectors(sector + s, pcount, NAND_BUFFER); int errorcode = sdmmc_nand_writesectors(sector + s, pcount, NAND_BUFFER);
if (errorcode) return errorcode; if (errorcode) return errorcode;
@ -328,43 +331,132 @@ int WriteNandSectors(const void* buffer, u32 sector, u32 count, u32 keyslot)
return 0; 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 zeroes[16] = { 0 };
/*u8 header_dec[0x200]; if ((memcmp(header->magic, "NCSD", 4) != 0) || // check magic number
memcpy(header_dec, header, 0x200); (memcmp(header->partitions_fs_type, zeroes, 8) == 0) || header->mediaId) // prevent detection of cart NCSD images
CryptNand(header_dec, 0, 1, 0x03); return 1;
if (memcmp(header_dec + 0x1BE, twl_mbr, sizeof(twl_mbr)) != 0)
return 0; // header does not belong to console */
// header type check u32 data_units = 0;
u8* header_enc = header; u32 firm_count = 0;
if (memcmp(header_enc + 0x100, nand_magic_n3ds, sizeof(nand_magic_n3ds) == 0) == 0) for (u32 i = 0; i < 8; i++) {
return (IS_O3DS) ? 0 : NAND_TYPE_N3DS; NandNcsdPartition* partition = header->partitions + i;
else if (memcmp(header_enc + 0x100, nand_magic_o3ds, sizeof(nand_magic_o3ds) == 0) == 0) u8 np_type = header->partitions_fs_type[i];
return NAND_TYPE_O3DS; 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; return 0;
} }
u32 CheckNandType(void) u32 GetNandNcsdMinSizeSectors(NandNcsdHeader* ncsd)
{ {
if (ReadNandSectors(NAND_BUFFER, 0, 1, 0xFF) != 0) u32 nand_minsize = 0;
return 0; for (u32 prt_idx = 0; prt_idx < 8; prt_idx++) {
if (memcmp(NAND_BUFFER + 0x100, nand_magic_n3ds, 0x60) == 0) { u32 prt_end = ncsd->partitions[prt_idx].offset + ncsd->partitions[prt_idx].size;
return (IS_O3DS) ? 0 : NAND_TYPE_N3DS; if (prt_end > nand_minsize) nand_minsize = prt_end;
} 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;
} }
return 0; return nand_minsize;
} }
u64 GetNandSizeSectors(void) 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; 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 GetNandPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 index)
{
// 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;
}

View File

@ -1,39 +1,59 @@
#pragma once #pragma once
#include "common.h" #include "common.h"
#include "unittype.h"
#define NAND_MIN_SECTORS ((!IS_O3DS) ? NAND_MIN_SECTORS_N3DS : NAND_MIN_SECTORS_O3DS) // hardcoded start sectors
#define SECTOR_D0K3 0x000001
#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
#define SECTOR_SECRET 0x000096 #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) // filenames for sector 0x96
#define SIZE_TWL 0x058800 #define SECTOR_NAME "sector0x96.bin"
#define SIZE_TWLN 0x047DA9 #define SECRET_NAME "secret_sector.bin"
#define SIZE_TWLP 0x0105B3
#define SIZE_AGBSAVE 0x000180 // 0x110...0x118 in the NAND NCSD header
#define SIZE_FIRM0 0x002000 // see: https://www.3dbrew.org/wiki/NCSD#NCSD_header
#define SIZE_FIRM1 0x002000 #define NP_TYPE_NONE 0
#define SIZE_CTR_O3DS 0x17AE80 #define NP_TYPE_STD 1
#define SIZE_CTR_N3DS 0x20F680 #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 InitNandCrypto(void);
bool CheckSlot0x05Crypto(void); bool CheckSlot0x05Crypto(void);
@ -42,11 +62,14 @@ bool CheckFirmCrypto(void);
void CryptNand(void* buffer, u32 sector, u32 count, u32 keyslot); void CryptNand(void* buffer, u32 sector, u32 count, u32 keyslot);
void CryptSector0x96(void* buffer, bool encrypt); void CryptSector0x96(void* buffer, bool encrypt);
int ReadNandBytes(void* buffer, u32 offset, u32 count, u32 keyslot); int ReadNandBytes(void* buffer, u64 offset, u64 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);
int ReadNandSectors(void* buffer, u32 sector, u32 count, u32 keyslot); int ReadNandSectors(void* buffer, u32 sector, u32 count, u32 keyslot);
int WriteNandSectors(const void* buffer, u32 sector, u32 count, u32 keyslot); int WriteNandSectors(const void* buffer, u32 sector, u32 count, u32 keyslot);
u64 GetNandSizeSectors(void); u32 ValidateNandNcsdHeader(NandNcsdHeader* header);
u32 CheckNandHeader(void* header); u32 GetNandNcsdMinSizeSectors(NandNcsdHeader* ncsd);
u32 CheckNandType(void); 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);

View File

@ -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; 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: // safe NAND writer function, warnings:
// (1) contents of buffer may change on corruption // (1) contents of buffer may change on corruption
// (2) uses SHA register // (2) uses SHA register

View File

@ -4,4 +4,5 @@
#include "ff.h" #include "ff.h"
u32 SafeWriteFile(FIL* file, void* buff, FSIZE_t ofs, UINT btw); 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); u32 SafeWriteNand(void* buff, u32 ofs, u32 btw, u32 keyslot);