2016-12-10 15:32:03 +01:00
|
|
|
#include "nand.h"
|
|
|
|
#include "fsdrive.h"
|
|
|
|
#include "fsutil.h"
|
2017-02-26 13:35:37 +01:00
|
|
|
#include "unittype.h"
|
2016-12-05 22:54:36 +01:00
|
|
|
#include "keydb.h"
|
2016-03-03 13:27:51 +01:00
|
|
|
#include "aes.h"
|
|
|
|
#include "sha.h"
|
2017-05-26 00:50:31 +02:00
|
|
|
#include "fatmbr.h"
|
2016-03-03 13:27:51 +01:00
|
|
|
#include "sdmmc.h"
|
2016-04-04 22:45:49 +02:00
|
|
|
#include "image.h"
|
2016-03-03 13:27:51 +01:00
|
|
|
|
|
|
|
|
2017-05-18 12:06:01 +02:00
|
|
|
#define KEY95_SHA256 ((IS_DEVKIT) ? slot0x11Key95dev_sha256 : slot0x11Key95_sha256)
|
|
|
|
#define SECTOR_SHA256 ((IS_DEVKIT) ? sector0x96dev_sha256 : sector0x96_sha256)
|
|
|
|
|
2017-05-26 00:50:31 +02:00
|
|
|
// 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)
|
|
|
|
};
|
|
|
|
|
2016-07-13 20:58:14 +02:00
|
|
|
static u8 slot0x05KeyY[0x10] = { 0x00 }; // need to load this from FIRM0 / external file
|
2017-01-05 02:50:41 +01:00
|
|
|
static const u8 slot0x05KeyY_sha256[0x20] = { // hash for slot0x05KeyY (16 byte)
|
2016-03-22 19:24:21 +01:00
|
|
|
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
|
|
|
|
};
|
2017-01-05 02:50:41 +01:00
|
|
|
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
|
|
|
|
};
|
2017-02-03 02:21:55 +01:00
|
|
|
|
|
|
|
static const u8 slot0x11Key95_sha256[0x20] = { // slot0x11Key95 hash (first 16 byte of sector0x96)
|
|
|
|
0xBA, 0xC1, 0x40, 0x9C, 0x6E, 0xE4, 0x1F, 0x04, 0xAA, 0xC4, 0xE2, 0x09, 0x5C, 0xE9, 0x4F, 0x78,
|
|
|
|
0x6C, 0x78, 0x5F, 0xAC, 0xEC, 0x7E, 0xC0, 0x11, 0x26, 0x9D, 0x4E, 0x47, 0xB3, 0x64, 0xC4, 0xA5
|
|
|
|
};
|
2017-04-12 00:27:02 +02:00
|
|
|
|
2017-05-18 12:06:01 +02:00
|
|
|
static const u8 slot0x11Key95dev_sha256[0x20] = { // slot0x11Key95 hash (first 16 byte of sector0x96)
|
|
|
|
0x97, 0x0E, 0x52, 0x29, 0x63, 0x19, 0x47, 0x51, 0x15, 0xD8, 0x02, 0x7A, 0x22, 0x0F, 0x58, 0x15,
|
|
|
|
0xD7, 0x6C, 0xE9, 0xAD, 0xE7, 0xFE, 0x9A, 0x25, 0x4E, 0x4A, 0x0C, 0x82, 0x67, 0xB5, 0x4A, 0x7B
|
|
|
|
};
|
|
|
|
|
2017-04-12 00:27:02 +02:00
|
|
|
// from: https://github.com/AuroraWright/SafeA9LHInstaller/blob/master/source/installer.c#L9-L17
|
|
|
|
static const u8 sector0x96_sha256[0x20] = { // hash for legit sector 0x96 (different on A9LH)
|
|
|
|
0x82, 0xF2, 0x73, 0x0D, 0x2C, 0x2D, 0xA3, 0xF3, 0x01, 0x65, 0xF9, 0x87, 0xFD, 0xCC, 0xAC, 0x5C,
|
|
|
|
0xBA, 0xB2, 0x4B, 0x4E, 0x5F, 0x65, 0xC9, 0x81, 0xCD, 0x7B, 0xE6, 0xF4, 0x38, 0xE6, 0xD9, 0xD3
|
|
|
|
};
|
2017-05-18 12:06:01 +02:00
|
|
|
|
|
|
|
// from: https://github.com/SciresM/CTRAesEngine/tree/master/CTRAesEngine/Resources/_byte
|
|
|
|
static const u8 sector0x96dev_sha256[0x20] = { // hash for legit sector 0x96 (different on A9LH)
|
|
|
|
0xB2, 0x91, 0xD9, 0xB1, 0x33, 0x05, 0x79, 0x0D, 0x47, 0xC6, 0x06, 0x98, 0x4C, 0x67, 0xC3, 0x70,
|
|
|
|
0x09, 0x54, 0xE3, 0x85, 0xDE, 0x47, 0x55, 0xAF, 0xC6, 0xCB, 0x1D, 0x8D, 0xC7, 0x84, 0x5A, 0x64
|
|
|
|
};
|
2017-03-02 22:40:58 +01:00
|
|
|
|
2016-03-03 13:27:51 +01:00
|
|
|
static u8 CtrNandCtr[16];
|
|
|
|
static u8 TwlNandCtr[16];
|
2016-05-15 18:16:45 +02:00
|
|
|
static u8 OtpSha256[32] = { 0 };
|
2016-03-03 13:27:51 +01:00
|
|
|
|
|
|
|
static u32 emunand_base_sector = 0x000000;
|
|
|
|
|
2017-01-05 02:50:41 +01:00
|
|
|
|
|
|
|
u32 LoadKeyYFromP9(u8* key, const u8* keyhash, u32 offset, u32 keyslot)
|
|
|
|
{
|
2017-05-26 00:50:31 +02:00
|
|
|
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)
|
2017-01-05 02:50:41 +01:00
|
|
|
u8 ctr0x15[16] __attribute__((aligned(32)));
|
|
|
|
u8 keyY0x15[16] __attribute__((aligned(32)));
|
|
|
|
u8 keyY[16] __attribute__((aligned(32)));
|
|
|
|
u8 header[0x200];
|
|
|
|
|
|
|
|
// check arm9loaderhax
|
2017-05-26 00:50:31 +02:00
|
|
|
if (!IS_A9LH || IS_SIGHAX || (offset < (offsetA9l + 0x0800))) return 1;
|
2017-01-05 02:50:41 +01:00
|
|
|
|
|
|
|
// section 2 (arm9loader) header of FIRM
|
|
|
|
// this is @0x066A00 in FIRM90 & FIRM81
|
2017-05-26 00:50:31 +02:00
|
|
|
ReadNandBytes(header, (sector_firm0 * 0x200) + offsetA9l, 0x200, 0x06, NAND_SYSNAND);
|
2017-01-05 02:50:41 +01:00
|
|
|
memcpy(keyY0x15, header + 0x10, 0x10); // 0x15 keyY
|
|
|
|
memcpy(ctr0x15, header + 0x20, 0x10); // 0x15 counter
|
|
|
|
|
|
|
|
// read and decrypt the encrypted keyY
|
2017-05-26 00:50:31 +02:00
|
|
|
ReadNandBytes(keyY, (sector_firm0 * 0x200) + offset, 0x10, 0x06, NAND_SYSNAND);
|
2017-01-05 02:50:41 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-03-03 13:27:51 +01:00
|
|
|
bool InitNandCrypto(void)
|
2016-06-10 02:28:13 +02:00
|
|
|
{
|
2016-05-15 18:16:45 +02:00
|
|
|
// part #0: KeyX / KeyY for secret sector 0x96
|
|
|
|
// on a9lh this MUST be run before accessing the SHA register in any other way
|
2017-05-17 13:57:12 +02:00
|
|
|
if (IS_UNLOCKED) { // if OTP is unlocked
|
|
|
|
// see: https://www.3dbrew.org/wiki/OTP_Registers
|
|
|
|
sha_quick(OtpSha256, (u8*) 0x10012000, 0x90, SHA256_MODE);
|
|
|
|
} else if (IS_A9LH) { // for a9lh
|
2016-05-15 18:16:45 +02:00
|
|
|
// store the current SHA256 from register
|
2017-01-05 02:50:41 +01:00
|
|
|
memcpy(OtpSha256, (void*) REG_SHAHASH, 32);
|
2016-05-15 18:16:45 +02:00
|
|
|
} else {
|
2017-05-26 00:50:31 +02:00
|
|
|
// load hash via keys?
|
2016-12-05 22:54:36 +01:00
|
|
|
const char* base[] = { INPUT_PATHS };
|
|
|
|
char path[64];
|
2016-05-15 18:16:45 +02:00
|
|
|
u8 otp[0x100];
|
2016-12-05 22:54:36 +01:00
|
|
|
for (u32 i = 0; i < 2 * (sizeof(base)/sizeof(char*)); i++) {
|
2017-04-17 23:38:30 +02:00
|
|
|
snprintf(path, 64, "%s/%s", base[i/2], (i%2) ? OTP_BIG_NAME : OTP_NAME);
|
2016-12-05 22:54:36 +01:00
|
|
|
if (FileGetData(path, otp, 0x100, 0) == 0x100) {
|
|
|
|
sha_quick(OtpSha256, otp, 0x90, SHA256_MODE);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-05-15 18:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// part #1: Get NAND CID, set up TWL/CTR counter
|
2016-07-26 20:58:41 +02:00
|
|
|
u32 NandCid[4];
|
2016-03-03 13:27:51 +01:00
|
|
|
u8 shasum[32];
|
|
|
|
|
2017-02-03 02:21:55 +01:00
|
|
|
sdmmc_sdcard_init();
|
|
|
|
sdmmc_get_cid(1, NandCid);
|
2016-07-26 20:58:41 +02:00
|
|
|
sha_quick(shasum, (u8*) NandCid, 16, SHA256_MODE);
|
2016-03-03 13:27:51 +01:00
|
|
|
memcpy(CtrNandCtr, shasum, 16);
|
2016-07-26 20:58:41 +02:00
|
|
|
sha_quick(shasum, (u8*) NandCid, 16, SHA1_MODE);
|
2016-03-03 13:27:51 +01:00
|
|
|
for(u32 i = 0; i < 16; i++) // little endian and reversed order
|
|
|
|
TwlNandCtr[i] = shasum[15-i];
|
|
|
|
|
2016-03-11 01:29:14 +01:00
|
|
|
// part #2: TWL KEY
|
|
|
|
// see: https://www.3dbrew.org/wiki/Memory_layout#ARM9_ITCM
|
2017-02-26 13:35:37 +01:00
|
|
|
if (IS_A9LH) { // only for a9lh
|
2016-04-14 11:28:08 +02:00
|
|
|
u32* TwlCustId = (u32*) (0x01FFB808);
|
2016-12-08 22:08:19 +01:00
|
|
|
u8 TwlKeyX[16] __attribute__((aligned(32)));
|
|
|
|
u8 TwlKeyY[16] __attribute__((aligned(32)));
|
2016-04-14 11:28:08 +02:00
|
|
|
|
|
|
|
// thanks b1l1s & Normmatt
|
|
|
|
// see source from https://gbatemp.net/threads/release-twltool-dsi-downgrading-save-injection-etc-multitool.393488/
|
|
|
|
const char* nintendo = "NINTENDO";
|
2016-07-26 20:58:41 +02:00
|
|
|
u32 TwlKeyXW0 = (TwlCustId[0] ^ 0xB358A6AF) | 0x80000000;
|
|
|
|
u32 TwlKeyXW3 = TwlCustId[1] ^ 0x08C267B7;
|
|
|
|
memcpy(TwlKeyX + 4, nintendo, 8);
|
|
|
|
memcpy(TwlKeyX + 0, &TwlKeyXW0, 4);
|
|
|
|
memcpy(TwlKeyX + 12, &TwlKeyXW3, 4);
|
2016-04-14 11:28:08 +02:00
|
|
|
|
|
|
|
// see: https://www.3dbrew.org/wiki/Memory_layout#ARM9_ITCM
|
|
|
|
u32 TwlKeyYW3 = 0xE1A00005;
|
|
|
|
memcpy(TwlKeyY, (u8*) 0x01FFD3C8, 12);
|
|
|
|
memcpy(TwlKeyY + 12, &TwlKeyYW3, 4);
|
|
|
|
|
|
|
|
setup_aeskeyX(0x03, TwlKeyX);
|
|
|
|
setup_aeskeyY(0x03, TwlKeyY);
|
|
|
|
use_aeskey(0x03);
|
|
|
|
}
|
2016-03-11 01:29:14 +01:00
|
|
|
|
2017-01-05 02:50:41 +01:00
|
|
|
// part #3: CTRNAND N3DS KEY / AGBSAVE CMAC KEY
|
2016-04-25 22:10:22 +02:00
|
|
|
// thanks AuroraWright and Gelex for advice on this
|
|
|
|
// see: https://github.com/AuroraWright/Luma3DS/blob/master/source/crypto.c#L347
|
2017-01-05 02:50:41 +01:00
|
|
|
|
|
|
|
// keyY 0x05 is encrypted @0x0EB014 in the FIRM90
|
|
|
|
// keyY 0x05 is encrypted @0x0EB24C in the FIRM81
|
|
|
|
if ((LoadKeyYFromP9(slot0x05KeyY, slot0x05KeyY_sha256, 0x0EB014, 0x05) != 0) &&
|
|
|
|
(LoadKeyYFromP9(slot0x05KeyY, slot0x05KeyY_sha256, 0x0EB24C, 0x05) != 0))
|
|
|
|
LoadKeyFromFile(slot0x05KeyY, 0x05, 'Y', NULL);
|
|
|
|
|
|
|
|
// keyY 0x24 is encrypted @0x0E62DC in the FIRM90
|
|
|
|
// keyY 0x24 is encrypted @0x0E6514 in the FIRM81
|
|
|
|
if ((LoadKeyYFromP9(NULL, slot0x24KeyY_sha256, 0x0E62DC, 0x24) != 0) &&
|
|
|
|
(LoadKeyYFromP9(NULL, slot0x24KeyY_sha256, 0x0E6514, 0x24) != 0))
|
|
|
|
LoadKeyFromFile(NULL, 0x24, 'Y', NULL);
|
2016-03-11 01:29:14 +01:00
|
|
|
|
2016-03-03 13:27:51 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-03-22 19:24:21 +01:00
|
|
|
bool CheckSlot0x05Crypto(void)
|
|
|
|
{
|
|
|
|
// step #1 - check the slot0x05KeyY SHA-256
|
2017-02-03 02:21:55 +01:00
|
|
|
if (sha_cmp(slot0x05KeyY_sha256, slot0x05KeyY, 16, SHA256_MODE) == 0)
|
2016-03-22 19:24:21 +01:00
|
|
|
return true;
|
|
|
|
|
|
|
|
// step #2 - check actual CTRNAND magic
|
|
|
|
const u8 magic[8] = {0xE9, 0x00, 0x00, 0x43, 0x54, 0x52, 0x20, 0x20};
|
|
|
|
const u32 sector = 0x05CAD7;
|
|
|
|
u8 buffer[0x200];
|
2016-04-04 22:45:49 +02:00
|
|
|
ReadNandSectors(buffer, sector, 1, 0x05, NAND_SYSNAND);
|
|
|
|
if (memcmp(buffer, magic, 8) == 0)
|
|
|
|
return true;
|
2016-03-22 19:24:21 +01:00
|
|
|
|
|
|
|
// failed if we arrive here
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-05-15 18:16:45 +02:00
|
|
|
bool CheckSector0x96Crypto(void)
|
|
|
|
{
|
2017-02-03 02:21:55 +01:00
|
|
|
u8 buffer[0x200];
|
2017-05-26 00:50:31 +02:00
|
|
|
ReadNandSectors(buffer, SECTOR_SECRET, 1, 0x11, NAND_SYSNAND);
|
2017-05-18 12:06:01 +02:00
|
|
|
return (sha_cmp(KEY95_SHA256, buffer, 16, SHA256_MODE) == 0);
|
2016-05-15 18:16:45 +02:00
|
|
|
}
|
|
|
|
|
2016-03-03 13:27:51 +01:00
|
|
|
void CryptNand(u8* buffer, u32 sector, u32 count, u32 keyslot)
|
|
|
|
{
|
2017-05-26 00:50:31 +02:00
|
|
|
u32 mode = (keyslot != 0x03) ? AES_CNT_CTRNAND_MODE : AES_CNT_TWLNAND_MODE; // somewhat hacky
|
2016-03-03 13:27:51 +01:00
|
|
|
u8 ctr[16] __attribute__((aligned(32)));
|
2016-07-28 03:35:20 +02:00
|
|
|
u32 blocks = count * (0x200 / 0x10);
|
2016-03-03 13:27:51 +01:00
|
|
|
|
|
|
|
// copy NAND CTR and increment it
|
2017-05-26 00:50:31 +02:00
|
|
|
memcpy(ctr, (keyslot != 0x03) ? CtrNandCtr : TwlNandCtr, 16); // hacky again
|
2017-01-05 02:50:41 +01:00
|
|
|
add_ctr(ctr, sector * (0x200 / 0x10));
|
2016-03-03 13:27:51 +01:00
|
|
|
|
|
|
|
// decrypt the data
|
|
|
|
use_aeskey(keyslot);
|
2016-07-28 03:35:20 +02:00
|
|
|
ctr_decrypt((void*) buffer, (void*) buffer, blocks, mode, ctr);
|
2016-03-03 13:27:51 +01:00
|
|
|
}
|
|
|
|
|
2016-05-15 18:16:45 +02:00
|
|
|
void CryptSector0x96(u8* buffer, bool encrypt)
|
|
|
|
{
|
|
|
|
u32 mode = encrypt ? AES_CNT_ECB_ENCRYPT_MODE : AES_CNT_ECB_DECRYPT_MODE;
|
|
|
|
|
|
|
|
// setup the key
|
|
|
|
setup_aeskeyX(0x11, OtpSha256);
|
|
|
|
setup_aeskeyY(0x11, OtpSha256 + 16);
|
|
|
|
|
|
|
|
// decrypt the sector
|
|
|
|
use_aeskey(0x11);
|
2017-01-05 02:50:41 +01:00
|
|
|
ecb_decrypt((void*) buffer, (void*) buffer, 0x200 / AES_BLOCK_SIZE, mode);
|
2016-05-15 18:16:45 +02:00
|
|
|
}
|
|
|
|
|
2017-02-17 04:01:25 +01:00
|
|
|
int ReadNandBytes(u8* buffer, u64 offset, u64 count, u32 keyslot, u32 nand_src)
|
2016-12-02 14:08:30 +01:00
|
|
|
{
|
|
|
|
if (!(offset % 0x200) && !(count % 0x200)) { // aligned data -> simple case
|
|
|
|
// simple wrapper function for ReadNandSectors(...)
|
|
|
|
return ReadNandSectors(buffer, offset / 0x200, count / 0x200, keyslot, nand_src);
|
|
|
|
} else { // misaligned data -> -___-
|
|
|
|
u8 l_buffer[0x200];
|
|
|
|
int errorcode = 0;
|
|
|
|
if (offset % 0x200) { // handle misaligned offset
|
|
|
|
u32 offset_fix = 0x200 - (offset % 0x200);
|
|
|
|
errorcode = ReadNandSectors(l_buffer, offset / 0x200, 1, keyslot, nand_src);
|
|
|
|
if (errorcode != 0) return errorcode;
|
|
|
|
memcpy(buffer, l_buffer + 0x200 - offset_fix, min(offset_fix, count));
|
|
|
|
if (count <= offset_fix) return 0;
|
|
|
|
offset += offset_fix;
|
|
|
|
buffer += offset_fix;
|
|
|
|
count -= offset_fix;
|
|
|
|
} // offset is now aligned and part of the data is read
|
|
|
|
if (count >= 0x200) { // otherwise this is misaligned and will be handled below
|
|
|
|
errorcode = ReadNandSectors(buffer, offset / 0x200, count / 0x200, keyslot, nand_src);
|
|
|
|
if (errorcode != 0) return errorcode;
|
|
|
|
}
|
|
|
|
if (count % 0x200) { // handle misaligned count
|
|
|
|
u32 count_fix = count % 0x200;
|
|
|
|
errorcode = ReadNandSectors(l_buffer, (offset + count) / 0x200, 1, keyslot, nand_src);
|
|
|
|
if (errorcode != 0) return errorcode;
|
|
|
|
memcpy(buffer + count - count_fix, l_buffer, count_fix);
|
|
|
|
}
|
|
|
|
return errorcode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-17 04:01:25 +01:00
|
|
|
int WriteNandBytes(const u8* buffer, u64 offset, u64 count, u32 keyslot, u32 nand_dst)
|
2016-12-02 14:08:30 +01:00
|
|
|
{
|
|
|
|
if (!(offset % 0x200) && !(count % 0x200)) { // aligned data -> simple case
|
|
|
|
// simple wrapper function for WriteNandSectors(...)
|
|
|
|
return WriteNandSectors(buffer, offset / 0x200, count / 0x200, keyslot, nand_dst);
|
|
|
|
} else { // misaligned data -> -___-
|
|
|
|
u8 l_buffer[0x200];
|
|
|
|
int errorcode = 0;
|
|
|
|
if (offset % 0x200) { // handle misaligned offset
|
|
|
|
u32 offset_fix = 0x200 - (offset % 0x200);
|
|
|
|
errorcode = ReadNandSectors(l_buffer, offset / 0x200, 1, keyslot, nand_dst);
|
|
|
|
if (errorcode != 0) return errorcode;
|
|
|
|
memcpy(l_buffer + 0x200 - offset_fix, buffer, min(offset_fix, count));
|
|
|
|
errorcode = WriteNandSectors((const u8*) l_buffer, offset / 0x200, 1, keyslot, nand_dst);
|
|
|
|
if (errorcode != 0) return errorcode;
|
|
|
|
if (count <= offset_fix) return 0;
|
|
|
|
offset += offset_fix;
|
|
|
|
buffer += offset_fix;
|
|
|
|
count -= offset_fix;
|
|
|
|
} // offset is now aligned and part of the data is written
|
|
|
|
if (count >= 0x200) { // otherwise this is misaligned and will be handled below
|
|
|
|
errorcode = WriteNandSectors(buffer, offset / 0x200, count / 0x200, keyslot, nand_dst);
|
|
|
|
if (errorcode != 0) return errorcode;
|
|
|
|
}
|
|
|
|
if (count % 0x200) { // handle misaligned count
|
|
|
|
u32 count_fix = count % 0x200;
|
|
|
|
errorcode = ReadNandSectors(l_buffer, (offset + count) / 0x200, 1, keyslot, nand_dst);
|
|
|
|
if (errorcode != 0) return errorcode;
|
|
|
|
memcpy(l_buffer, buffer + count - count_fix, count_fix);
|
|
|
|
errorcode = WriteNandSectors((const u8*) l_buffer, (offset + count) / 0x200, 1, keyslot, nand_dst);
|
|
|
|
if (errorcode != 0) return errorcode;
|
|
|
|
}
|
|
|
|
return errorcode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-04 22:45:49 +02:00
|
|
|
int ReadNandSectors(u8* buffer, u32 sector, u32 count, u32 keyslot, u32 nand_src)
|
2016-03-03 13:27:51 +01:00
|
|
|
{
|
2016-04-08 21:02:38 +02:00
|
|
|
if (!count) return 0; // <--- just to be safe
|
2016-04-04 22:45:49 +02:00
|
|
|
if (nand_src == NAND_EMUNAND) { // EmuNAND
|
2016-03-15 16:25:27 +01:00
|
|
|
int errorcode = 0;
|
|
|
|
if ((sector == 0) && (emunand_base_sector % 0x200000 == 0)) { // GW EmuNAND header handling
|
|
|
|
errorcode = sdmmc_sdcard_readsectors(emunand_base_sector + getMMCDevice(0)->total_size, 1, buffer);
|
2016-12-07 00:01:36 +01:00
|
|
|
if ((keyslot < 0x40) && (keyslot != 0x11) && !errorcode) CryptNand(buffer, 0, 1, keyslot);
|
2016-03-15 16:25:27 +01:00
|
|
|
sector = 1;
|
|
|
|
count--;
|
|
|
|
buffer += 0x200;
|
|
|
|
}
|
|
|
|
errorcode = (!errorcode && count) ? sdmmc_sdcard_readsectors(emunand_base_sector + sector, count, buffer) : errorcode;
|
2016-03-03 13:27:51 +01:00
|
|
|
if (errorcode) return errorcode;
|
2016-04-04 22:45:49 +02:00
|
|
|
} else if (nand_src == NAND_IMGNAND) { // ImgNAND
|
|
|
|
int errorcode = ReadImageSectors(buffer, sector, count);
|
|
|
|
if (errorcode) return errorcode;
|
|
|
|
} else if (nand_src == NAND_SYSNAND) { // SysNAND
|
2016-03-03 13:27:51 +01:00
|
|
|
int errorcode = sdmmc_nand_readsectors(sector, count, buffer);
|
|
|
|
if (errorcode) return errorcode;
|
2016-12-02 15:42:05 +01:00
|
|
|
} else if (nand_src == NAND_ZERONAND) { // zero NAND (good for XORpads)
|
|
|
|
memset(buffer, 0, count * 0x200);
|
2016-04-04 22:45:49 +02:00
|
|
|
} else {
|
|
|
|
return -1;
|
2016-03-03 13:27:51 +01:00
|
|
|
}
|
2017-05-26 00:50:31 +02:00
|
|
|
if ((keyslot == 0x11) && (sector == SECTOR_SECRET)) CryptSector0x96(buffer, false);
|
2016-05-15 18:16:45 +02:00
|
|
|
else if (keyslot < 0x40) CryptNand(buffer, sector, count, keyslot);
|
2016-03-03 13:27:51 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-04-04 22:45:49 +02:00
|
|
|
int WriteNandSectors(const u8* buffer, u32 sector, u32 count, u32 keyslot, u32 nand_dst)
|
2016-03-03 13:27:51 +01:00
|
|
|
{
|
|
|
|
// buffer must not be changed, so this is a little complicated
|
|
|
|
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);
|
2017-05-26 00:50:31 +02:00
|
|
|
if ((keyslot == 0x11) && (sector == SECTOR_SECRET)) CryptSector0x96(NAND_BUFFER, true);
|
2016-05-15 18:16:45 +02:00
|
|
|
else if (keyslot < 0x40) CryptNand(NAND_BUFFER, sector + s, pcount, keyslot);
|
2016-04-04 22:45:49 +02:00
|
|
|
if (nand_dst == NAND_EMUNAND) {
|
2016-03-15 16:25:27 +01:00
|
|
|
int errorcode = 0;
|
|
|
|
if ((sector + s == 0) && (emunand_base_sector % 0x200000 == 0)) { // GW EmuNAND header handling
|
|
|
|
errorcode = sdmmc_sdcard_writesectors(emunand_base_sector + getMMCDevice(0)->total_size, 1, NAND_BUFFER);
|
|
|
|
errorcode = (!errorcode && (pcount > 1)) ? sdmmc_sdcard_writesectors(emunand_base_sector + 1, pcount - 1, NAND_BUFFER + 0x200) : errorcode;
|
|
|
|
} else errorcode = sdmmc_sdcard_writesectors(emunand_base_sector + sector + s, pcount, NAND_BUFFER);
|
2016-03-03 13:27:51 +01:00
|
|
|
if (errorcode) return errorcode;
|
2016-04-04 22:45:49 +02:00
|
|
|
} else if (nand_dst == NAND_IMGNAND) {
|
|
|
|
int errorcode = WriteImageSectors(NAND_BUFFER, sector + s, pcount);
|
|
|
|
if (errorcode) return errorcode;
|
|
|
|
} else if (nand_dst == NAND_SYSNAND) {
|
2016-03-03 13:27:51 +01:00
|
|
|
int errorcode = sdmmc_nand_writesectors(sector + s, pcount, NAND_BUFFER);
|
2016-04-04 22:45:49 +02:00
|
|
|
if (errorcode) return errorcode;
|
|
|
|
} else {
|
|
|
|
return -1;
|
2016-03-03 13:27:51 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-05-31 15:48:14 +02:00
|
|
|
// shamelessly stolen from myself
|
|
|
|
// see: https://github.com/d0k3/GodMode9/blob/master/source/game/ncsd.c#L4
|
|
|
|
u32 ValidateNandNcsdHeader(NandNcsdHeader* header)
|
|
|
|
{
|
|
|
|
u8 zeroes[16] = { 0 };
|
|
|
|
if ((memcmp(header->magic, "NCSD", 4) != 0) || // check magic number
|
|
|
|
(memcmp(header->partitions_fs_type, zeroes, 8) == 0) || header->mediaId) // prevent detection of cart NCSD images
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
u32 data_units = 0;
|
|
|
|
u32 firm_count = 0;
|
|
|
|
for (u32 i = 0; i < 8; i++) {
|
|
|
|
NandNcsdPartition* partition = header->partitions + i;
|
|
|
|
u8 np_type = header->partitions_fs_type[i];
|
|
|
|
if ((i == 0) && !partition->size) return 1; // first content must be there
|
|
|
|
else if (!partition->size) continue;
|
|
|
|
if (!np_type) return 1; // partition must have a type
|
|
|
|
if (partition->offset < data_units)
|
|
|
|
return 1; // overlapping partitions, failed
|
|
|
|
data_units = partition->offset + partition->size;
|
|
|
|
if (np_type == NP_TYPE_FIRM) firm_count++; // count firms
|
|
|
|
}
|
|
|
|
if (data_units > header->size) return 1;
|
|
|
|
if (!firm_count) return 1; // at least one firm is required
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 GetNandNcsdMinSizeSectors(NandNcsdHeader* ncsd)
|
2017-05-26 00:50:31 +02:00
|
|
|
{
|
2017-05-31 15:48:14 +02:00
|
|
|
u32 nand_minsize = 0;
|
2017-05-26 00:50:31 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-05-31 15:48:14 +02:00
|
|
|
u32 GetNandMinSizeSectors(u32 nand_src)
|
|
|
|
{
|
|
|
|
NandNcsdHeader ncsd;
|
|
|
|
if ((ReadNandSectors((u8*) &ncsd, 0, 1, 0xFF, nand_src) != 0) ||
|
|
|
|
(ValidateNandNcsdHeader(&ncsd) != 0)) return 0;
|
|
|
|
|
|
|
|
return GetNandNcsdMinSizeSectors(&ncsd);
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 GetNandSizeSectors(u32 nand_src)
|
|
|
|
{
|
|
|
|
u32 sysnand_sectors = getMMCDevice(0)->total_size;
|
|
|
|
if (nand_src == NAND_SYSNAND) return sysnand_sectors; // for SysNAND
|
|
|
|
|
|
|
|
u32 min_sectors = GetNandMinSizeSectors(nand_src);
|
|
|
|
if (nand_src == NAND_EMUNAND) { // for EmuNAND
|
|
|
|
u32 partition_offset = GetPartitionOffsetSector("0:");
|
|
|
|
u32 emunand_max_sectors = (partition_offset >= (emunand_base_sector + 1)) ? // +1 for safety
|
|
|
|
partition_offset - (emunand_base_sector + 1) : 0;
|
|
|
|
u32 emunand_min_sectors = (emunand_base_sector % 0x2000 == 0) ? sysnand_sectors : min_sectors;
|
|
|
|
return (emunand_min_sectors > emunand_max_sectors) ? 0 : emunand_min_sectors;
|
|
|
|
} else if (nand_src == NAND_IMGNAND) { // for images
|
|
|
|
u32 img_sectors = (GetMountState() & IMG_NAND) ? GetMountSize() / 0x200 : 0;
|
|
|
|
return (img_sectors >= min_sectors) ? img_sectors : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-05-26 00:50:31 +02:00
|
|
|
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)
|
|
|
|
{
|
2017-05-31 15:48:14 +02:00
|
|
|
// workaround for info == NULL
|
|
|
|
NandPartitionInfo dummy;
|
|
|
|
if (!info) info = &dummy;
|
|
|
|
|
2017-05-26 00:50:31 +02:00
|
|
|
// 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;
|
2017-05-31 15:48:14 +02:00
|
|
|
if ((ValidateNandNcsdHeader(ncsd) != 0) ||
|
|
|
|
((type == NP_TYPE_FAT) && (GetNandNcsdPartitionInfo(info, NP_TYPE_STD, subtype, 0, ncsd) != 0)) ||
|
2017-05-26 00:50:31 +02:00
|
|
|
((type != NP_TYPE_FAT) && (GetNandNcsdPartitionInfo(info, type, subtype, index, ncsd) != 0)))
|
|
|
|
return 1; // not found
|
|
|
|
|
2017-05-31 15:48:14 +02:00
|
|
|
if (type == NP_TYPE_BONUS) { // size of bonus partition
|
2017-05-26 00:50:31 +02:00
|
|
|
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;
|
2017-02-20 22:59:03 +01:00
|
|
|
}
|
|
|
|
|
2017-04-12 00:27:02 +02:00
|
|
|
u32 GetLegitSector0x96(u8* sector)
|
|
|
|
{
|
|
|
|
// secret sector already in buffer?
|
2017-05-18 12:06:01 +02:00
|
|
|
if (sha_cmp(SECTOR_SHA256, sector, 0x200, SHA256_MODE) == 0)
|
2017-04-12 00:27:02 +02:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
// 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++) {
|
2017-05-26 00:50:31 +02:00
|
|
|
ReadNandSectors(sector, SECTOR_SECRET, 1, 0x11, nand_src[i]);
|
2017-05-18 12:06:01 +02:00
|
|
|
if (sha_cmp(SECTOR_SHA256, sector, 0x200, SHA256_MODE) == 0)
|
2017-04-12 00:27:02 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// no luck? try searching for a file
|
|
|
|
const char* base[] = { INPUT_PATHS };
|
|
|
|
for (u32 i = 0; i < (sizeof(base)/sizeof(char*)); i++) {
|
|
|
|
char path[64];
|
|
|
|
snprintf(path, 64, "%s/%s", base[i], SECTOR_NAME);
|
|
|
|
if ((FileGetData(path, sector, 0x200, 0) == 0x200) &&
|
2017-05-18 12:06:01 +02:00
|
|
|
(sha_cmp(SECTOR_SHA256, sector, 0x200, SHA256_MODE) == 0))
|
2017-04-12 00:27:02 +02:00
|
|
|
return 0;
|
|
|
|
snprintf(path, 64, "%s/%s", base[i], SECRET_NAME);
|
|
|
|
if ((FileGetData(path, sector, 0x200, 0) == 0x200) &&
|
2017-05-18 12:06:01 +02:00
|
|
|
(sha_cmp(SECTOR_SHA256, sector, 0x200, SHA256_MODE) == 0))
|
2017-04-12 00:27:02 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// failed if we arrive here
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2017-04-19 14:14:02 +02:00
|
|
|
// OTP hash is 32 byte in size
|
|
|
|
u32 GetOtpHash(void* hash) {
|
|
|
|
if (!CheckSector0x96Crypto()) return 1;
|
|
|
|
memcpy(hash, OtpSha256, 0x20);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// NAND CID is 16 byte in size
|
|
|
|
u32 GetNandCid(void* cid) {
|
|
|
|
sdmmc_get_cid(1, (u32*) cid);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-01-17 21:44:16 +01:00
|
|
|
bool CheckMultiEmuNand(void)
|
2016-03-16 18:46:05 +01:00
|
|
|
{
|
2017-01-17 21:44:16 +01:00
|
|
|
// this only checks for the theoretical possibility
|
2017-05-31 15:48:14 +02:00
|
|
|
u32 emunand_min_sectors = GetNandMinSizeSectors(NAND_EMUNAND);
|
|
|
|
return (emunand_min_sectors && (GetPartitionOffsetSector("0:") >= (u64) (align(emunand_min_sectors + 1, 0x2000) * 2)));
|
2017-01-17 21:44:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
u32 InitEmuNandBase(bool reset)
|
|
|
|
{
|
|
|
|
if (!reset) {
|
|
|
|
u32 last_valid = emunand_base_sector;
|
2017-05-31 15:48:14 +02:00
|
|
|
u32 emunand_min_sectors = GetNandMinSizeSectors(NAND_EMUNAND);
|
2017-01-17 21:44:16 +01:00
|
|
|
|
|
|
|
// legacy type multiNAND
|
|
|
|
u32 legacy_sectors = (getMMCDevice(0)->total_size > 0x200000) ? 0x400000 : 0x200000;
|
|
|
|
emunand_base_sector += legacy_sectors - (emunand_base_sector % legacy_sectors);
|
2017-05-31 15:48:14 +02:00
|
|
|
if (GetNandSizeSectors(NAND_EMUNAND) && (GetNandPartitionInfo(NULL, NP_TYPE_NCSD, NP_SUBTYPE_CTR, 0, NAND_EMUNAND) == 0))
|
2017-01-17 21:44:16 +01:00
|
|
|
return emunand_base_sector; // GW type EmuNAND
|
|
|
|
emunand_base_sector++;
|
2017-05-31 15:48:14 +02:00
|
|
|
if (GetNandSizeSectors(NAND_EMUNAND) && (GetNandPartitionInfo(NULL, NP_TYPE_NCSD, NP_SUBTYPE_CTR, 0, NAND_EMUNAND) == 0))
|
2017-01-17 21:44:16 +01:00
|
|
|
return emunand_base_sector; // RedNAND type EmuNAND
|
|
|
|
|
|
|
|
// compact type multiNAND
|
2017-05-31 15:48:14 +02:00
|
|
|
if (emunand_min_sectors && (last_valid % 0x2000 <= 1)) {
|
|
|
|
u32 compact_sectors = align(emunand_min_sectors + 1, 0x2000);
|
2017-01-17 21:44:16 +01:00
|
|
|
emunand_base_sector = last_valid + compact_sectors;
|
2017-05-31 15:48:14 +02:00
|
|
|
if (GetNandSizeSectors(NAND_EMUNAND) && (GetNandPartitionInfo(NULL, NP_TYPE_NCSD, NP_SUBTYPE_CTR, 0, NAND_EMUNAND) == 0))
|
2017-01-17 21:44:16 +01:00
|
|
|
return emunand_base_sector;
|
|
|
|
}
|
|
|
|
}
|
2016-03-16 18:46:05 +01:00
|
|
|
|
2017-01-17 21:44:16 +01:00
|
|
|
emunand_base_sector = 0x000000; // GW type EmuNAND
|
2017-05-31 15:48:14 +02:00
|
|
|
if (GetNandPartitionInfo(NULL, NP_TYPE_NCSD, NP_SUBTYPE_CTR, 0, NAND_EMUNAND) != 0)
|
2017-01-17 21:44:16 +01:00
|
|
|
emunand_base_sector = 0x000001; // RedNAND type EmuNAND
|
2016-03-16 18:46:05 +01:00
|
|
|
|
2017-01-17 21:44:16 +01:00
|
|
|
return emunand_base_sector;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 GetEmuNandBase(void)
|
|
|
|
{
|
|
|
|
return emunand_base_sector;
|
2016-03-16 18:46:05 +01:00
|
|
|
}
|