Handle GBA VC save in virtual NAND drives

This commit is contained in:
d0k3 2017-01-05 02:50:41 +01:00
parent 9432723791
commit d0785b12d1
7 changed files with 179 additions and 67 deletions

48
source/nand/agbsave.c Normal file
View File

@ -0,0 +1,48 @@
#include "agbsave.h"
#include "sha.h"
#include "aes.h"
u32 GetAgbSaveSize(u32 nand_src) {
AgbSave* agbsave = (AgbSave*) NAND_BUFFER;
if (ReadNandSectors((u8*) agbsave, SECTOR_AGBSAVE, 1, 0x07, nand_src) != 0)
return 0;
return agbsave->save_size; // it's recommended to also check the CMAC
}
u32 CheckAgbSaveCmac(u32 nand_src) {
u8 magic[] = { AGBSAVE_MAGIC };
AgbSave* agbsave = (AgbSave*) NAND_BUFFER;
if ((ReadNandSectors((u8*) agbsave, SECTOR_AGBSAVE, 1, 0x07, nand_src) != 0) ||
(memcmp(agbsave->magic, magic, sizeof(magic)) != 0) ||
(ReadNandBytes(agbsave->savegame, (SECTOR_AGBSAVE+1) * 0x200, agbsave->save_size, 0x07, nand_src) != 0))
return 1;
u8 cmac[16] __attribute__((aligned(32)));
u8 shasum[32];
sha_quick(shasum, ((u8*) agbsave) + 0x30, (0x200 - 0x30) + agbsave->save_size, SHA256_MODE);
use_aeskey(0x24);
aes_cmac(shasum, cmac, 2);
return (memcmp(cmac, agbsave->cmac, 16) == 0) ? 0 : 1;
}
u32 FixAgbSaveCmac(u32 nand_dst) {
AgbSave* agbsave = (AgbSave*) NAND_BUFFER;
if ((ReadNandSectors((u8*) agbsave, SECTOR_AGBSAVE, 1, 0x07, nand_dst) != 0) ||
(ReadNandBytes(agbsave->savegame, (SECTOR_AGBSAVE+1) * 0x200, agbsave->save_size, 0x07, nand_dst) != 0))
return 1;
u8 cmac[16] __attribute__((aligned(32)));
u8 shasum[32];
sha_quick(shasum, ((u8*) agbsave) + 0x30, (0x200 - 0x30) + agbsave->save_size, SHA256_MODE);
use_aeskey(0x24);
aes_cmac(shasum, cmac, 2);
memcpy(agbsave->cmac, cmac, 16);
// set CFG_BOOTENV = 0x7 so the save is taken over
// https://www.3dbrew.org/wiki/CONFIG_Registers#CFG_BOOTENV
*(u32*) 0x10010000 = 0x7;
return (WriteNandSectors((u8*) agbsave, SECTOR_AGBSAVE, 1, 0x07, nand_dst) != 0) ? 0 : 1;
}

29
source/nand/agbsave.h Normal file
View File

@ -0,0 +1,29 @@
#pragma once
#include "common.h"
#include "nand.h"
#define AGBSAVE_MAGIC '.', 'S', 'A', 'V'
// see: http://3dbrew.org/wiki/3DS_Virtual_Console#NAND_Savegame
typedef struct {
u8 magic[4]; // ".SAV"
u8 reserved0[0xC]; // always 0xFF
u8 cmac[0x10];
u8 reserved1[0x10]; // always 0xFF
u32 unknown0; // always 0x01
u32 times_saved;
u64 title_id;
u8 sd_cid[0x10];
u32 save_start; // always 0x200
u32 save_size;
u8 reserved2[0x8]; // always 0xFF
u32 unknown1; // has to do with ARM7?
u32 unknown2; // has to do with ARM7?
u8 reserved3[0x198]; // always 0xFF
u8 savegame[(SIZE_AGBSAVE-1)*0x200];
} __attribute__((packed)) AgbSave;
u32 GetAgbSaveSize(u32 nand_src);
u32 CheckAgbSaveCmac(u32 nand_src);
u32 FixAgbSaveCmac(u32 nand_dst);

View File

@ -11,12 +11,16 @@
#define NAND_MIN_SECTORS ((GetUnitPlatform() == PLATFORM_N3DS) ? NAND_MIN_SECTORS_N3DS : NAND_MIN_SECTORS_O3DS) #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[0x10] = { 0x00 }; // need to load this from FIRM0 / external file
static 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 u8 nand_magic_n3ds[0x60] = { // NCSD NAND header N3DS magic 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, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x05, 0x00, 0x00, 0x88, 0x05, 0x00, 0x80, 0x01, 0x00, 0x00,
@ -25,7 +29,7 @@ static u8 nand_magic_n3ds[0x60] = { // NCSD NAND header N3DS magic
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
}; };
static u8 nand_magic_o3ds[0x60] = { // NCSD NAND header O3DS magic 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, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x05, 0x00, 0x00, 0x88, 0x05, 0x00, 0x80, 0x01, 0x00, 0x00,
@ -34,7 +38,7 @@ 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 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) 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, 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, 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,
@ -48,6 +52,43 @@ static u8 OtpSha256[32] = { 0 };
static u32 emunand_base_sector = 0x000000; 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
u8 ctr0x15[16] __attribute__((aligned(32)));
u8 keyY0x15[16] __attribute__((aligned(32)));
u8 keyY[16] __attribute__((aligned(32)));
u8 header[0x200];
// check arm9loaderhax
if (!CheckA9lh() || (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);
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);
setup_aeskeyY(0x15, keyY0x15);
use_aeskey(0x15);
ctr_decrypt_byte(keyY, keyY, 0x10, offset - (offsetA9l + 0x800), AES_CNT_CTRNAND_MODE, ctr0x15);
if (key) memcpy(key, keyY, 0x10);
// check the key
u8 shasum[0x32];
sha_quick(shasum, keyY, 16, SHA256_MODE);
if (memcmp(shasum, keyhash, 32) == 0) {
setup_aeskeyY(keyslot, keyY);
use_aeskey(keyslot);
return 0;
}
return 1;
}
bool InitNandCrypto(void) bool InitNandCrypto(void)
{ {
// part #0: KeyX / KeyY for secret sector 0x96 // part #0: KeyX / KeyY for secret sector 0x96
@ -105,55 +146,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 (CheckA9lh()) { // only for a9lh
u8 ctr[16] __attribute__((aligned(32)));
u8 keyY[16] __attribute__((aligned(32)));
u8 header[0x200];
// section 2 header of FIRM0 // keyY 0x05 is encrypted @0x0EB014 in the FIRM90
// this is @0x066A00 in FIRM90 & FIRM81 // keyY 0x05 is encrypted @0x0EB24C in the FIRM81
static u32 offsetSection2 = 0x066A00; if ((LoadKeyYFromP9(slot0x05KeyY, slot0x05KeyY_sha256, 0x0EB014, 0x05) != 0) &&
ReadNandSectors(header, 0x58980 + (offsetSection2 / 0x200), 1, 0x06, NAND_SYSNAND); (LoadKeyYFromP9(slot0x05KeyY, slot0x05KeyY_sha256, 0x0EB24C, 0x05) != 0))
memcpy(keyY, header + 0x10, 0x10); // 0x15 keyY LoadKeyFromFile(slot0x05KeyY, 0x05, 'Y', NULL);
// try FIRM90 & FIRM81 offsets, search for the key // keyY 0x24 is encrypted @0x0E62DC in the FIRM90
for (u32 fver = 0; fver < 2; fver++) { // keyY 0x24 is encrypted @0x0E6514 in the FIRM81
static u32 offset0x05KeyY[2] = { 0x0EB014, 0x0EB24C }; if ((LoadKeyYFromP9(NULL, slot0x24KeyY_sha256, 0x0E62DC, 0x24) != 0) &&
u32 offset = offset0x05KeyY[fver]; (LoadKeyYFromP9(NULL, slot0x24KeyY_sha256, 0x0E6514, 0x24) != 0))
u8 sector[0x200]; LoadKeyFromFile(NULL, 0x24, 'Y', NULL);
// sector containing the slot0x05 keyY
// key is encrypted @0x0EB014 in the FIRM90
// key is encrypted @0x0EB24C in the FIRM81
ReadNandSectors(sector, 0x58980 + (offset / 0x200), 1, 0x06, NAND_SYSNAND);
// decrypt the sector, get the key
memcpy(ctr, header + 0x20, 0x10); // 0x15 counter
add_ctr(ctr, (offset - (offset % 0x200) - (offsetSection2 + 0x800)) / 16);
for (u32 i = 0x0; i < 0x200; i += 0x10) {
setup_aeskeyY(0x15, keyY);
use_aeskey(0x15);
set_ctr(ctr);
aes_decrypt(sector + i, sector + i, 1, AES_CNT_CTRNAND_MODE);
add_ctr(ctr, 0x1);
}
memcpy(slot0x05KeyY, sector + (offset % 0x200), 16);
// check the key
sha_quick(shasum, slot0x05KeyY, 16, SHA256_MODE);
if (memcmp(shasum, slot0x05KeyY_sha256, 32) == 0) {
setup_aeskeyY(0x05, slot0x05KeyY);
use_aeskey(0x05);
break;
}
}
if ((memcmp(shasum, slot0x05KeyY_sha256, 32) != 0) && // last resort
(LoadKeyFromFile(slot0x05KeyY, 0x05, 'Y', NULL) != 0)) {};
}
return true; return true;
} }
@ -191,12 +198,12 @@ bool CheckA9lh(void)
void CryptNand(u8* buffer, u32 sector, u32 count, u32 keyslot) void CryptNand(u8* buffer, u32 sector, u32 count, u32 keyslot)
{ {
u32 mode = (sector >= (0x0B100000 / 0x200)) ? AES_CNT_CTRNAND_MODE : AES_CNT_TWLNAND_MODE; u32 mode = (sector >= SECTOR_TWL + SIZE_TWL) ? AES_CNT_CTRNAND_MODE : AES_CNT_TWLNAND_MODE;
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 >= (0x0B100000 / 0x200)) ? CtrNandCtr : TwlNandCtr, 16); memcpy(ctr, (sector >= SECTOR_TWL + SIZE_TWL) ? CtrNandCtr : TwlNandCtr, 16);
add_ctr(ctr, sector * (0x200 / 0x10)); add_ctr(ctr, sector * (0x200 / 0x10));
// decrypt the data // decrypt the data
@ -214,8 +221,7 @@ void CryptSector0x96(u8* buffer, bool encrypt)
// decrypt the sector // decrypt the sector
use_aeskey(0x11); use_aeskey(0x11);
for (u32 b = 0x0; b < 0x200; b += 0x10, buffer += 0x10) ecb_decrypt((void*) buffer, (void*) buffer, 0x200 / AES_BLOCK_SIZE, mode);
aes_decrypt((void*) buffer, (void*) buffer, 1, mode);
} }
int ReadNandBytes(u8* buffer, u32 offset, u32 count, u32 keyslot, u32 nand_src) int ReadNandBytes(u8* buffer, u32 offset, u32 count, u32 keyslot, u32 nand_src)

View File

@ -10,9 +10,30 @@
#define NAND_TYPE_N3DS (1<<5) #define NAND_TYPE_N3DS (1<<5)
#define NAND_TYPE_NO3DS (1<<6) #define NAND_TYPE_NO3DS (1<<6)
// minimum size of NAND memory
#define NAND_MIN_SECTORS_O3DS 0x1D7800 #define NAND_MIN_SECTORS_O3DS 0x1D7800
#define NAND_MIN_SECTORS_N3DS 0x26C000 #define NAND_MIN_SECTORS_N3DS 0x26C000
// start sectors of partitions
#define SECTOR_TWL 0x000000
#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
bool InitNandCrypto(void); bool InitNandCrypto(void);
bool CheckSlot0x05Crypto(void); bool CheckSlot0x05Crypto(void);
bool CheckSector0x96Crypto(void); bool CheckSector0x96Crypto(void);

View File

@ -20,8 +20,8 @@ u32 ReadNandFile(FIL* file, void* buffer, u32 sector, u32 count, u32 keyslot) {
} }
u32 ValidateNandDump(const char* path) { u32 ValidateNandDump(const char* path) {
const u32 mbr_sectors[] = { TWL_OFFSET, CTR_OFFSET }; const u32 mbr_sectors[] = { SECTOR_TWL, SECTOR_CTR };
const u32 firm_sectors[] = { FIRM_OFFSETS }; const u32 firm_sectors[] = { SECTOR_FIRM0, SECTOR_FIRM1 };
u8 buffer[0x200]; u8 buffer[0x200];
FirmHeader firm; FirmHeader firm;
MbrHeader mbr; MbrHeader mbr;

View File

@ -2,11 +2,8 @@
#include "common.h" #include "common.h"
#define TWL_OFFSET 0x000000 #define SAFE_SECTORS 0x000000 + 0x1, SECTOR_SECRET, SECTOR_SECRET + 0x1, \
#define CTR_OFFSET 0x05C980 SECTOR_FIRM0, SECTOR_CTR, 0x000000 // last one is a placeholder
#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 ValidateNandDump(const char* path);
u32 SafeRestoreNandDump(const char* path); u32 SafeRestoreNandDump(const char* path);

View File

@ -1,15 +1,18 @@
#include "vnand.h" #include "vnand.h"
#include "nand.h" #include "nand.h"
#include "agbsave.h"
#include "platform.h" #include "platform.h"
#define VFLAG_ON_O3DS NAND_TYPE_O3DS #define VFLAG_ON_O3DS NAND_TYPE_O3DS
#define VFLAG_ON_N3DS NAND_TYPE_N3DS #define VFLAG_ON_N3DS NAND_TYPE_N3DS
#define VFLAG_ON_NO3DS NAND_TYPE_NO3DS #define VFLAG_ON_NO3DS NAND_TYPE_NO3DS
#define VFLAG_ON_NAND (VFLAG_ON_O3DS | VFLAG_ON_N3DS | VFLAG_ON_NO3DS) #define VFLAG_ON_NAND (VFLAG_ON_O3DS | VFLAG_ON_N3DS | VFLAG_ON_NO3DS)
#define VFLAG_GBA_VC (1<<29)
#define VFLAG_NEEDS_OTP (1<<30) #define VFLAG_NEEDS_OTP (1<<30)
#define VFLAG_NAND_SIZE (1<<31) #define VFLAG_NAND_SIZE (1<<31)
// see: http://3dbrew.org/wiki/Flash_Filesystem#NAND_structure // see: http://3dbrew.org/wiki/Flash_Filesystem#NAND_structure
// too much hardcoding, but more readable this way
static const VirtualFile vNandFileTemplates[] = { static const VirtualFile vNandFileTemplates[] = {
{ "twln.bin" , 0x00012E00, 0x08FB5200, 0x03, VFLAG_ON_NAND }, { "twln.bin" , 0x00012E00, 0x08FB5200, 0x03, VFLAG_ON_NAND },
{ "twlp.bin" , 0x09011A00, 0x020B6600, 0x03, VFLAG_ON_NAND }, { "twlp.bin" , 0x09011A00, 0x020B6600, 0x03, VFLAG_ON_NAND },
@ -28,7 +31,8 @@ static const VirtualFile vNandFileTemplates[] = {
{ "nand_minsize.bin" , 0x00000000, 0x4D800000, 0xFF, VFLAG_ON_N3DS | VFLAG_ON_NO3DS | VFLAG_A9LH_AREA }, { "nand_minsize.bin" , 0x00000000, 0x4D800000, 0xFF, VFLAG_ON_N3DS | VFLAG_ON_NO3DS | VFLAG_A9LH_AREA },
{ "nand_hdr.bin" , 0x00000000, 0x00000200, 0xFF, VFLAG_ON_NAND | VFLAG_A9LH_AREA }, { "nand_hdr.bin" , 0x00000000, 0x00000200, 0xFF, VFLAG_ON_NAND | VFLAG_A9LH_AREA },
{ "twlmbr.bin" , 0x000001BE, 0x00000042, 0x03, VFLAG_ON_NAND | VFLAG_A9LH_AREA }, { "twlmbr.bin" , 0x000001BE, 0x00000042, 0x03, VFLAG_ON_NAND | VFLAG_A9LH_AREA },
{ "unused.bin" , 0x00000200, 0x00012A00, 0xFF, VFLAG_ON_NAND } { "unused.bin" , 0x00000200, 0x00012A00, 0xFF, VFLAG_ON_NAND },
{ "gbavc.sav" , 0x0B100200, 0x00000000, 0x07, VFLAG_ON_NAND | VFLAG_GBA_VC },
}; };
bool CheckVNandDrive(u32 nand_src) { bool CheckVNandDrive(u32 nand_src) {
@ -51,7 +55,8 @@ bool ReadVNandDir(VirtualFile* vfile, VirtualDir* vdir) { // uses a generic vdir
// XORpad drive handling // XORpad drive handling
if (nand_src == VRT_XORPAD) { if (nand_src == VRT_XORPAD) {
snprintf(vfile->name, 32, "%s.xorpad", templates[vdir->index].name); snprintf(vfile->name, 32, "%s.xorpad", templates[vdir->index].name);
if ((vfile->keyslot == 0x11) || (vfile->keyslot >= 0x40)) continue; if ((vfile->keyslot == 0x11) || (vfile->keyslot >= 0x40) || (vfile->flags & VFLAG_GBA_VC))
continue;
} }
// process / check special flags // process / check special flags
@ -68,6 +73,10 @@ bool ReadVNandDir(VirtualFile* vfile, VirtualDir* vdir) { // uses a generic vdir
continue; // EmuNAND/ImgNAND is too small continue; // EmuNAND/ImgNAND is too small
vfile->size = GetNandSizeSectors(NAND_SYSNAND) * 0x200; vfile->size = GetNandSizeSectors(NAND_SYSNAND) * 0x200;
} }
if (vfile->flags & VFLAG_GBA_VC) {
if (CheckAgbSaveCmac(nand_src) != 0) continue;
vfile->size = GetAgbSaveSize(nand_src);
}
// found if arriving here // found if arriving here
vfile->flags |= nand_src; vfile->flags |= nand_src;
@ -84,5 +93,7 @@ int ReadVNandFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count) {
int WriteVNandFile(const VirtualFile* vfile, const u8* buffer, u32 offset, u32 count) { int WriteVNandFile(const VirtualFile* vfile, const u8* buffer, u32 offset, u32 count) {
u32 nand_dst = vfile->flags & (VRT_SYSNAND|VRT_EMUNAND|VRT_IMGNAND|VRT_XORPAD); u32 nand_dst = vfile->flags & (VRT_SYSNAND|VRT_EMUNAND|VRT_IMGNAND|VRT_XORPAD);
return WriteNandBytes(buffer, vfile->offset + offset, count, vfile->keyslot, nand_dst); int res = WriteNandBytes(buffer, vfile->offset + offset, count, vfile->keyslot, nand_dst);
if ((res == 0) && (vfile->flags & VFLAG_GBA_VC)) res = FixAgbSaveCmac(nand_dst);
return res;
} }