diff --git a/source/nand/nand.c b/source/nand/nand.c index 3b9f33b..32cf5b4 100644 --- a/source/nand/nand.c +++ b/source/nand/nand.c @@ -5,6 +5,7 @@ #include "keydb.h" #include "aes.h" #include "sha.h" +#include "fatmbr.h" #include "sdmmc.h" #include "image.h" @@ -13,6 +14,19 @@ #define KEY95_SHA256 ((IS_DEVKIT) ? slot0x11Key95dev_sha256 : slot0x11Key95_sha256) #define SECTOR_SHA256 ((IS_DEVKIT) ? sector0x96dev_sha256 : sector0x96_sha256) +// see: https://www.3dbrew.org/wiki/NCSD#NCSD_header +static const u32 np_keyslots[9][4] = { // [NP_TYPE][NP_SUBTYPE] + { 0xFF, 0xFF, 0xFF, 0xFF }, // none + { 0xFF, 0x03, 0x04, 0x05 }, // standard + { 0xFF, 0x03, 0x04, 0x05 }, // FAT (custom, not in NCSD) + { 0xFF, 0xFF, 0x06, 0xFF }, // FIRM + { 0xFF, 0xFF, 0x07, 0xFF }, // AGBSAVE + { 0xFF, 0xFF, 0xFF, 0xFF }, // NCSD (custom) + { 0xFF, 0xFF, 0xFF, 0xFF }, // D0K3 (custom) + { 0xFF, 0xFF, 0xFF, 0x11 }, // SECRET (custom) + { 0xFF, 0xFF, 0xFF, 0xFF } // BONUS (custom) +}; + static u8 slot0x05KeyY[0x10] = { 0x00 }; // need to load this from FIRM0 / external file static const u8 slot0x05KeyY_sha256[0x20] = { // hash for slot0x05KeyY (16 byte) 0x98, 0x24, 0x27, 0x14, 0x22, 0xB0, 0x6B, 0xF2, 0x10, 0x96, 0x9C, 0x36, 0x42, 0x53, 0x7C, 0x86, @@ -96,23 +110,24 @@ static u32 emunand_base_sector = 0x000000; u32 LoadKeyYFromP9(u8* key, const u8* keyhash, u32 offset, u32 keyslot) { - static u32 offsetA9l = 0x066A00; // fixed offset, this only has to work for FIRM90 / FIRM81 + static const u32 offsetA9l = 0x066A00; // fixed offset, this only has to work for FIRM90 / FIRM81 + static const u32 sector_firm0 = 0x058980; // standard firm0 sector (this only has to work in A9LH anyways) u8 ctr0x15[16] __attribute__((aligned(32))); u8 keyY0x15[16] __attribute__((aligned(32))); u8 keyY[16] __attribute__((aligned(32))); u8 header[0x200]; // check arm9loaderhax - if (!IS_A9LH || (offset < (offsetA9l + 0x0800))) return 1; + if (!IS_A9LH || IS_SIGHAX || (offset < (offsetA9l + 0x0800))) return 1; // section 2 (arm9loader) header of FIRM // this is @0x066A00 in FIRM90 & FIRM81 - ReadNandBytes(header, (SECTOR_FIRM0 * 0x200) + offsetA9l, 0x200, 0x06, NAND_SYSNAND); + ReadNandBytes(header, (sector_firm0 * 0x200) + offsetA9l, 0x200, 0x06, NAND_SYSNAND); memcpy(keyY0x15, header + 0x10, 0x10); // 0x15 keyY memcpy(ctr0x15, header + 0x20, 0x10); // 0x15 counter // read and decrypt the encrypted keyY - ReadNandBytes(keyY, (SECTOR_FIRM0 * 0x200) + offset, 0x10, 0x06, NAND_SYSNAND); + ReadNandBytes(keyY, (sector_firm0 * 0x200) + offset, 0x10, 0x06, NAND_SYSNAND); setup_aeskeyY(0x15, keyY0x15); use_aeskey(0x15); ctr_decrypt_byte(keyY, keyY, 0x10, offset - (offsetA9l + 0x800), AES_CNT_CTRNAND_MODE, ctr0x15); @@ -141,6 +156,7 @@ bool InitNandCrypto(void) // store the current SHA256 from register memcpy(OtpSha256, (void*) REG_SHAHASH, 32); } else { + // load hash via keys? const char* base[] = { INPUT_PATHS }; char path[64]; u8 otp[0x100]; @@ -231,18 +247,18 @@ bool CheckSlot0x05Crypto(void) bool CheckSector0x96Crypto(void) { u8 buffer[0x200]; - ReadNandSectors(buffer, 0x96, 1, 0x11, NAND_SYSNAND); + ReadNandSectors(buffer, SECTOR_SECRET, 1, 0x11, NAND_SYSNAND); return (sha_cmp(KEY95_SHA256, buffer, 16, SHA256_MODE) == 0); } void CryptNand(u8* buffer, u32 sector, u32 count, u32 keyslot) { - u32 mode = (sector >= SECTOR_TWL + SIZE_TWL) ? AES_CNT_CTRNAND_MODE : AES_CNT_TWLNAND_MODE; + u32 mode = (keyslot != 0x03) ? AES_CNT_CTRNAND_MODE : AES_CNT_TWLNAND_MODE; // somewhat hacky u8 ctr[16] __attribute__((aligned(32))); u32 blocks = count * (0x200 / 0x10); // copy NAND CTR and increment it - memcpy(ctr, (sector >= SECTOR_TWL + SIZE_TWL) ? CtrNandCtr : TwlNandCtr, 16); + memcpy(ctr, (keyslot != 0x03) ? CtrNandCtr : TwlNandCtr, 16); // hacky again add_ctr(ctr, sector * (0x200 / 0x10)); // decrypt the data @@ -356,7 +372,7 @@ int ReadNandSectors(u8* buffer, u32 sector, u32 count, u32 keyslot, u32 nand_src } else { return -1; } - if ((keyslot == 0x11) && (sector == 0x96)) CryptSector0x96(buffer, false); + if ((keyslot == 0x11) && (sector == SECTOR_SECRET)) CryptSector0x96(buffer, false); else if (keyslot < 0x40) CryptNand(buffer, sector, count, keyslot); return 0; @@ -368,7 +384,7 @@ int WriteNandSectors(const u8* buffer, u32 sector, u32 count, u32 keyslot, u32 n for (u32 s = 0; s < count; s += (NAND_BUFFER_SIZE / 0x200)) { u32 pcount = min((NAND_BUFFER_SIZE/0x200), (count - s)); memcpy(NAND_BUFFER, buffer + (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); if (nand_dst == NAND_EMUNAND) { int errorcode = 0; @@ -391,6 +407,94 @@ int WriteNandSectors(const u8* buffer, u32 sector, u32 count, u32 keyslot, u32 n return 0; } +u32 GetNandNcsdMinSizeSectors(NandNcsdHeader* ncsd) // in sectors +{ + u32 nand_minsize = 1; + for (u32 prt_idx = 0; prt_idx < 8; prt_idx++) { + u32 prt_end = ncsd->partitions[prt_idx].offset + ncsd->partitions[prt_idx].size; + if (prt_end > nand_minsize) nand_minsize = prt_end; + } + + return nand_minsize; +} + +u32 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, u32 nand_src) +{ + // check NAND NCSD integrity(!!!) + // workaround for ZERONAND + if (nand_src == NAND_ZERONAND) nand_src = NAND_SYSNAND; + + // find type & subtype in NCSD header + 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)) || + ((type != NP_TYPE_FAT) && (GetNandNcsdPartitionInfo(info, type, subtype, index, ncsd) != 0))) + return 1; // not found + + // size of bonus partition + if (type == NP_TYPE_BONUS) { + 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); + 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; +} + u32 CheckNandMbr(u8* mbr) { if (memcmp(mbr + (0x200 - sizeof(twl_mbr)), twl_mbr, sizeof(twl_mbr)) == 0) @@ -470,7 +574,7 @@ u32 GetLegitSector0x96(u8* sector) // search for valid secret sector in SysNAND / EmuNAND const u32 nand_src[] = { NAND_SYSNAND, NAND_EMUNAND }; for (u32 i = 0; i < sizeof(nand_src) / sizeof(u32); i++) { - ReadNandSectors(sector, 0x96, 1, 0x11, nand_src[i]); + ReadNandSectors(sector, SECTOR_SECRET, 1, 0x11, nand_src[i]); if (sha_cmp(SECTOR_SHA256, sector, 0x200, SHA256_MODE) == 0) return 0; } diff --git a/source/nand/nand.h b/source/nand/nand.h index 6714de7..c5116cd 100644 --- a/source/nand/nand.h +++ b/source/nand/nand.h @@ -17,6 +17,7 @@ // start sectors of partitions #define SECTOR_TWL 0x000000 +#define SECTOR_D0K3 0x000001 #define SECTOR_SECRET 0x000096 #define SECTOR_TWLN 0x000097 #define SECTOR_TWLP 0x04808D @@ -39,7 +40,52 @@ #define SECTOR_NAME "sector0x96.bin" #define SECRET_NAME "secret_sector.bin" #define OTP_NAME "otp.bin" -#define OTP_BIG_NAME "otp0x108.bin" +#define OTP_BIG_NAME "otp0x108.bin" + +// 0x110...0x118 in the NAND NCSD header +// see: https://www.3dbrew.org/wiki/NCSD#NCSD_header +#define NP_TYPE_NONE 0 +#define NP_TYPE_STD 1 +#define NP_TYPE_FAT 2 // this is of our own making +#define NP_TYPE_FIRM 3 +#define NP_TYPE_AGB 4 +#define NP_TYPE_NCSD 5 // this is of our own making +#define NP_TYPE_D0K3 6 // my own partition ^_^ +#define NP_TYPE_SECRET 7 // this is of our own making +#define NP_TYPE_BONUS 8 // this is of our own making + +// 0x118...0x120 in the NAND NCSD header +// see: https://www.3dbrew.org/wiki/NCSD#NCSD_header +#define NP_SUBTYPE_NONE 0 +#define NP_SUBTYPE_TWL 1 +#define NP_SUBTYPE_CTR 2 +#define NP_SUBTYPE_CTR_N 3 + + +typedef struct { + u32 sector; + u32 count; + u32 keyslot; +} __attribute__((packed)) NandPartitionInfo; + +typedef struct { + u32 offset; + u32 size; +} __attribute__((packed)) NandNcsdPartition; + +// see: https://www.3dbrew.org/wiki/NCSD#NCSD_header +typedef struct { + u8 signature[0x100]; + u8 magic[4]; + u32 size; + u64 mediaId; // this is zero + u8 partitions_fs_type[8]; + u8 partitions_crypto_type[8]; + NandNcsdPartition partitions[8]; + u8 unknown[0x5E]; + u8 twl_mbr[0x42]; +} __attribute__((packed)) NandNcsdHeader; + bool InitNandCrypto(void); bool CheckSlot0x05Crypto(void); @@ -52,7 +98,10 @@ 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 GetNandNcsdMinSizeSectors(NandNcsdHeader* ncsd); u64 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); diff --git a/source/virtual/vnand.c b/source/virtual/vnand.c index 592de3d..ffd71ea 100644 --- a/source/virtual/vnand.c +++ b/source/virtual/vnand.c @@ -4,40 +4,45 @@ #include "essentials.h" #include "unittype.h" -#define VFLAG_ON_O3DS NAND_TYPE_O3DS -#define VFLAG_ON_N3DS NAND_TYPE_N3DS -#define VFLAG_ON_NO3DS NAND_TYPE_NO3DS -#define VFLAG_ON_NAND (VFLAG_ON_O3DS | VFLAG_ON_N3DS | VFLAG_ON_NO3DS) +#define VFLAG_MBR (1UL<<27) #define VFLAG_ESSENTIAL (1UL<<28) #define VFLAG_GBA_VC (1UL<<29) #define VFLAG_NEEDS_OTP (1UL<<30) #define VFLAG_NAND_SIZE (1UL<<31) -// see: http://3dbrew.org/wiki/Flash_Filesystem#NAND_structure -// too much hardcoding, but more readable this way -static const VirtualFile vNandFileTemplates[] = { - { "twln.bin" , 0x00012E00, 0x08FB5200, 0x03, VFLAG_ON_NAND }, - { "twlp.bin" , 0x09011A00, 0x020B6600, 0x03, VFLAG_ON_NAND }, - { "agbsave.bin" , 0x0B100000, 0x00030000, 0x07, VFLAG_ON_NAND }, - { "firm0.bin" , 0x0B130000, 0x00400000, 0x06, VFLAG_ON_NAND}, - { "firm1.bin" , 0x0B530000, 0x00400000, 0x06, VFLAG_ON_NAND}, - { "ctrnand_fat.bin" , 0x0B95CA00, 0x2F3E3600, 0x04, VFLAG_ON_O3DS }, - { "ctrnand_fat.bin" , 0x0B95AE00, 0x41D2D200, 0x05, VFLAG_ON_N3DS }, - { "ctrnand_fat.bin" , 0x0B95AE00, 0x41D2D200, 0x04, VFLAG_ON_NO3DS }, - { "ctrnand_full.bin" , 0x0B930000, 0x2F5D0000, 0x04, VFLAG_ON_O3DS }, - { "ctrnand_full.bin" , 0x0B930000, 0x41ED0000, 0x05, VFLAG_ON_N3DS }, - { "ctrnand_full.bin" , 0x0B930000, 0x41ED0000, 0x04, VFLAG_ON_NO3DS }, - { "sector0x96.bin" , 0x00012C00, 0x00000200, 0x11, VFLAG_ON_NAND | VFLAG_NEEDS_OTP }, - { "nand.bin" , 0x00000000, 0x00000000, 0xFF, VFLAG_ON_NAND | VFLAG_NAND_SIZE }, - { "nand_minsize.bin" , 0x00000000, 0x3AF00000, 0xFF, VFLAG_ON_O3DS }, - { "nand_minsize.bin" , 0x00000000, 0x4D800000, 0xFF, VFLAG_ON_N3DS | VFLAG_ON_NO3DS }, - { "nand_hdr.bin" , 0x00000000, 0x00000200, 0xFF, VFLAG_ON_NAND }, - { "twlmbr.bin" , 0x000001BE, 0x00000042, 0x03, VFLAG_ON_NAND }, - { "free0x01.bin" , 0x00000200, 0x00012A00, 0xFF, VFLAG_ON_NAND }, - { "essential.exefs" , 0x00000200, 0x00000000, 0xFF, VFLAG_ON_NAND | VFLAG_ESSENTIAL }, - { "bonus0x1D7800.bin", 0x3AF00000, 0x00000000, 0xFF, VFLAG_ON_O3DS | VFLAG_NAND_SIZE }, - { "bonus0x26C000.bin", 0x4D800000, 0x00000000, 0xFF, VFLAG_ON_N3DS | VFLAG_ON_NO3DS | VFLAG_NAND_SIZE }, - { "gbavc.sav" , 0x0B100200, 0x00000000, 0x07, VFLAG_ON_NAND | VFLAG_GBA_VC }, +typedef struct { + char name[32]; + u32 type; + u32 subtype; + u32 index; + u32 flags; +} __attribute__((packed)) VirtualNandTemplate; + +// see NP_TYPE_ and NP_SUBTYPE_ in nand.h +static const VirtualNandTemplate vNandTemplates[] = { + { "nand_hdr.bin" , NP_TYPE_NCSD , NP_SUBTYPE_CTR , 0, 0 }, + { "twlmbr.bin" , NP_TYPE_STD , NP_SUBTYPE_TWL , 0, VFLAG_MBR }, + { "essential.exefs" , NP_TYPE_D0K3 , NP_SUBTYPE_NONE , 0, VFLAG_ESSENTIAL }, + { "sector0x96.bin" , NP_TYPE_SECRET, NP_SUBTYPE_CTR_N, 0, VFLAG_NEEDS_OTP }, + { "twln.bin" , NP_TYPE_FAT , NP_SUBTYPE_TWL , 0, 0 }, + { "twlp.bin" , NP_TYPE_FAT , NP_SUBTYPE_TWL , 1, 0 }, + { "agbsave.bin" , NP_TYPE_AGB , NP_SUBTYPE_CTR , 0, 0 }, + { "gbavc.sav" , NP_TYPE_AGB , NP_SUBTYPE_CTR , 0, VFLAG_GBA_VC }, + { "firm0.bin" , NP_TYPE_FIRM , NP_SUBTYPE_CTR , 0, 0 }, + { "firm1.bin" , NP_TYPE_FIRM , NP_SUBTYPE_CTR , 1, 0 }, + { "firm2.bin" , NP_TYPE_FIRM , NP_SUBTYPE_CTR , 2, 0 }, + { "firm3.bin" , NP_TYPE_FIRM , NP_SUBTYPE_CTR , 3, 0 }, + { "firm4.bin" , NP_TYPE_FIRM , NP_SUBTYPE_CTR , 4, 0 }, + { "firm5.bin" , NP_TYPE_FIRM , NP_SUBTYPE_CTR , 5, 0 }, + { "firm6.bin" , NP_TYPE_FIRM , NP_SUBTYPE_CTR , 6, 0 }, + { "firm7.bin" , NP_TYPE_FIRM , NP_SUBTYPE_CTR , 7, 0 }, + { "ctrnand_full.bin" , NP_TYPE_STD , NP_SUBTYPE_CTR , 0, 0 }, + { "ctrnand_full.bin" , NP_TYPE_STD , NP_SUBTYPE_CTR_N, 0, 0 }, + { "ctrnand_fat.bin" , NP_TYPE_FAT , NP_SUBTYPE_CTR , 0, 0 }, + { "ctrnand_fat.bin" , NP_TYPE_FAT , NP_SUBTYPE_CTR_N, 0, 0 }, + { "bonus.bin" , NP_TYPE_BONUS , NP_SUBTYPE_CTR , 0, 0 }, + { "nand.bin" , NP_TYPE_NONE , NP_SUBTYPE_NONE , 0, VFLAG_NAND_SIZE }, + { "nand_minsize.bin" , NP_TYPE_NONE , NP_SUBTYPE_NONE , 0, 0 } }; bool CheckVNandDrive(u32 nand_src) { @@ -45,35 +50,38 @@ bool CheckVNandDrive(u32 nand_src) { } bool ReadVNandDir(VirtualFile* vfile, VirtualDir* vdir) { // uses a generic vdir object generated in virtual.c - int n_templates = sizeof(vNandFileTemplates) / sizeof(VirtualFile); - const VirtualFile* templates = vNandFileTemplates; + int n_templates = sizeof(vNandTemplates) / sizeof(VirtualNandTemplate); + const VirtualNandTemplate* templates = vNandTemplates; u32 nand_src = vdir->flags & VRT_SOURCE; - while (++vdir->index < n_templates) { - // get NAND type (O3DS/N3DS/NO3DS), workaround for empty EmuNAND - u32 nand_type = CheckNandType(nand_src); - if (!nand_type) nand_type = (IS_O3DS) ? NAND_TYPE_O3DS : NAND_TYPE_N3DS; + while (++vdir->index < n_templates) { + const VirtualNandTemplate* template = templates + vdir->index; + NandPartitionInfo prt_info; - // copy current template to vfile - memcpy(vfile, templates + vdir->index, sizeof(VirtualFile)); + // set up virtual file + if (GetNandPartitionInfo(&prt_info, template->type, template->subtype, template->index, nand_src) != 0) + continue; + snprintf(vfile->name, 32, "%s%s", template->name, (nand_src == VRT_XORPAD) ? ".xorpad" : ""); + vfile->offset = ((u64) prt_info.sector) * 0x200; + vfile->size = ((u64) prt_info.count) * 0x200; + vfile->keyslot = prt_info.keyslot; + vfile->flags = template->flags; - // XORpad drive handling - if (nand_src == VRT_XORPAD) { - snprintf(vfile->name, 32, "%s.xorpad", templates[vdir->index].name); - if ((vfile->keyslot == 0x11) || (vfile->keyslot >= 0x40) || (vfile->flags & VFLAG_GBA_VC)) - continue; - } - - // process / check special flags - if (!(vfile->flags & nand_type)) - continue; // virtual file has wrong NAND type + // handle special cases + if (!vfile->size) continue; + if ((nand_src == VRT_XORPAD) && ((vfile->keyslot == 0x11) || (vfile->keyslot >= 0x40) || (vfile->flags & VFLAG_GBA_VC))) + continue; if ((vfile->keyslot == 0x05) && !CheckSlot0x05Crypto()) continue; // keyslot 0x05 not properly set up if ((vfile->flags & VFLAG_NEEDS_OTP) && !CheckSector0x96Crypto()) continue; // sector 0x96 crypto not set up + if (vfile->flags & VFLAG_MBR) { + vfile->offset += 0x200 - 0x42; + vfile->size = 0x42; + } if (vfile->flags & VFLAG_NAND_SIZE) { - if ((nand_src != VRT_SYSNAND) && (GetNandSizeSectors(NAND_SYSNAND) != GetNandSizeSectors(nand_src))) - continue; // EmuNAND/ImgNAND is too small + if ((nand_src != VRT_SYSNAND) && (GetNandSizeSectors(NAND_SYSNAND) > GetNandSizeSectors(nand_src))) + continue; // EmuNAND / ImgNAND is too small u64 nand_size = GetNandSizeSectors(NAND_SYSNAND) * 0x200; if (nand_size <= vfile->offset) continue; vfile->size = nand_size - vfile->offset; @@ -87,9 +95,11 @@ bool ReadVNandDir(VirtualFile* vfile, VirtualDir* vdir) { // uses a generic vdir } if (vfile->flags & VFLAG_GBA_VC) { if (CheckAgbSaveCmac(nand_src) != 0) continue; + vfile->offset += 0x200; vfile->size = GetAgbSaveSize(nand_src); } + // found if arriving here vfile->flags |= nand_src; return true;