mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 13:42:47 +00:00
Handle GBA VC save in virtual NAND drives
This commit is contained in:
parent
9432723791
commit
d0785b12d1
48
source/nand/agbsave.c
Normal file
48
source/nand/agbsave.c
Normal 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
29
source/nand/agbsave.h
Normal 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);
|
@ -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,13 +52,50 @@ 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
|
||||||
// on a9lh this MUST be run before accessing the SHA register in any other way
|
// on a9lh this MUST be run before accessing the SHA register in any other way
|
||||||
if (CheckA9lh()) { // for a9lh
|
if (CheckA9lh()) { // for a9lh
|
||||||
// store the current SHA256 from register
|
// store the current SHA256 from register
|
||||||
memcpy(OtpSha256, (void*)REG_SHAHASH, 32);
|
memcpy(OtpSha256, (void*) REG_SHAHASH, 32);
|
||||||
} else {
|
} else {
|
||||||
const char* base[] = { INPUT_PATHS };
|
const char* base[] = { INPUT_PATHS };
|
||||||
char path[64];
|
char path[64];
|
||||||
@ -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)));
|
// keyY 0x05 is encrypted @0x0EB014 in the FIRM90
|
||||||
u8 keyY[16] __attribute__((aligned(32)));
|
// keyY 0x05 is encrypted @0x0EB24C in the FIRM81
|
||||||
u8 header[0x200];
|
if ((LoadKeyYFromP9(slot0x05KeyY, slot0x05KeyY_sha256, 0x0EB014, 0x05) != 0) &&
|
||||||
|
(LoadKeyYFromP9(slot0x05KeyY, slot0x05KeyY_sha256, 0x0EB24C, 0x05) != 0))
|
||||||
// section 2 header of FIRM0
|
LoadKeyFromFile(slot0x05KeyY, 0x05, 'Y', NULL);
|
||||||
// this is @0x066A00 in FIRM90 & FIRM81
|
|
||||||
static u32 offsetSection2 = 0x066A00;
|
// keyY 0x24 is encrypted @0x0E62DC in the FIRM90
|
||||||
ReadNandSectors(header, 0x58980 + (offsetSection2 / 0x200), 1, 0x06, NAND_SYSNAND);
|
// keyY 0x24 is encrypted @0x0E6514 in the FIRM81
|
||||||
memcpy(keyY, header + 0x10, 0x10); // 0x15 keyY
|
if ((LoadKeyYFromP9(NULL, slot0x24KeyY_sha256, 0x0E62DC, 0x24) != 0) &&
|
||||||
|
(LoadKeyYFromP9(NULL, slot0x24KeyY_sha256, 0x0E6514, 0x24) != 0))
|
||||||
// try FIRM90 & FIRM81 offsets, search for the key
|
LoadKeyFromFile(NULL, 0x24, 'Y', NULL);
|
||||||
for (u32 fver = 0; fver < 2; fver++) {
|
|
||||||
static u32 offset0x05KeyY[2] = { 0x0EB014, 0x0EB24C };
|
|
||||||
u32 offset = offset0x05KeyY[fver];
|
|
||||||
u8 sector[0x200];
|
|
||||||
|
|
||||||
// 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,13 +198,13 @@ 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
|
||||||
use_aeskey(keyslot);
|
use_aeskey(keyslot);
|
||||||
@ -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)
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user