From 9e40ce3e86e7570d00a8520a30f9abab3f2ab593 Mon Sep 17 00:00:00 2001 From: d0k3 Date: Mon, 2 Jan 2017 17:37:08 +0100 Subject: [PATCH] Allow NAND safe restore and validation --- source/fs/fatmbr.c | 32 +++++++++ source/fs/fatmbr.h | 90 ++++++++++++++++++++++++ source/fs/filetype.c | 19 +++--- source/fs/filetype.h | 3 +- source/godmode.c | 20 ++++-- source/nand/nand.c | 28 +++++++- source/nand/nand.h | 4 ++ source/nand/nandutil.c | 152 +++++++++++++++++++++++++++++++++++++++++ source/nand/nandutil.h | 12 ++++ 9 files changed, 343 insertions(+), 17 deletions(-) create mode 100644 source/fs/fatmbr.c create mode 100644 source/fs/fatmbr.h create mode 100644 source/nand/nandutil.c create mode 100644 source/nand/nandutil.h diff --git a/source/fs/fatmbr.c b/source/fs/fatmbr.c new file mode 100644 index 0000000..a3bfacb --- /dev/null +++ b/source/fs/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/fs/fatmbr.h b/source/fs/fatmbr.h new file mode 100644 index 0000000..6a39e59 --- /dev/null +++ b/source/fs/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/fs/filetype.c b/source/fs/filetype.c index d4ef605..5255f97 100644 --- a/source/fs/filetype.c +++ b/source/fs/filetype.c @@ -1,5 +1,6 @@ #include "filetype.h" #include "fsutil.h" +#include "fatmbr.h" #include "game.h" u32 IdentifyFileType(const char* path) { @@ -12,17 +13,13 @@ u32 IdentifyFileType(const char* path) { if ((getbe32(header + 0x100) == 0x4E435344) && (getbe64(header + 0x110) == (u64) 0x0104030301000000) && (getbe64(header + 0x108) == (u64) 0) && (fsize >= 0x8FC8000)) { return IMG_NAND; // NAND image - } else if (getbe16(header + 0x1FE) == 0x55AA) { // migt be FAT or MBR - if ((strncmp((char*) header + 0x36, "FAT12 ", 8) == 0) || (strncmp((char*) header + 0x36, "FAT16 ", 8) == 0) || - (strncmp((char*) header + 0x36, "FAT ", 8) == 0) || (strncmp((char*) header + 0x52, "FAT32 ", 8) == 0) || - ((getle64(header + 0x36) == 0) && (getle16(header + 0x0B) == 0x200))) { // last one is a special case for public.sav - return IMG_FAT; // this is an actual FAT header - } else if (((getle32(header + 0x1BE + 0x8) + getle32(header + 0x1BE + 0xC)) < (fsize / 0x200)) && // check file size - (getle32(header + 0x1BE + 0x8) > 0) && (getle32(header + 0x1BE + 0xC) >= 0x800) && // check first partition sanity - ((header[0x1BE + 0x4] == 0x1) || (header[0x1BE + 0x4] == 0x4) || (header[0x1BE + 0x4] == 0x6) || // filesystem type - (header[0x1BE + 0x4] == 0xB) || (header[0x1BE + 0x4] == 0xC) || (header[0x1BE + 0x4] == 0xE))) { - return IMG_FAT; // this might be an MBR -> give it the benefit of doubt - } + } else if (ValidateFatHeader(header) == 0) { + return IMG_FAT; // FAT image file + } else if (ValidateMbrHeader((MbrHeader*) (void*) header) == 0) { + MbrHeader* mbr = (MbrHeader*) (void*) header; + MbrPartitionInfo* partition0 = mbr->partitions; + if ((partition0->sector + partition0->count) <= (fsize / 0x200)) // size check + return IMG_FAT; // possibly an MBR -> also treat as FAT image } else if (ValidateCiaHeader((CiaHeader*) (void*) header) == 0) { // this only works because these functions ignore CIA content index CiaInfo info; diff --git a/source/fs/filetype.h b/source/fs/filetype.h index f159298..551f56f 100644 --- a/source/fs/filetype.h +++ b/source/fs/filetype.h @@ -15,9 +15,10 @@ #define SYS_FIRM (1<<8) #define FTYPE_MOUNTABLE (IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|SYS_FIRM) -#define FYTPE_VERIFICABLE (GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|SYS_FIRM) +#define FYTPE_VERIFICABLE (IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|SYS_FIRM) #define FYTPE_DECRYPTABLE (GAME_CIA|GAME_NCSD|GAME_NCCH|SYS_FIRM) #define FTYPE_BUILDABLE (GAME_NCSD|GAME_NCCH|GAME_TMD) #define FTYPE_BUILDABLE_L (FTYPE_BUILDABLE&(GAME_TMD)) +#define FTYPE_RESTORABLE (IMG_NAND) u32 IdentifyFileType(const char* path); diff --git a/source/godmode.c b/source/godmode.c index fe4e91a..4ab80b8 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -6,6 +6,7 @@ #include "fsutil.h" #include "fsperm.h" #include "gameutil.h" +#include "nandutil.h" #include "filetype.h" #include "platform.h" #include "nand.h" @@ -576,6 +577,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur bool decryptable_inplace = (decryptable && (drvtype & (DRV_SDCARD|DRV_RAMDRIVE))); bool buildable = (filetype & FTYPE_BUILDABLE); bool buildable_legit = (filetype & FTYPE_BUILDABLE_L); + bool restorable = CheckA9lh() && (filetype & FTYPE_RESTORABLE); char pathstr[32 + 1]; TruncateString(pathstr, curr_entry->path, 32, 8); @@ -592,7 +594,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (int) ++n_opt : -1; int searchdrv = (DriveType(current_path) & DRV_SEARCH) ? ++n_opt : -1; if (special > 0) optionstr[special-1] = - (filetype == IMG_NAND ) ? "Mount as NAND image" : + (filetype == IMG_NAND ) ? "NAND image options..." : (filetype == IMG_FAT ) ? "Mount as FAT image" : (filetype == GAME_CIA ) ? "CIA image options..." : (filetype == GAME_NCSD ) ? "NCSD image options..." : @@ -640,12 +642,14 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur // stuff for special menu starts here n_opt = 0; int mount = (mountable) ? ++n_opt : -1; + int restore = (restorable) ? ++n_opt : -1; int decrypt = (decryptable) ? ++n_opt : -1; int decrypt_inplace = (decryptable_inplace) ? ++n_opt : -1; int build = (buildable) ? ++n_opt : -1; int build_legit = (buildable_legit) ? ++n_opt : -1; int verify = (verificable) ? ++n_opt : -1; if (mount > 0) optionstr[mount-1] = "Mount image to drive"; + if (restore > 0) optionstr[restore-1] = "Restore SysNAND (safe)"; if (decrypt > 0) optionstr[decrypt-1] = "Decrypt file (SD output)"; if (decrypt_inplace > 0) optionstr[decrypt_inplace-1] = "Decrypt file (inplace)"; if (build > 0) optionstr[build-1] = (build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)"; @@ -752,7 +756,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur else ShowPrompt(false, "%s\nCIA build failed", pathstr); } return 0; - } else if (user_select == verify) { // -> verify game file + } else if (user_select == verify) { // -> verify game / nand file if ((n_marked > 1) && ShowPrompt(true, "Try to verify all %lu selected files?", n_marked)) { u32 n_success = 0; u32 n_other = 0; @@ -768,7 +772,9 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur if (!(filetype & (GAME_CIA|GAME_TMD)) && !ShowProgress(n_processed++, n_marked, path)) break; current_dir->entry[i].marked = false; - if (VerifyGameFile(path) == 0) n_success++; + if (filetype & IMG_NAND) { + if (ValidateNandDump(path) == 0) n_success++; + } else if (VerifyGameFile(path) == 0) n_success++; else { // on failure: set *cursor on failed title, break; *cursor = i; break; @@ -779,10 +785,16 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur else ShowPrompt(false, "%lu/%lu files verified ok", n_success, n_marked); } else { ShowString("%s\nVerifying file, please wait...", pathstr); - ShowPrompt(false, "%s\nVerification %s", pathstr, + if (filetype & IMG_NAND) { + ShowPrompt(false, "%s\nNAND validation %s", pathstr, + (ValidateNandDump(curr_entry->path) == 0) ? "success" : "failed"); + } else ShowPrompt(false, "%s\nVerification %s", pathstr, (VerifyGameFile(curr_entry->path) == 0) ? "success" : "failed"); } return 0; + } else if (user_select == restore) { // -> restore SysNAND (A9LH preserving) + ShowPrompt(false, "%s\nNAND restore %s", pathstr, + (SafeRestoreNandDump(curr_entry->path) == 0) ? "success" : "failed"); } return 1; diff --git a/source/nand/nand.c b/source/nand/nand.c index ddbfd8d..36e2431 100644 --- a/source/nand/nand.c +++ b/source/nand/nand.c @@ -8,7 +8,7 @@ #include "sdmmc.h" #include "image.h" -#define NAND_MIN_SECTORS ((GetUnitPlatform() == PLATFORM_N3DS) ? 0x26C000 : 0x1D7800) +#define NAND_MIN_SECTORS ((GetUnitPlatform() == PLATFORM_N3DS) ? NAND_MIN_SECTORS_N3DS : NAND_MIN_SECTORS_O3DS) static u8 slot0x05KeyY[0x10] = { 0x00 }; // need to load this from FIRM0 / external file static u8 slot0x05KeyY_sha256[0x20] = { // hash for slot0x05KeyY (16 byte) @@ -34,6 +34,14 @@ static u8 nand_magic_o3ds[0x60] = { // NCSD NAND header O3DS magic 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static 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]; static u8 OtpSha256[32] = { 0 }; @@ -338,6 +346,24 @@ int WriteNandSectors(const u8* buffer, u32 sector, u32 count, u32 keyslot, u32 n 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 (memcmp(header_dec + 0x1BE, twl_mbr, sizeof(twl_mbr)) != 0) + return 0; // header does not belong to console + + // header type check + if (memcmp(header + 0x100, nand_magic_n3ds, sizeof(nand_magic_n3ds) == 0) == 0) + return (GetUnitPlatform() == PLATFORM_3DS) ? 0 : NAND_TYPE_N3DS; + else if (memcmp(header + 0x100, nand_magic_o3ds, sizeof(nand_magic_o3ds) == 0) == 0) + return NAND_TYPE_O3DS; + + return 0; +} + u32 CheckNandType(u32 nand_src) { if (ReadNandSectors(NAND_BUFFER, 0, 1, 0xFF, nand_src) != 0) diff --git a/source/nand/nand.h b/source/nand/nand.h index 84d0c2a..d8721c7 100644 --- a/source/nand/nand.h +++ b/source/nand/nand.h @@ -10,6 +10,9 @@ #define NAND_TYPE_N3DS (1<<5) #define NAND_TYPE_NO3DS (1<<6) +#define NAND_MIN_SECTORS_O3DS 0x1D7800 +#define NAND_MIN_SECTORS_N3DS 0x26C000 + bool InitNandCrypto(void); bool CheckSlot0x05Crypto(void); bool CheckSector0x96Crypto(void); @@ -23,6 +26,7 @@ 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); u64 GetNandSizeSectors(u32 src); +u32 CheckNandHeader(u8* header); u32 CheckNandType(u32 src); bool InitEmuNandBase(void); diff --git a/source/nand/nandutil.c b/source/nand/nandutil.c new file mode 100644 index 0000000..521551e --- /dev/null +++ b/source/nand/nandutil.c @@ -0,0 +1,152 @@ +#include "nandutil.h" +#include "nand.h" +#include "firm.h" +#include "fatmbr.h" +#include "fsperm.h" +#include "sha.h" +#include "ui.h" +#include "ff.h" + +u32 ReadNandFile(FIL* file, void* buffer, u32 sector, u32 count, u32 keyslot) { + u32 offset = sector * 0x200; + u32 size = count * 0x200; + UINT btr; + if ((f_tell(file) != offset) && (f_lseek(file, offset) != FR_OK)) + return 1; // seek failed + if ((f_read(file, buffer, size, &btr) != FR_OK) || (btr != size)) + return 1; // read failed + if (keyslot < 0x40) CryptNand(buffer, sector, count, keyslot); + return 0; +} + +u32 ValidateNandDump(const char* path) { + const u32 mbr_sectors[] = { TWL_OFFSET, CTR_OFFSET }; + const u32 firm_sectors[] = { FIRM_OFFSETS }; + u8 buffer[0x200]; + FirmHeader firm; + MbrHeader mbr; + u32 nand_type; + FIL file; + + // truncated path string + char pathstr[32 + 1]; + TruncateString(pathstr, path, 32, 8); + + // open file + if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) + 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); + f_close(&file); + return 1; + } + + // check size + if (f_size(&file) < ((nand_type == NAND_TYPE_O3DS) ? NAND_MIN_SECTORS_O3DS : NAND_MIN_SECTORS_N3DS)) { + ShowPrompt(false, "%s\nNAND dump misses data", pathstr); + f_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; + char* section_type = (i) ? "CTR" : "MBR"; + if ((ReadNandFile(&file, &mbr, mbr_sectors[i], 1, keyslot) != 0) || + (ValidateMbrHeader(&mbr) != 0)) { + ShowPrompt(false, "%s\nError: %s MBR is corrupt", pathstr, section_type); + f_close(&file); + return 1; // impossible to happen + } + for (u32 p = 0; p < 4; p++) { + u32 p_sector = mbr.partitions[p].sector; + if (!p_sector) continue; + if ((ReadNandFile(&file, buffer, mbr_sectors[i] + p_sector, 1, keyslot) != 0) || + (ValidateFatHeader(buffer) != 0)) { + ShowPrompt(false, "%s\nError: %s partition%u is corrupt", pathstr, section_type, p); + f_close(&file); + return 1; + } + } + } + + // 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) || + (getbe32(firm.dec_magic) != 0)) { // decrypted firms are not allowed + ShowPrompt(false, "%s\nError: FIRM%u header is corrupt", pathstr, i); + f_close(&file); + return 1; + } + // hash verify all available sections + if (i == 0) continue; // no hash checks for FIRM0 (might be A9LH) + for (u32 s = 0; s < 4; s++) { + FirmSectionHeader* section = firm.sections + s; + u32 sector = firm_sectors[i] + (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); + 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); + f_close(&file); + return 1; + } + } + } + + return 0; +} + +u32 SafeRestoreNandDump(const char* path) { + u32 safe_sectors[] = { SAFE_SECTORS }; + FIL file; + + /* if (ValidateNandDump(path) != 0) { // NAND dump validation + ShowPrompt(false, "NAND dump corrupt or not from console.\nYou can still try mount and copy."); + return 1; + }*/ + if (!CheckA9lh()) { + ShowPrompt(false, "Error: A9LH not detected."); + return 1; + } + if (!SetWritePermissions(PERM_SYSNAND, true)) return 1; + + // open file, get size + if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) + return 1; + u32 fsize = f_size(&file); + safe_sectors[(sizeof(safe_sectors) / sizeof(u32)) - 1] = fsize / 0x200; + + // main processing loop + u32 ret = 0; + 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]; + f_lseek(&file, sector0 * 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 (f_read(&file, MAIN_BUFFER, count * 0x200, &btr) != FR_OK) 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; + } + } + f_close(&file); + + return ret; +} \ No newline at end of file diff --git a/source/nand/nandutil.h b/source/nand/nandutil.h new file mode 100644 index 0000000..fa65270 --- /dev/null +++ b/source/nand/nandutil.h @@ -0,0 +1,12 @@ +#pragma once + +#include "common.h" + +#define TWL_OFFSET 0x000000 +#define CTR_OFFSET 0x05C980 +#define FIRM_OFFSETS 0x058980, 0x05A980 + +#define SAFE_SECTORS 0x000001, 0x000096, 0x000097, 0x058980, 0x05C980, 0x000000 // last one is a placeholder + +u32 ValidateNandDump(const char* path); +u32 SafeRestoreNandDump(const char* path);