Use actual offsets from NCSD in virtual drives

This commit is contained in:
d0k3 2017-05-26 00:50:31 +02:00
parent 90049a5fd3
commit 48281307a0
3 changed files with 223 additions and 60 deletions

View File

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

View File

@ -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
@ -41,6 +42,51 @@
#define OTP_NAME "otp.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);
bool CheckSector0x96Crypto(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);

View File

@ -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;
const VirtualNandTemplate* template = templates + vdir->index;
NandPartitionInfo prt_info;
// copy current template to vfile
memcpy(vfile, templates + vdir->index, sizeof(VirtualFile));
// 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))
// 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;
// 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;