diff --git a/source/fs/filetype.c b/source/fs/filetype.c index 0117f29..902d87d 100644 --- a/source/fs/filetype.c +++ b/source/fs/filetype.c @@ -4,6 +4,7 @@ #include "nand.h" #include "game.h" #include "keydb.h" +#include "ctrtransfer.h" #include "chainload.h" u32 IdentifyFileType(const char* path) { @@ -30,7 +31,7 @@ u32 IdentifyFileType(const char* path) { } else if (ValidateMbrHeader((MbrHeader*) data) == 0) { MbrHeader* mbr = (MbrHeader*) data; MbrPartitionInfo* partition0 = mbr->partitions; - bool ctr = (CheckNandMbr(data) & (NAND_TYPE_O3DS|NAND_TYPE_N3DS)); // is this a CTRNAND MBR? + bool ctr = (CheckTransferableMbr(mbr) == 0); // is this a CTRNAND MBR? if ((partition0->sector + partition0->count) <= (fsize / 0x200)) // size check return IMG_FAT | (ctr ? FLAG_CTR : 0); // possibly an MBR -> also treat as FAT image } else if (ValidateCiaHeader((CiaHeader*) data) == 0) { diff --git a/source/game/firm.c b/source/game/firm.c index 48f5702..b547668 100644 --- a/source/game/firm.c +++ b/source/game/firm.c @@ -14,15 +14,25 @@ u32 ValidateFirmHeader(FirmHeader* header, u32 data_size) { return 1; u32 firm_size = sizeof(FirmHeader); + int section_arm11 = -1; + int section_arm9 = -1; for (u32 i = 0; i < 4; i++) { FirmSectionHeader* section = header->sections + i; if (!section->size) continue; if (section->offset < firm_size) return 1; + if ((header->entry_arm11 >= section->address) && + (header->entry_arm11 < section->address + section->size)) + section_arm11 = i; + if ((header->entry_arm9 >= section->address) && + (header->entry_arm9 < section->address + section->size)) + section_arm9 = i; firm_size = section->offset + section->size; } if ((firm_size > FIRM_MAX_SIZE) || (data_size && (firm_size > data_size))) return 1; + if ((header->entry_arm11 && (section_arm11 < 0)) || (header->entry_arm9 && (section_arm9 < 0))) + return 1; return 0; } diff --git a/source/game/gameutil.c b/source/game/gameutil.c index a4844a0..0a3ff5d 100644 --- a/source/game/gameutil.c +++ b/source/game/gameutil.c @@ -525,23 +525,12 @@ u32 VerifyFirmFile(const char* path) { } fvx_close(&file); - // check arm11 / arm9 entrypoints - int section_arm11 = -1; - int section_arm9 = -1; - for (u32 i = 0; i < 4; i++) { - FirmSectionHeader* section = header.sections + i; - if ((header.entry_arm11 >= section->address) && - (header.entry_arm11 < section->address + section->size)) - section_arm11 = i; - if ((header.entry_arm9 >= section->address) && - (header.entry_arm9 < section->address + section->size)) - section_arm9 = i; - } - - // sections for arm11 / arm9 entrypoints not found? - if ((section_arm11 < 0) || (section_arm9 < 0)) { - ShowPrompt(false, "%s\nARM11/ARM9 entrypoint not found", pathstr); + // no arm11 / arm9 entrypoints? + if (!header.entry_arm9) { + ShowPrompt(false, "%s\nARM9 entrypoint is missing", pathstr); return 1; + } else if (!header.entry_arm11) { + ShowPrompt(false, "%s\nWarning: ARM11 entrypoint is missing", pathstr); } return 0; diff --git a/source/game/ncsd.c b/source/game/ncsd.c index b553fda..4ceaa0c 100644 --- a/source/game/ncsd.c +++ b/source/game/ncsd.c @@ -4,7 +4,7 @@ u32 ValidateNcsdHeader(NcsdHeader* header) { u8 zeroes[16] = { 0 }; if ((memcmp(header->magic, "NCSD", 4) != 0) || // check magic number - (memcmp(header->partitions_fs_type, zeroes, 8) != 0)) // prevent detection of NAND images + (memcmp(header->partitions_fs_type, zeroes, 8) != 0) || !header->mediaId) // prevent detection of NAND images return 1; u32 data_units = 0; diff --git a/source/godmode.c b/source/godmode.c index e8618af..7fd50cd 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -1199,15 +1199,18 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur } u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboard) { + NandPartitionInfo np_info; + if (GetNandPartitionInfo(&np_info, NP_TYPE_BONUS, NP_SUBTYPE_CTR, 0, NAND_SYSNAND) != 0) np_info.count = 0; + const char* optionstr[8]; const char* promptstr = "HOME more... menu.\nSelect action:"; u32 n_opt = 0; + int nandbak = ++n_opt; int sdformat = ++n_opt; - int bonus = (GetNandUnusedSectors(NAND_SYSNAND) > 0x2000) ? (int) ++n_opt : -1; // 4MB minsize + int bonus = (np_info.count > 0x2000) ? (int) ++n_opt : -1; // 4MB minsize int multi = (CheckMultiEmuNand()) ? (int) ++n_opt : -1; int bsupport = ++n_opt; int hsrestore = ((CheckHealthAndSafetyInject("1:") == 0) || (CheckHealthAndSafetyInject("4:") == 0)) ? (int) ++n_opt : -1; - int nandbak = ++n_opt; if (nandbak > 0) optionstr[nandbak - 1] = "Backup NAND"; if (sdformat > 0) optionstr[sdformat - 1] = "SD format menu"; diff --git a/source/nand/ctrtransfer.c b/source/nand/ctrtransfer.c index 99e5239..021d76b 100644 --- a/source/nand/ctrtransfer.c +++ b/source/nand/ctrtransfer.c @@ -9,6 +9,40 @@ #include "ui.h" +/*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 const u8 ctr_mbr_o3ds[0x42] = { // found at the beginning of the CTRNAND partition (O3DS) + 0x00, 0x05, 0x2B, 0x00, 0x06, 0x02, 0x42, 0x80, 0x65, 0x01, 0x00, 0x00, 0x1B, 0x9F, 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, 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 const u8 ctr_mbr_n3ds[0x42] = { // found at the beginning of the CTRNAND partition (N3DS) + 0x00, 0x05, 0x1D, 0x00, 0x06, 0x02, 0x82, 0x17, 0x57, 0x01, 0x00, 0x00, 0x69, 0xE9, 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, 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 +}; + + +u32 CheckTransferableMbr(void* data) { // strict checks, custom partitions not allowed + u8* mbr = (u8*) data; + if (memcmp(mbr + (0x200 - sizeof(ctr_mbr_o3ds)), ctr_mbr_o3ds, sizeof(ctr_mbr_o3ds)) == 0) + return 0; // is transferable, O3DS type + else if (memcmp(mbr + (0x200 - sizeof(ctr_mbr_n3ds)), ctr_mbr_n3ds, sizeof(ctr_mbr_n3ds)) == 0) + return 0; // is transferable, N3DS type + return 1; +} + u32 TransferCtrNandImage(const char* path_img, const char* drv) { if (!CheckWritePermissions(drv)) return 1; diff --git a/source/nand/ctrtransfer.h b/source/nand/ctrtransfer.h index 025dfbc..6cac75b 100644 --- a/source/nand/ctrtransfer.h +++ b/source/nand/ctrtransfer.h @@ -2,4 +2,5 @@ #include "common.h" +u32 CheckTransferableMbr(void* data); u32 TransferCtrNandImage(const char* path_img, const char* drv); diff --git a/source/nand/nand.c b/source/nand/nand.c index 32cf5b4..5198b77 100644 --- a/source/nand/nand.c +++ b/source/nand/nand.c @@ -9,7 +9,6 @@ #include "sdmmc.h" #include "image.h" -#define NAND_MIN_SECTORS ((!IS_O3DS) ? NAND_MIN_SECTORS_N3DS : NAND_MIN_SECTORS_O3DS) #define KEY95_SHA256 ((IS_DEVKIT) ? slot0x11Key95dev_sha256 : slot0x11Key95_sha256) #define SECTOR_SHA256 ((IS_DEVKIT) ? sector0x96dev_sha256 : sector0x96_sha256) @@ -58,48 +57,6 @@ static const u8 sector0x96dev_sha256[0x20] = { // hash for legit sector 0x96 (di 0xB2, 0x91, 0xD9, 0xB1, 0x33, 0x05, 0x79, 0x0D, 0x47, 0xC6, 0x06, 0x98, 0x4C, 0x67, 0xC3, 0x70, 0x09, 0x54, 0xE3, 0x85, 0xDE, 0x47, 0x55, 0xAF, 0xC6, 0xCB, 0x1D, 0x8D, 0xC7, 0x84, 0x5A, 0x64 }; - -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 const u8 ctr_mbr_o3ds[0x42] = { // found at the beginning of the CTRNAND partition (O3DS) - 0x00, 0x05, 0x2B, 0x00, 0x06, 0x02, 0x42, 0x80, 0x65, 0x01, 0x00, 0x00, 0x1B, 0x9F, 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, 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 const u8 ctr_mbr_n3ds[0x42] = { // found at the beginning of the CTRNAND partition (N3DS) - 0x00, 0x05, 0x1D, 0x00, 0x06, 0x02, 0x82, 0x17, 0x57, 0x01, 0x00, 0x00, 0x69, 0xE9, 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, 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]; @@ -407,9 +364,37 @@ int WriteNandSectors(const u8* buffer, u32 sector, u32 count, u32 keyslot, u32 n return 0; } -u32 GetNandNcsdMinSizeSectors(NandNcsdHeader* ncsd) // in sectors +// shamelessly stolen from myself +// see: https://github.com/d0k3/GodMode9/blob/master/source/game/ncsd.c#L4 +u32 ValidateNandNcsdHeader(NandNcsdHeader* header) { - u32 nand_minsize = 1; + 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; + + 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; @@ -418,6 +403,35 @@ u32 GetNandNcsdMinSizeSectors(NandNcsdHeader* ncsd) // in sectors return nand_minsize; } +u32 GetNandMinSizeSectors(u32 nand_src) +{ + NandNcsdHeader ncsd; + if ((ReadNandSectors((u8*) &ncsd, 0, 1, 0xFF, nand_src) != 0) || + (ValidateNandNcsdHeader(&ncsd) != 0)) return 0; + + return GetNandNcsdMinSizeSectors(&ncsd); +} + +u32 GetNandSizeSectors(u32 nand_src) +{ + u32 sysnand_sectors = getMMCDevice(0)->total_size; + if (nand_src == NAND_SYSNAND) return sysnand_sectors; // for SysNAND + + u32 min_sectors = GetNandMinSizeSectors(nand_src); + if (nand_src == NAND_EMUNAND) { // for EmuNAND + u32 partition_offset = GetPartitionOffsetSector("0:"); + u32 emunand_max_sectors = (partition_offset >= (emunand_base_sector + 1)) ? // +1 for safety + partition_offset - (emunand_base_sector + 1) : 0; + u32 emunand_min_sectors = (emunand_base_sector % 0x2000 == 0) ? sysnand_sectors : min_sectors; + return (emunand_min_sectors > emunand_max_sectors) ? 0 : emunand_min_sectors; + } else if (nand_src == NAND_IMGNAND) { // for images + u32 img_sectors = (GetMountState() & IMG_NAND) ? GetMountSize() / 0x200 : 0; + return (img_sectors >= min_sectors) ? img_sectors : 0; + } + + return 0; +} + u32 GetNandNcsdPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 index, NandNcsdHeader* ncsd) { // safety / set keyslot @@ -466,7 +480,10 @@ u32 GetNandNcsdPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 u32 GetNandPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 index, u32 nand_src) { - // check NAND NCSD integrity(!!!) + // workaround for info == NULL + NandPartitionInfo dummy; + if (!info) info = &dummy; + // workaround for ZERONAND if (nand_src == NAND_ZERONAND) nand_src = NAND_SYSNAND; @@ -474,12 +491,12 @@ u32 GetNandPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 ind u8 header[0x200]; ReadNandSectors(header, 0x00, 1, 0xFF, nand_src); NandNcsdHeader* ncsd = (NandNcsdHeader*) header; - if (((type == NP_TYPE_FAT) && (GetNandNcsdPartitionInfo(info, NP_TYPE_STD, subtype, 0, ncsd) != 0)) || + 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 - // size of bonus partition - if (type == NP_TYPE_BONUS) { + if (type == NP_TYPE_BONUS) { // size of bonus partition info->count = GetNandSizeSectors(nand_src) - info->sector; } else if (type == NP_TYPE_FAT) { // FAT type specific stuff ReadNandSectors(header, info->sector, 1, info->keyslot, nand_src); @@ -493,76 +510,6 @@ u32 GetNandPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 ind } return 0; -} - -u32 CheckNandMbr(u8* mbr) -{ - if (memcmp(mbr + (0x200 - sizeof(twl_mbr)), twl_mbr, sizeof(twl_mbr)) == 0) - return NAND_TYPE_TWL; // TWLNAND MBR (included in NAND header) - else if (memcmp(mbr + (0x200 - sizeof(ctr_mbr_o3ds)), ctr_mbr_o3ds, sizeof(ctr_mbr_o3ds)) == 0) - return NAND_TYPE_O3DS; // CTRNAND MBR (@0x05C980) - else if (memcmp(mbr + (0x200 - sizeof(ctr_mbr_n3ds)), ctr_mbr_n3ds, sizeof(ctr_mbr_n3ds)) == 0) - return NAND_TYPE_N3DS; // CTRNAND MBR (@0x05C980) - else return 0; -} - -u32 CheckNandHeader(u8* header) -{ - // TWL MBR check - u8 header_dec[0x200]; - memcpy(header_dec, header, 0x200); - CryptNand(header_dec, 0, 1, 0x03); - if (CheckNandMbr(header_dec) != NAND_TYPE_TWL) - return 0; // header does not belong to console - - // header type check - if (memcmp(header + 0x100, nand_magic_n3ds, sizeof(nand_magic_n3ds)) == 0) - return (IS_O3DS) ? 0 : NAND_TYPE_N3DS; - else if (memcmp(header + 0x100, nand_magic_o3ds, sizeof(nand_magic_o3ds)) == 0) - return NAND_TYPE_O3DS; - - return 0; -} - -u32 CheckNandType(u32 nand_src) -{ - if (ReadNandSectors(NAND_BUFFER, 0, 1, 0xFF, nand_src) != 0) - return 0; - if (memcmp(NAND_BUFFER + 0x100, nand_magic_n3ds, sizeof(nand_magic_n3ds)) == 0) { - return (IS_O3DS) ? 0 : NAND_TYPE_N3DS; - } else if (memcmp(NAND_BUFFER + 0x100, nand_magic_o3ds, sizeof(nand_magic_o3ds)) == 0) { - u8 magic[8] = {0xE9, 0x00, 0x00, 0x43, 0x54, 0x52, 0x20, 0x20}; - if (ReadNandSectors(NAND_BUFFER, 0x5CAE5, 1, 0x04, nand_src) != 0) - return 0; - return ((IS_O3DS) || (memcmp(magic, NAND_BUFFER, 8) == 0)) ? - NAND_TYPE_O3DS : NAND_TYPE_NO3DS; - } - - return 0; -} - -u64 GetNandSizeSectors(u32 nand_src) -{ - u32 sysnand_sectors = getMMCDevice(0)->total_size; - if (nand_src == NAND_EMUNAND) { // for EmuNAND - u32 partition_offset = GetPartitionOffsetSector("0:"); - u32 emunand_max_sectors = (partition_offset >= (emunand_base_sector + 1)) ? // +1 for safety - partition_offset - (emunand_base_sector + 1) : 0; - u32 emunand_min_sectors = (emunand_base_sector % 0x2000 == 0) ? sysnand_sectors : NAND_MIN_SECTORS; - return (emunand_min_sectors > emunand_max_sectors) ? 0 : emunand_min_sectors; - } else if (nand_src == NAND_IMGNAND) { // for images - u32 img_sectors = (GetMountState() & IMG_NAND) ? GetMountSize() / 0x200 : 0; - return (img_sectors >= sysnand_sectors) ? sysnand_sectors : (img_sectors >= NAND_MIN_SECTORS) ? NAND_MIN_SECTORS : 0; - } else if (nand_src == NAND_SYSNAND) { // for SysNAND - return sysnand_sectors; - } - - return 0; -} - -u64 GetNandUnusedSectors(u32 nand_src) -{ - return GetNandSizeSectors(nand_src) - NAND_MIN_SECTORS; } u32 GetLegitSector0x96(u8* sector) @@ -613,34 +560,36 @@ u32 GetNandCid(void* cid) { bool CheckMultiEmuNand(void) { // this only checks for the theoretical possibility - return (GetPartitionOffsetSector("0:") >= (u64) (align(NAND_MIN_SECTORS + 1, 0x2000) * 2)); + u32 emunand_min_sectors = GetNandMinSizeSectors(NAND_EMUNAND); + return (emunand_min_sectors && (GetPartitionOffsetSector("0:") >= (u64) (align(emunand_min_sectors + 1, 0x2000) * 2))); } u32 InitEmuNandBase(bool reset) { if (!reset) { u32 last_valid = emunand_base_sector; + u32 emunand_min_sectors = GetNandMinSizeSectors(NAND_EMUNAND); // legacy type multiNAND u32 legacy_sectors = (getMMCDevice(0)->total_size > 0x200000) ? 0x400000 : 0x200000; emunand_base_sector += legacy_sectors - (emunand_base_sector % legacy_sectors); - if (GetNandSizeSectors(NAND_EMUNAND) && CheckNandType(NAND_EMUNAND)) + if (GetNandSizeSectors(NAND_EMUNAND) && (GetNandPartitionInfo(NULL, NP_TYPE_NCSD, NP_SUBTYPE_CTR, 0, NAND_EMUNAND) == 0)) return emunand_base_sector; // GW type EmuNAND emunand_base_sector++; - if (GetNandSizeSectors(NAND_EMUNAND) && CheckNandType(NAND_EMUNAND)) + if (GetNandSizeSectors(NAND_EMUNAND) && (GetNandPartitionInfo(NULL, NP_TYPE_NCSD, NP_SUBTYPE_CTR, 0, NAND_EMUNAND) == 0)) return emunand_base_sector; // RedNAND type EmuNAND // compact type multiNAND - if (last_valid % 0x2000 <= 1) { - u32 compact_sectors = align(NAND_MIN_SECTORS + 1, 0x2000); + if (emunand_min_sectors && (last_valid % 0x2000 <= 1)) { + u32 compact_sectors = align(emunand_min_sectors + 1, 0x2000); emunand_base_sector = last_valid + compact_sectors; - if (GetNandSizeSectors(NAND_EMUNAND) && CheckNandType(NAND_EMUNAND)) + if (GetNandSizeSectors(NAND_EMUNAND) && (GetNandPartitionInfo(NULL, NP_TYPE_NCSD, NP_SUBTYPE_CTR, 0, NAND_EMUNAND) == 0)) return emunand_base_sector; } } emunand_base_sector = 0x000000; // GW type EmuNAND - if (!CheckNandType(NAND_EMUNAND)) + if (GetNandPartitionInfo(NULL, NP_TYPE_NCSD, NP_SUBTYPE_CTR, 0, NAND_EMUNAND) != 0) emunand_base_sector = 0x000001; // RedNAND type EmuNAND return emunand_base_sector; diff --git a/source/nand/nand.h b/source/nand/nand.h index c5116cd..5961945 100644 --- a/source/nand/nand.h +++ b/source/nand/nand.h @@ -6,35 +6,10 @@ #define NAND_EMUNAND (1UL<<1) #define NAND_IMGNAND (1UL<<2) #define NAND_ZERONAND (1UL<<3) -#define NAND_TYPE_O3DS (1UL<<4) -#define NAND_TYPE_N3DS (1UL<<5) -#define NAND_TYPE_NO3DS (1UL<<6) -#define NAND_TYPE_TWL (1UL<<7) -// 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" @@ -98,14 +73,11 @@ int WriteNandBytes(const u8* buffer, u64 offset, u64 count, u32 keyslot, u32 nan int ReadNandSectors(u8* buffer, u32 sector, u32 count, u32 keyslot, u32 src); int WriteNandSectors(const u8* buffer, u32 sector, u32 count, u32 keyslot, u32 dest); +u32 ValidateNandNcsdHeader(NandNcsdHeader* header); u32 GetNandNcsdMinSizeSectors(NandNcsdHeader* ncsd); -u64 GetNandSizeSectors(u32 src); +u32 GetNandSizeSectors(u32 src); u32 GetNandNcsdPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 index, NandNcsdHeader* ncsd); u32 GetNandPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 index, u32 nand_src); -u64 GetNandUnusedSectors(u32 src); -u32 CheckNandMbr(u8* mbr); -u32 CheckNandHeader(u8* header); -u32 CheckNandType(u32 src); u32 GetLegitSector0x96(u8* sector); u32 GetOtpHash(void* hash); diff --git a/source/nand/nandutil.c b/source/nand/nandutil.c index f7de90f..c2525b5 100644 --- a/source/nand/nandutil.c +++ b/source/nand/nandutil.c @@ -61,11 +61,6 @@ u32 BuildEssentialBackup(const char* path, EssentialBackup* essential) { // mount original file InitImgFS(path_bak); - - // check sizes - stubbed - /*if ((files[0].size != 0x200) || (files[1].size != 0x111) || - ((files[2].size != 0x120) && (files[2].size != 0x140)) || (files[3].size != 0x110)) - return 1;*/ // fill nand cid / otp hash if (GetNandCid(&(essential->nand_cid)) != 0) return 1; @@ -86,7 +81,7 @@ u32 CheckEmbeddedBackup(const char* path) { EssentialBackup* embedded = (EssentialBackup*) (TEMP_BUFFER + sizeof(EssentialBackup)); UINT btr; if ((BuildEssentialBackup(path, essential) != 0) || - (fvx_qread(path, embedded, 0x200, sizeof(EssentialBackup), &btr) != FR_OK) || + (fvx_qread(path, embedded, SECTOR_D0K3 * 0x200, sizeof(EssentialBackup), &btr) != FR_OK) || (memcmp(embedded, essential, sizeof(EssentialBackup)) != 0)) return 1; return 0; @@ -97,20 +92,15 @@ u32 EmbedEssentialBackup(const char* path) { UINT btw; // leaving out the write permissions check here, it's okay if ((BuildEssentialBackup(path, essential) != 0) || - (CheckNandHeader(essential->nand_hdr) == 0) || // 0 -> header not recognized - (fvx_qwrite(path, essential, 0x200, sizeof(EssentialBackup), &btw) != FR_OK) || + (ValidateNandNcsdHeader((NandNcsdHeader*) essential->nand_hdr) != 0) || + (fvx_qwrite(path, essential, SECTOR_D0K3 * 0x200, sizeof(EssentialBackup), &btw) != FR_OK) || (btw != sizeof(EssentialBackup))) return 1; return 0; } u32 ValidateNandDump(const char* path) { - const u32 mbr_sectors[] = { SECTOR_TWL, SECTOR_CTR }; - const u32 firm_sectors[] = { SECTOR_FIRM0, SECTOR_FIRM1 }; - u8 buffer[0x200]; - FirmHeader firm; - MbrHeader mbr; - u32 nand_type; + NandPartitionInfo info; FIL file; // truncated path string @@ -122,25 +112,29 @@ u32 ValidateNandDump(const char* path) { return 1; // check NAND header - if ((ReadNandFile(&file, buffer, 0, 1, 0xFF) != 0) || - ((nand_type = CheckNandHeader(buffer)) == 0)) { // zero means header not recognized - ShowPrompt(false, "%s\nHeader does not belong to device", pathstr); + NandNcsdHeader ncsd; + if ((ReadNandFile(&file, &ncsd, 0, 1, 0xFF) != 0) || (ValidateNandNcsdHeader(&ncsd) != 0)) { + ShowPrompt(false, "%s\nNCSD header is not valid", pathstr); fvx_close(&file); return 1; } // check size - if (fvx_size(&file) < ((nand_type == NAND_TYPE_O3DS) ? NAND_MIN_SECTORS_O3DS : NAND_MIN_SECTORS_N3DS)) { + if (fvx_size(&file) < (GetNandNcsdMinSizeSectors(&ncsd) * 0x200)) { ShowPrompt(false, "%s\nNAND dump misses data", pathstr); fvx_close(&file); return 1; } - // check MBRs (TWL & CTR) - for (u32 i = 0; i < sizeof(mbr_sectors) / sizeof(u32); i++) { - u32 keyslot = (i == 0) ? 0x03 : (nand_type == NAND_TYPE_O3DS) ? 0x04 : 0x05; + // check TWL & CTR FAT partitions + for (u32 i = 0; i < 2; i++) { char* section_type = (i) ? "CTR" : "MBR"; - if ((ReadNandFile(&file, &mbr, mbr_sectors[i], 1, keyslot) != 0) || + if (i == 0) { // check TWL first, then CTR + if (GetNandNcsdPartitionInfo(&info, NP_TYPE_STD, NP_SUBTYPE_TWL, 0, &ncsd) != 0) return 1; + } else if ((GetNandNcsdPartitionInfo(&info, NP_TYPE_STD, NP_SUBTYPE_CTR, 0, &ncsd) != 0) && + (GetNandNcsdPartitionInfo(&info, NP_TYPE_STD, NP_SUBTYPE_CTR_N, 0, &ncsd) != 0)) return 1; + MbrHeader mbr; + if ((ReadNandFile(&file, &mbr, info.sector, 1, info.keyslot) != 0) || (ValidateMbrHeader(&mbr) != 0)) { ShowPrompt(false, "%s\nError: %s MBR is corrupt", pathstr, section_type); fvx_close(&file); @@ -148,9 +142,10 @@ u32 ValidateNandDump(const char* path) { } for (u32 p = 0; p < 4; p++) { u32 p_sector = mbr.partitions[p].sector; + u8 fat[0x200]; if (!p_sector) continue; - if ((ReadNandFile(&file, buffer, mbr_sectors[i] + p_sector, 1, keyslot) != 0) || - (ValidateFatHeader(buffer) != 0)) { + if ((ReadNandFile(&file, fat, info.sector + p_sector, 1, info.keyslot) != 0) || + (ValidateFatHeader(fat) != 0)) { ShowPrompt(false, "%s\nError: %s partition%u is corrupt", pathstr, section_type, p); fvx_close(&file); return 1; @@ -158,56 +153,54 @@ u32 ValidateNandDump(const char* path) { } } - // check FIRMs (FIRM1 must be valid) - for (u32 i = 0; i < sizeof(firm_sectors) / sizeof(u32); i++) { - u32 keyslot = 0x06; - if ((ReadNandFile(&file, &firm, firm_sectors[i], 1, keyslot) != 0) || - (ValidateFirmHeader(&firm, 0) != 0) || - (getbe32(firm.dec_magic) != 0)) { // decrypted firms are not allowed - ShowPrompt(false, "%s\nError: FIRM%u header is corrupt", pathstr, i); + // check FIRMs (at least one FIRM must be valid) + // check all 8 firms, also check if ARM9 & ARM11 entrypoints are available + for (u32 f = 0; f <= 8; f++) { + FirmHeader firm; + if (GetNandNcsdPartitionInfo(&info, NP_TYPE_FIRM, NP_SUBTYPE_CTR, f, &ncsd) != 0) { + ShowPrompt(false, "%s\nNo valid FIRM found", pathstr); fvx_close(&file); return 1; } + if ((ReadNandFile(&file, &firm, info.sector, 1, info.keyslot) != 0) || + (ValidateFirmHeader(&firm, 0) != 0) || (getbe32(firm.dec_magic) != 0) || // decrypted firms are not allowed + (!firm.entry_arm9) || (!firm.entry_arm11)) // arm9 / arm11 entry points must be there + continue; // hash verify all available sections - if (i == 0) continue; // no hash checks for FIRM0 (might be A9LH) - for (u32 s = 0; s < 4; s++) { + u32 s; + for (s = 0; s < 4; s++) { FirmSectionHeader* section = firm.sections + s; - u32 sector = firm_sectors[i] + (section->offset / 0x200); + u32 sector = info.sector + (section->offset / 0x200); u32 count = section->size / 0x200; if (!count) continue; sha_init(SHA256_MODE); // relies on sections being aligned to sectors for (u32 c = 0; c < count; c += MAIN_BUFFER_SIZE / 0x200) { u32 read_sectors = min(MAIN_BUFFER_SIZE / 0x200, (count - c)); - ReadNandFile(&file, MAIN_BUFFER, sector + c, read_sectors, keyslot); + ReadNandFile(&file, MAIN_BUFFER, sector + c, read_sectors, info.keyslot); sha_update(MAIN_BUFFER, read_sectors * 0x200); } u8 hash[0x20]; sha_get(hash); - if (memcmp(hash, section->hash, 0x20) != 0) { - ShowPrompt(false, "%s\nFIRM%u/%u hash mismatch", pathstr, i, s); - fvx_close(&file); - return 1; - } + if (memcmp(hash, section->hash, 0x20) != 0) break; } + if (s >= 4) break; // valid FIRM found } + fvx_close(&file); return 0; } u32 SafeRestoreNandDump(const char* path) { - u32 safe_sectors[] = { SAFE_SECTORS }; - FIL file; - if ((ValidateNandDump(path) != 0) && // NAND dump validation - !ShowPrompt(true, "NAND dump corrupt or not from console.\nStill continue?")) + !ShowPrompt(true, "Error: NAND dump is corrupt.\nStill continue?")) return 1; if (!IS_A9LH) { - ShowPrompt(false, "Error: A9LH/B9S not detected."); + ShowPrompt(false, "Error: B9S/A9LH not detected."); return 1; } - if (!ShowUnlockSequence(5, "!WARNING!\n \nProceeding will overwrite the\nSysNAND with the provided dump.\n \n(A9LH/B9S will be left intact.)")) + if (!ShowUnlockSequence(5, "!WARNING!\n \nProceeding will overwrite the\nSysNAND with the provided dump.\n \n(B9S/A9LH will be left intact.)")) return 1; if (!SetWritePermissions(PERM_SYS_LVL1, true)) return 1; @@ -217,39 +210,81 @@ u32 SafeRestoreNandDump(const char* path) { memset(essential, 0, sizeof(EssentialBackup)); // open file, get size + FIL file; if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) return 1; u32 fsize = fvx_size(&file); - safe_sectors[(sizeof(safe_sectors) / sizeof(u32)) - 1] = fsize / 0x200; + + // get NCSD headers from image and SysNAND + NandNcsdHeader ncsd_loc, ncsd_img; + MbrHeader twl_mbr_img; + if ((ReadNandFile(&file, &ncsd_img, 0, 1, 0xFF) != 0) || + (ReadNandFile(&file, &twl_mbr_img, 0, 1, 0x03) != 0) || + (ReadNandSectors((u8*) &ncsd_loc, 0, 1, 0xFF, NAND_SYSNAND) != 0)) { + fvx_close(&file); + return 1; + } + + // compare NCSD header partitioning + bool header_inject = false; + if (memcmp(&ncsd_loc, &ncsd_img, sizeof(NandNcsdHeader)) != 0) { + for (int p = -1; p <= 8; p++) { + NandPartitionInfo info_loc = { 0 }; + NandPartitionInfo info_img = { 0 }; + u32 idx = (p < 0) ? 0 : p; + u32 type = (p < 0) ? NP_TYPE_SECRET : NP_TYPE_FIRM; + u32 subtype = (p < 0) ? NP_SUBTYPE_CTR_N : NP_SUBTYPE_CTR; + bool np_loc = (GetNandNcsdPartitionInfo(&info_loc, type, subtype, idx, &ncsd_loc) == 0); + bool np_img = (GetNandNcsdPartitionInfo(&info_img, type, subtype, idx, &ncsd_img) == 0); + if (!np_loc && !np_img) { + if (p >= 1) header_inject = true; + break; + } + if ((np_loc != np_img) || (memcmp(&info_loc, &info_img, sizeof(NandPartitionInfo)) != 0)) + break; + } + if (!header_inject || (ValidateNandNcsdHeader(&ncsd_img) != 0) || (ValidateMbrHeader(&twl_mbr_img) != 0)) { + ShowPrompt(false, "Image NCSD corrupt or customized,\nsafe restore is not possible!"); + fvx_close(&file); + return 1; + } + } + + // additional warning for elevated write permissions + if (header_inject) { + if (!ShowPrompt(true, "!WARNING!\n \nNCSD differs between image and local,\nelevated write permissions required\n \nProceed on your own risk?") || + !SetWritePermissions(PERM_SYS_LVL3, true)) { + fvx_close(&file); + return 1; + } + } // main processing loop u32 ret = 0; + u32 sector0 = 1; // start at the sector after NCSD if (!ShowProgress(0, 0, path)) ret = 1; - for (u32 p = 0; p < sizeof(safe_sectors) / sizeof(u32); p += 2) { - u32 sector0 = safe_sectors[p]; - u32 sector1 = safe_sectors[p+1]; - fvx_lseek(&file, sector0 * 0x200); + for (int p = -1; p < 8; p++) { + NandPartitionInfo np_info; + u32 idx = (p < 0) ? 0 : p; + u32 type = (p < 0) ? NP_TYPE_SECRET : NP_TYPE_FIRM; + u32 subtype = (p < 0) ? NP_SUBTYPE_CTR_N : NP_SUBTYPE_CTR; + u32 sector1 = (GetNandNcsdPartitionInfo(&np_info, type, subtype, idx, &ncsd_loc) == 0) ? np_info.sector : fsize / 0x200; for (u32 s = sector0; (s < sector1) && (ret == 0); s += MAIN_BUFFER_SIZE / 0x200) { - UINT btr; u32 count = min(MAIN_BUFFER_SIZE / 0x200, (sector1 - s)); - if (fvx_read(&file, MAIN_BUFFER, count * 0x200, &btr) != FR_OK) ret = 1; + if (ReadNandFile(&file, MAIN_BUFFER, s, count, 0xFF)) ret = 1; if (WriteNandSectors(MAIN_BUFFER, s, count, 0xFF, NAND_SYSNAND)) ret = 1; - if (btr != count * 0x200) ret = 1; if (!ShowProgress(s + count, fsize / 0x200, path)) ret = 1; } - } - if (!IS_O3DS && (CheckNandType(NAND_SYSNAND) != NAND_TYPE_N3DS) && (ret == 0)) { - // NAND header handling / special case, only for downgraded N3DS - u8 header[0x200]; // NAND header - UINT btr; - fvx_lseek(&file, 0); - if ((fvx_read(&file, header, 0x200, &btr) == FR_OK) && - (CheckNandHeader(header) == NAND_TYPE_N3DS) && - (WriteNandSectors(header, 0, 1, 0xFF, NAND_SYSNAND) != 0)) - ret = 1; + if (sector1 == fsize / 0x200) break; // at file end + sector0 = np_info.sector + np_info.count; } fvx_close(&file); + // NCSD header inject, should only be required with 2.1 local NANDs on N3DS + if (header_inject && (ret == 0) && + (WriteNandSectors((u8*) &ncsd_img, 0, 1, 0xFF, NAND_SYSNAND) != 0)) + ret = 1; + // inject essential backup to NAND WriteNandSectors((u8*) essential, ESSENTIAL_SECTOR, (sizeof(EssentialBackup) + 0x1FF) / 0x200, 0xFF, NAND_SYSNAND); diff --git a/source/nand/nandutil.h b/source/nand/nandutil.h index 6c81ae5..0789dd3 100644 --- a/source/nand/nandutil.h +++ b/source/nand/nandutil.h @@ -2,9 +2,6 @@ #include "common.h" -#define SAFE_SECTORS 0x000000 + 0x1, SECTOR_SECRET, SECTOR_SECRET + 0x1, \ - SECTOR_FIRM0, SECTOR_CTR, 0x000000 // last one is a placeholder - u32 CheckEmbeddedBackup(const char* path); u32 EmbedEssentialBackup(const char* path); u32 ValidateNandDump(const char* path);