27 Commits

Author SHA1 Message Date
e578e09ff9 v1.7.0: Titlekey save parsing, memory bugfixes 2019-10-28 09:56:38 -06:00
6ad7192199 Move key offsets to struct for cleaner code 2019-10-28 09:54:01 -06:00
1c82665901 Parse ES saves right, vastly improve titlekey time 2019-10-26 20:29:36 -06:00
1feb83f1dc heap: Fix calloc memset, end node max size on free 2019-10-25 22:01:23 -06:00
5d44ef0af6 se: Hardcode internal se vars, add HMAC-SHA256 2019-10-25 21:48:29 -06:00
5f100fef8a nx_emmc: Fix nested loop with same variable 2019-10-25 12:04:03 -06:00
a5bb071927 keys: Fix potential memory leak in _nca_process 2019-10-25 11:59:34 -06:00
dbc86a6699 keys: Ensure SD mount before writing key buffer 2019-10-25 11:57:59 -06:00
86f2a63fb7 keys: Fix failure to free decompressed kip1 2019-10-25 11:56:06 -06:00
535a2d97f2 keys: Validate storage init 2019-10-25 11:54:57 -06:00
d946ade94b kfuse: Fix missing include 2019-10-25 11:46:10 -06:00
472aa1665e sept: Prevent memory leak 2019-10-25 11:44:51 -06:00
2d4ffd2965 diskio: Alloc xts enc, don't realloc sector cache 2019-10-25 11:43:11 -06:00
b468026540 heap: Prevent node chain collapse on free 2019-10-25 11:41:30 -06:00
e5849e3ab2 Remove invalid free for this use case 2019-10-25 10:05:57 -06:00
28f4190076 Compile as Thumb to reduce binary size 2019-10-22 11:22:55 -06:00
76a13c85fa Remove "Os" pragmas, merge hekate changes 2019-10-22 11:22:26 -06:00
eee656bb34 Add missing file for KFuse fix 2019-10-22 11:18:05 -06:00
b5e932755a Remove wait for button press before reboot to Sept 2019-10-22 11:16:32 -06:00
3ea82573e0 Add wait for KFuse init to prevent wrong TSEC key 2019-10-22 11:15:59 -06:00
8c2aa76fd0 v1.6.4: Skip f_mkdir validation altogether 2019-10-21 11:06:39 -06:00
340a256518 Fix the f_mkdir fix 2019-10-15 11:14:14 -06:00
ee1a3e08ae Fix bad f_mkdir check skipping file save 2019-10-15 11:11:47 -06:00
d4fa066854 Bump version to v1.6.2 2019-10-15 10:26:42 -06:00
d890616c33 Fix function name typo 2019-10-15 10:25:20 -06:00
5d1386cc10 Validate more file I/O calls 2019-10-07 13:18:14 -06:00
0024f049b6 Add new FS keys for LibHac, ff.c -> thumb 2019-10-06 18:41:20 -06:00
22 changed files with 1752 additions and 305 deletions

View File

@ -10,8 +10,8 @@ include $(DEVKITARM)/base_rules
IPL_LOAD_ADDR := 0x40003000
LPVERSION_MAJOR := 1
LPVERSION_MINOR := 6
LPVERSION_BUGFX := 1
LPVERSION_MINOR := 7
LPVERSION_BUGFX := 0
################################################################################
@ -29,7 +29,7 @@ OBJS = $(patsubst $(SOURCEDIR)/%.S, $(BUILDDIR)/$(TARGET)/%.o, \
CUSTOMDEFINES := -DIPL_LOAD_ADDR=$(IPL_LOAD_ADDR)
CUSTOMDEFINES += -DLP_VER_MJ=$(LPVERSION_MAJOR) -DLP_VER_MN=$(LPVERSION_MINOR) -DLP_VER_BF=$(LPVERSION_BUGFX)
ARCH := -march=armv4t -mtune=arm7tdmi -mthumb-interwork
ARCH := -march=armv4t -mtune=arm7tdmi -mthumb -mthumb-interwork
CFLAGS = $(ARCH) -O2 -nostdlib -ffunction-sections -fdata-sections -fomit-frame-pointer -std=gnu11 -Wall $(CUSTOMDEFINES)
LDFLAGS = $(ARCH) -nostartfiles -lgcc -Wl,--nmagic,--gc-sections -Xlinker --defsym=IPL_LOAD_ADDR=$(IPL_LOAD_ADDR)

View File

@ -22,20 +22,25 @@
#include "pkg1.h"
#include "../sec/se.h"
#define HASH_ORDER_100_100 {2, 3, 4, 0, 5, 6, 1}
#define HASH_ORDER_200_510 {2, 3, 4, 0, 5, 7, 10, 12, 11, 6, 8, 1}
#define HASH_ORDER_600_620 {6, 5, 10, 7, 8, 2, 3, 4, 0, 12, 11, 1}
#define HASH_ORDER_700_9xx {6, 5, 10, 7, 8, 2, 3, 4, 0, 12, 11, 9, 1}
static const pkg1_id_t _pkg1_ids[] = {
{ "20161121183008", 0 }, //1.0.0
{ "20170210155124", 0 }, //2.0.0 - 2.3.0
{ "20170519101410", 1 }, //3.0.0
{ "20170710161758", 2 }, //3.0.1 - 3.0.2
{ "20170921172629", 3 }, //4.0.0 - 4.1.0
{ "20180220163747", 4 }, //5.0.0 - 5.1.0
{ "20180802162753", 5 }, //6.0.0 - 6.1.0
{ "20181107105733", 6 }, //6.2.0
{ "20181218175730", 7 }, //7.0.0
{ "20190208150037", 7 }, //7.0.1
{ "20190314172056", 7 }, //8.0.0
{ "20190531152432", 8 }, //8.1.0
{ "20190809135709", 9 }, //9.0.0
{ "20161121183008", 0, {0x1b517, 0x125bc2, 1, 16, 6, HASH_ORDER_100_100, 0, 0x449dc} }, //1.0.0
{ "20170210155124", 0, {0x1d226, 0x26fe, 0, 16, 11, HASH_ORDER_200_510, 0x557b, 0x3d41a} }, //2.0.0 - 2.3.0
{ "20170519101410", 1, {0x1ffa6, 0x298b, 0, 16, 11, HASH_ORDER_200_510, 0x552d, 0x3cb81} }, //3.0.0
{ "20170710161758", 2, {0x20026, 0x29ab, 0, 16, 11, HASH_ORDER_200_510, 0x552d, 0x3cb81} }, //3.0.1 - 3.0.2
{ "20170921172629", 3, {0x1c64c, 0x37eb, 0, 16, 11, HASH_ORDER_200_510, 0x5382, 0x3711c} }, //4.0.0 - 4.1.0
{ "20180220163747", 4, {0x1f3b4, 0x465b, 0, 16, 11, HASH_ORDER_200_510, 0x5a63, 0x37901} }, //5.0.0 - 5.1.0
{ "20180802162753", 5, {0x27350, 0x17ff5, 1, 8, 11, HASH_ORDER_600_620, 0x5674, 0x1d5be} }, //6.0.0 - 6.1.0
{ "20181107105733", 6, {0x27350, 0x17ff5, 1, 8, 11, HASH_ORDER_600_620, 0x5674, 0x1d5be} }, //6.2.0
{ "20181218175730", 7, {0x29c50, 0x6a73, 0, 8, 12, HASH_ORDER_700_9xx, 0x5563, 0x1d437} }, //7.0.0
{ "20190208150037", 7, {0x29c50, 0x6a73, 0, 8, 12, HASH_ORDER_700_9xx, 0x5563, 0x1d437} }, //7.0.1
{ "20190314172056", 7, {0x29c50, 0x6a73, 0, 8, 12, HASH_ORDER_700_9xx, 0x5563, 0x1d437} }, //8.0.0 - 8.0.1
{ "20190531152432", 8, {0x29c50, 0x6a73, 0, 8, 12, HASH_ORDER_700_9xx, 0x5563, 0x1d437} }, //8.1.0
{ "20190809135709", 9, {0x2ec10, 0x5573, 0, 1, 12, HASH_ORDER_700_9xx, 0x6495, 0x1d807} }, //9.0.0 - 9.0.1
{ NULL } //End.
};

View File

@ -19,10 +19,23 @@
#include "../utils/types.h"
typedef struct _key_info_t
{
u32 start_offset;
u32 hks_offset;
bool hks_offset_is_from_end;
u32 alignment;
u32 hash_max;
u8 hash_order[13];
u32 es_offset;
u32 ssl_offset;
} key_info_t;
typedef struct _pkg1_id_t
{
const char *id;
u32 kb;
key_info_t key_info;
} pkg1_id_t;
const pkg1_id_t *pkg1_identify(u8 *pkg1);

View File

@ -139,7 +139,6 @@ int pkg2_decompress_kip(pkg2_kip1_info_t* ki, u32 sectsToDecomp)
memcpy(newKip, &hdr, sizeof(hdr));
newKipSize = dstDataPtr-(unsigned char*)(newKip);
free(ki->kip1);
ki->kip1 = newKip;
ki->size = newKipSize;

View File

@ -107,8 +107,10 @@ int reboot_to_sept(const u8 *tsec_fw, const u32 tsec_size, const u32 kb)
tmp_cfg->boot_cfg |= BOOT_CFG_SEPT_RUN;
if (f_open(&fp, "sd:/sept/payload.bin", FA_READ | FA_WRITE))
if (f_open(&fp, "sd:/sept/payload.bin", FA_READ | FA_WRITE)) {
free(tmp_cfg);
goto error;
}
f_lseek(&fp, PATCHED_RELOC_SZ);
f_write(&fp, tmp_cfg, sizeof(boot_cfg_t), NULL);
@ -117,7 +119,6 @@ int reboot_to_sept(const u8 *tsec_fw, const u32 tsec_size, const u32 kb)
sd_unmount();
gfx_printf("\n%kPress Power or Vol +/-\n to Reboot to Sept...", colors[(color_idx++) % 6]);
btn_wait();
u32 pk1t_sept = SEPT_PK1T_ADDR - (ALIGN(PATCHED_RELOC_SZ, 0x10) + WB_RST_SIZE);

View File

@ -110,7 +110,7 @@ static const u8 bis_key_source[3][0x20] = {
0x4D, 0x12, 0xE1, 0x4B, 0x2A, 0x47, 0x4C, 0x1C, 0x09, 0xCB, 0x03, 0x59, 0xF0, 0x15, 0xF4, 0xE4}
};
static const u8 fs_hashes_sha256[10][0x20] = {
static const u8 fs_hashes_sha256[13][0x20] = {
{ // header_kek_source
0x18, 0x88, 0xca, 0xed, 0x55, 0x51, 0xb3, 0xed, 0xe0, 0x14, 0x99, 0xe8, 0x7c, 0xe0, 0xd8, 0x68,
0x27, 0xf8, 0x08, 0x20, 0xef, 0xb2, 0x75, 0x92, 0x10, 0x55, 0xaa, 0x4e, 0x2a, 0xbd, 0xff, 0xc2},
@ -132,6 +132,15 @@ static const u8 fs_hashes_sha256[10][0x20] = {
{ // save_mac_key_source
0xB4, 0x7B, 0x60, 0x0B, 0x1A, 0xD3, 0x14, 0xF9, 0x41, 0x14, 0x7D, 0x8B, 0x39, 0x1D, 0x4B, 0x19,
0x87, 0xCC, 0x8C, 0x88, 0x4A, 0xC8, 0x9F, 0xFC, 0x91, 0xCA, 0xE2, 0x21, 0xC5, 0x24, 0x51, 0xF7},
{ // save_mac_sd_card_kek_source
0x60, 0x1a, 0x60, 0xbe, 0x13, 0xf6, 0x3e, 0xda, 0xec, 0xcc, 0x96, 0x7f, 0x27, 0xa3, 0xa3, 0x64,
0x65, 0xcb, 0xe8, 0xf0, 0x29, 0xf0, 0xc4, 0x14, 0xb2, 0x36, 0x6a, 0x8b, 0x8a, 0x0f, 0x13, 0x00},
{ // save_mac_sd_card_key_source
0xc2, 0x22, 0x0a, 0x38, 0xb6, 0x87, 0x2b, 0x63, 0xee, 0x77, 0xac, 0x8c, 0x28, 0x24, 0x7a, 0x44,
0x02, 0xe6, 0xdd, 0x85, 0x24, 0x8b, 0x41, 0x9a, 0x6f, 0x9b, 0x17, 0x93, 0xc0, 0x50, 0x3f, 0x21},
{ // sd_card_custom_storage_key_source
0x6b, 0x8f, 0xd2, 0x6c, 0x76, 0x5b, 0x7c, 0x67, 0x70, 0x0c, 0x68, 0x54, 0x90, 0x8e, 0xbe, 0x88,
0x45, 0xb0, 0x55, 0xa6, 0xbb, 0xbb, 0xea, 0x0c, 0x06, 0x3a, 0x85, 0x04, 0x12, 0xd4, 0xca, 0x53},
{ // sd_card_kek_source
0x6B, 0x2E, 0xD8, 0x77, 0xC2, 0xC5, 0x23, 0x34, 0xAC, 0x51, 0xE5, 0x9A, 0xBF, 0xA7, 0xEC, 0x45,
0x7F, 0x4A, 0x7D, 0x01, 0xE4, 0x62, 0x91, 0xE9, 0xF2, 0xEA, 0xA4, 0x5F, 0x01, 0x1D, 0x24, 0xB7},

View File

@ -43,6 +43,7 @@
#include "../utils/util.h"
#include "key_sources.inl"
#include "save.h"
#include <string.h>
@ -95,7 +96,7 @@ void dump_keys() {
new_device_key[0x10] = {0},
sd_seed[0x10] = {0},
// FS-related keys
fs_keys[10][0x20] = {0},
fs_keys[13][0x20] = {0},
header_key[0x20] = {0},
save_mac_key[0x10] = {0},
// other sysmodule sources
@ -115,6 +116,8 @@ void dump_keys() {
package2_key[KB_FIRMWARE_VERSION_MAX+1][0x10] = {0},
titlekek[KB_FIRMWARE_VERSION_MAX+1][0x10] = {0};
sd_mount();
display_backlight_brightness(h_cfg.backlight, 1000);
gfx_clear_partial_grey(0x1B, 0, 1256);
gfx_con_setpos(0, 0);
@ -135,7 +138,10 @@ void dump_keys() {
tsec_ctxt_t tsec_ctxt;
sdmmc_t sdmmc;
emummc_storage_init_mmc(&storage, &sdmmc);
if (!emummc_storage_init_mmc(&storage, &sdmmc)) {
EPRINTF("Unable to init MMC.");
goto out_wait;
}
TPRINTFARGS("%kMMC init... ", colors[(color_idx++) % 6]);
// Read package1.
@ -175,7 +181,8 @@ void dump_keys() {
if (pkg1_id->kb >= KB_FIRMWARE_VERSION_700) {
sd_mount();
if (!f_stat("sd:/sept/payload.bak", NULL)) {
f_unlink("sd:/sept/payload.bin");
if (f_unlink("sd:/sept/payload.bin"))
gfx_printf("%kNote: no payload.bin already in /sept\n", colors[(color_idx++) % 6]);
f_rename("sd:/sept/payload.bak", "sd:/sept/payload.bin");
}
@ -188,16 +195,27 @@ void dump_keys() {
goto get_tsec;
}
// backup post-reboot payload
if (!f_stat("sd:/sept/payload.bin", NULL))
f_rename("sd:/sept/payload.bin", "sd:/sept/payload.bak");
if (!f_stat("sd:/sept/payload.bin", NULL)) {
if (f_rename("sd:/sept/payload.bin", "sd:/sept/payload.bak")) {
EPRINTF("Unable to backup payload.bin.");
goto out_wait;
}
}
// write self to payload.bin to run again when sept finishes
f_open(&fp, "sd:/sept/payload.bin", FA_CREATE_NEW | FA_WRITE);
u32 payload_size = *(u32 *)(IPL_LOAD_ADDR + 0x84) - IPL_LOAD_ADDR;
f_write(&fp, (u8 *)IPL_LOAD_ADDR, payload_size, NULL);
if (f_open(&fp, "sd:/sept/payload.bin", FA_CREATE_NEW | FA_WRITE)) {
EPRINTF("Unable to open /sept/payload.bin to write.");
goto out_wait;
}
if (f_write(&fp, (u8 *)IPL_LOAD_ADDR, payload_size, NULL)) {
EPRINTF("Unable to write self to /sept/payload.bin.");
f_close(&fp);
goto out_wait;
}
f_close(&fp);
gfx_printf("%k\nFirmware 7.x or higher detected.\n\n", colors[(color_idx++) % 6]);
gfx_printf("%kRenamed /sept/payload.bin", colors[(color_idx++) % 6]);
gfx_printf("\n to /sept/payload.bak\n\n", colors[(color_idx++) % 6]);
gfx_printf("\n to /sept/payload.bak\n\n");
gfx_printf("%kCopied self to /sept/payload.bin\n", colors[(color_idx++) % 6]);
sdmmc_storage_end(&storage);
if (!reboot_to_sept((u8 *)tsec_ctxt.fw, tsec_ctxt.size, pkg1_id->kb))
@ -435,99 +453,37 @@ get_tsec: ;
pkg2_decompress_kip(ki, 2 | 4); // we only need .rodata and .data
TPRINTFARGS("%kDecompress FS...", colors[(color_idx++) % 6]);
u8 hash_index = 0, hash_max = 9, hash_order[10],
key_lengths[10] = {0x10, 0x20, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x20, 0x20};
u32 start_offset = 0, hks_offset_from_end = ki->kip1->sections[2].size_decomp, alignment = 1;
u8 hash_index = 0;
const u8 key_lengths[13] = {0x10, 0x20, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x20, 0x10, 0x20, 0x20};
// the FS keys appear in different orders
if (!memcmp(pkg1_id->id, "2016", 4)) {
// 1.0.0 doesn't have SD keys at all
hash_max = 6;
// the first key isn't aligned with the rest
// 1.0.0 doesn't have SD keys at all and the first key isn't aligned with the rest
memcpy(fs_keys[2], ki->kip1->data + ki->kip1->sections[0].size_comp + 0x1ae0e, 0x10);
hash_index = 1;
start_offset = 0x1b517;
hks_offset_from_end = 0x125bc2;
alignment = 0x10;
u8 temp[7] = {2, 3, 4, 0, 5, 6, 1};
memcpy(hash_order, temp, 7);
} else {
// 2.0.0 - 8.0.0
alignment = 0x40;
switch (pkg1_id->kb) {
case KB_FIRMWARE_VERSION_100_200:
start_offset = 0x1d226;
alignment = 0x10;
hks_offset_from_end -= 0x26fe;
break;
case KB_FIRMWARE_VERSION_300:
start_offset = 0x1ffa6;
hks_offset_from_end -= 0x298b;
break;
case KB_FIRMWARE_VERSION_301:
start_offset = 0x20026;
hks_offset_from_end -= 0x29ab;
break;
case KB_FIRMWARE_VERSION_400:
start_offset = 0x1c64c;
hks_offset_from_end -= 0x37eb;
break;
case KB_FIRMWARE_VERSION_500:
start_offset = 0x1f3b4;
hks_offset_from_end -= 0x465b;
alignment = 0x20;
break;
case KB_FIRMWARE_VERSION_600:
case KB_FIRMWARE_VERSION_620:
start_offset = 0x27350;
hks_offset_from_end = 0x17ff5;
alignment = 8;
break;
case KB_FIRMWARE_VERSION_700:
case KB_FIRMWARE_VERSION_810:
start_offset = 0x29c50;
hks_offset_from_end -= 0x6a73;
alignment = 8;
break;
case KB_FIRMWARE_VERSION_900:
start_offset = 0x2ec10;
hks_offset_from_end -= 0x5573;
alignment = 1; // RIP
break;
}
if (pkg1_id->kb <= KB_FIRMWARE_VERSION_500) {
u8 temp[10] = {2, 3, 4, 0, 5, 7, 9, 8, 6, 1};
memcpy(hash_order, temp, 10);
} else {
u8 temp[10] = {6, 5, 7, 2, 3, 4, 0, 9, 8, 1};
memcpy(hash_order, temp, 10);
}
}
u8 temp_hash[0x20];
for (u32 i = ki->kip1->sections[0].size_comp + start_offset; i < ki->size - 0x20; ) {
for (u32 i = ki->kip1->sections[0].size_comp + pkg1_id->key_info.start_offset; i < ki->size - 0x20; ) {
minerva_periodic_training();
se_calc_sha256(temp_hash, ki->kip1->data + i, key_lengths[hash_order[hash_index]]);
if (!memcmp(temp_hash, fs_hashes_sha256[hash_order[hash_index]], 0x20)) {
memcpy(fs_keys[hash_order[hash_index]], ki->kip1->data + i, key_lengths[hash_order[hash_index]]);
/*if (hash_index == hash_max) {
TPRINTFARGS("%d: %x end -%x", hash_index, (*(ki->kip1->data + i)), ki->size - i);
} else {
TPRINTFARGS("%d: %x rodata +%x", hash_index, (*(ki->kip1->data + i)), i - ki->kip1->sections[0].size_comp);
}*/
i += key_lengths[hash_order[hash_index]];
if (hash_index == hash_max - 1) {
i = ki->size - hks_offset_from_end;
} else if (hash_index == hash_max) {
se_calc_sha256(temp_hash, ki->kip1->data + i, key_lengths[pkg1_id->key_info.hash_order[hash_index]]);
if (!memcmp(temp_hash, fs_hashes_sha256[pkg1_id->key_info.hash_order[hash_index]], 0x20)) {
memcpy(fs_keys[pkg1_id->key_info.hash_order[hash_index]], ki->kip1->data + i, key_lengths[pkg1_id->key_info.hash_order[hash_index]]);
i += key_lengths[pkg1_id->key_info.hash_order[hash_index]];
if (hash_index == pkg1_id->key_info.hash_max - 1) {
if (pkg1_id->key_info.hks_offset_is_from_end)
i = ki->size - pkg1_id->key_info.hks_offset;
else
i = ki->size - (ki->kip1->sections[2].size_decomp - pkg1_id->key_info.hks_offset);
} else if (hash_index == pkg1_id->key_info.hash_max) {
break;
}
hash_index++;
} else {
i += alignment;
i += pkg1_id->key_info.alignment;
}
}
pkg2_done:
free(ki->kip1);
free(pkg2);
free(ki);
@ -588,6 +544,8 @@ pkg2_done:
DIR dir;
FILINFO fno;
FIL fp;
save_ctx_t *save_ctx = NULL;
// sysmodule NCAs only ever have one section (exefs) so 0x600 is sufficient
u8 *dec_header = (u8*)malloc(0x600);
char path[100] = "emmc:/Contents/registered";
@ -611,7 +569,6 @@ pkg2_done:
}
path[25] = '/';
start_offset = 0;
while (!f_readdir(&dir, &fno) && fno.fname[0] && titles_found < title_limit) {
minerva_periodic_training();
memcpy(path + 26, fno.fname, 36);
@ -626,44 +583,14 @@ pkg2_done:
se_aes_xts_crypt(5, 4, 0, 1, dec_header + 0x200, dec_header, 32, 1);
// es doesn't contain es key sources on 1.0.0
if (memcmp(pkg1_id->id, "2016", 4) && *(u32*)(dec_header + 0x210) == 0x33 && dec_header[0x205] == 0) {
// es (offset 0x210 is lower half of titleid, 0x205 == 0 means it's program nca, not meta)
switch (pkg1_id->kb) {
case KB_FIRMWARE_VERSION_100_200:
start_offset = 0x557b;
break;
case KB_FIRMWARE_VERSION_300:
case KB_FIRMWARE_VERSION_301:
start_offset = 0x552d;
break;
case KB_FIRMWARE_VERSION_400:
start_offset = 0x5382;
break;
case KB_FIRMWARE_VERSION_500:
start_offset = 0x5a63;
break;
case KB_FIRMWARE_VERSION_600:
case KB_FIRMWARE_VERSION_620:
start_offset = 0x5674;
break;
case KB_FIRMWARE_VERSION_700:
case KB_FIRMWARE_VERSION_810:
start_offset = 0x5563;
break;
case KB_FIRMWARE_VERSION_900:
start_offset = 0x6495;
break;
}
hash_order[2] = 2;
if (pkg1_id->kb < KB_FIRMWARE_VERSION_500) {
hash_order[0] = 0;
hash_order[1] = 1;
} else {
u8 hash_order[3] = {0, 1, 2};
if (pkg1_id->kb >= KB_FIRMWARE_VERSION_500) {
hash_order[0] = 1;
hash_order[1] = 0;
}
hash_index = 0;
// decrypt only what is needed to locate needed keys
temp_file = (u8*)_nca_process(5, 4, &fp, start_offset, 0xc0, key_area_key);
temp_file = (u8*)_nca_process(5, 4, &fp, pkg1_id->key_info.es_offset, 0xc0, key_area_key);
for (u32 i = 0; i <= 0xb0; ) {
se_calc_sha256(temp_hash, temp_file + i, 0x10);
if (!memcmp(temp_hash, es_hashes_sha256[hash_order[hash_index]], 0x10)) {
@ -680,36 +607,7 @@ pkg2_done:
temp_file = NULL;
titles_found++;
} else if (*(u32*)(dec_header + 0x210) == 0x24 && dec_header[0x205] == 0) {
// ssl
switch (pkg1_id->kb) {
case KB_FIRMWARE_VERSION_100_200:
start_offset = 0x3d41a;
break;
case KB_FIRMWARE_VERSION_300:
case KB_FIRMWARE_VERSION_301:
start_offset = 0x3cb81;
break;
case KB_FIRMWARE_VERSION_400:
start_offset = 0x3711c;
break;
case KB_FIRMWARE_VERSION_500:
start_offset = 0x37901;
break;
case KB_FIRMWARE_VERSION_600:
case KB_FIRMWARE_VERSION_620:
start_offset = 0x1d5be;
break;
case KB_FIRMWARE_VERSION_700:
case KB_FIRMWARE_VERSION_810:
start_offset = 0x1d437;
break;
case KB_FIRMWARE_VERSION_900:
start_offset = 0x1d807;
break;
}
if (!memcmp(pkg1_id->id, "2016", 4))
start_offset = 0x449dc;
temp_file = (u8*)_nca_process(5, 4, &fp, start_offset, 0x70, key_area_key);
temp_file = (u8*)_nca_process(5, 4, &fp, pkg1_id->key_info.ssl_offset, 0x70, key_area_key);
for (u32 i = 0; i <= 0x60; i++) {
se_calc_sha256(temp_hash, temp_file + i, 0x10);
if (!memcmp(temp_hash, ssl_hashes_sha256[1], 0x10)) {
@ -766,6 +664,7 @@ pkg2_done:
}
f_close(&fp);
// this file is so small that parsing the savedata properly would take longer
if (f_open(&fp, "emmc:/save/8000000000000043", FA_READ | FA_OPEN_EXISTING)) {
EPRINTF("Unable to open ns_appman save.\nSkipping SD seed.");
goto get_titlekeys;
@ -788,13 +687,9 @@ get_titlekeys:
if (!_key_exists(eticket_rsa_kek))
goto dismount;
if (!minerva_cfg) {
gfx_printf("%k Minerva not found!\n This may take up to a minute...\n", colors[(color_idx++) % 6]);
gfx_printf(" For better performance, download Hekate\n and put bootloader/sys/libsys_minerva.bso\n on SD.\n");
}
gfx_printf("%kTitlekeys... ", colors[color_idx % 6]);
gfx_printf("%kTitlekeys... ", colors[(color_idx++) % 6]);
u32 save_x = gfx_con.x, save_y = gfx_con.y;
gfx_printf("\n");
gfx_printf("\n%kCommon... ", colors[color_idx % 6]);
u8 null_hash[0x20] = {
0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24,
@ -803,7 +698,7 @@ get_titlekeys:
se_aes_key_set(8, bis_key[0] + 0x00, 0x10);
se_aes_key_set(9, bis_key[0] + 0x10, 0x10);
u32 buf_size = 0x80000;
u32 buf_size = 0x4000;
u8 *buffer = (u8 *)malloc(buf_size);
u8 keypair[0x230] = {0};
@ -841,63 +736,83 @@ get_titlekeys:
se_rsa_key_set(0, N, 0x100, D, 0x100);
if (f_stat("emmc:/save/80000000000000E1", &fno)) {
EPRINTF("Unable to stat ES save 1. Skipping.");
free(buffer);
goto dismount;
}
u64 total_size = fno.fsize;
if (f_stat("emmc:/save/80000000000000E2", &fno)) {
EPRINTF("Unable to stat ES save 2. Skipping.");
free(buffer);
goto dismount;
}
total_size += fno.fsize;
u32 br;
u32 br = buf_size;
u32 file_tkey_count = 0;
u64 total_br = 0;
rights_ids = (u8 *)malloc(0x400000);
titlekeys = (u8 *)malloc(0x400000);
rights_ids = (u8 *)malloc(0x40000);
titlekeys = (u8 *)malloc(0x40000);
save_ctx = calloc(1, sizeof(save_ctx_t));
u8 M[0x100];
if (f_open(&fp, "emmc:/save/80000000000000E1", FA_READ | FA_OPEN_EXISTING)) {
EPRINTF("Unable to open ES save 1. Skipping.");
free(buffer);
goto dismount;
}
f_lseek(&fp, 0x8000);
u32 pct = 0, last_pct = 0;
tui_pbar(save_x, save_y, pct, COLOR_GREEN, 0xFF155500);
while (!f_read(&fp, buffer, buf_size, &br)) {
save_ctx->file = &fp;
save_ctx->tool_ctx.action = 0;
memcpy(save_ctx->save_mac_key, save_mac_key, 0x10);
save_process(save_ctx);
char ticket_bin_path[SAVE_FS_LIST_MAX_NAME_LENGTH] = "/ticket.bin";
char ticket_list_bin_path[SAVE_FS_LIST_MAX_NAME_LENGTH] = "/ticket_list.bin";
allocation_table_storage_ctx_t fat_storage;
save_fs_list_entry_t entry = {0, "", {0}, 0};
if (!save_hierarchical_file_table_get_file_entry_by_path(&save_ctx->save_filesystem_core.file_table, ticket_list_bin_path, &entry)) {
EPRINTF("Unable to locate ticket_list.bin in e1.");
goto dismount;
}
save_open_fat_storage(&save_ctx->save_filesystem_core, &fat_storage, entry.value.save_file_info.start_block);
while (br == buf_size && total_br < entry.value.save_file_info.length) {
br = save_allocation_table_storage_read(&fat_storage, buffer, total_br, buf_size);
if (buffer[0] == 0) break;
total_br += br;
for (u32 i = 0; i < br; i += 0x4000) {
pct = (u32)((total_br + i) * 100 / total_size);
minerva_periodic_training();
for (u32 j = 0; j < buf_size; j += 0x20) {
if (buffer[j] == 0xff && buffer[j+1] == 0xff && buffer[j+2] == 0xff && buffer[j+3] == 0xff) break;
file_tkey_count++;
}
}
if (!save_hierarchical_file_table_get_file_entry_by_path(&save_ctx->save_filesystem_core.file_table, ticket_bin_path, &entry)) {
EPRINTF("Unable to locate ticket.bin in e1.");
goto dismount;
}
save_open_fat_storage(&save_ctx->save_filesystem_core, &fat_storage, entry.value.save_file_info.start_block);
total_br = 0;
while (br == buf_size && total_br < entry.value.save_file_info.length) {
br = save_allocation_table_storage_read(&fat_storage, buffer, total_br, buf_size);
if (buffer[0] == 0) break;
total_br += br;
for (u32 j = 0; j < buf_size; j += 0x400) {
pct = _titlekey_count * 100 / file_tkey_count;
if (pct > last_pct && pct <= 100) {
last_pct = pct;
tui_pbar(save_x, save_y, pct, COLOR_GREEN, 0xFF155500);
}
for (u32 j = i; j < i + 0x4000; j += 0x400) {
minerva_periodic_training();
if (buffer[j] == 4 && buffer[j+1] == 0 && buffer[j+2] == 1 && buffer[j+3] == 0) {
u32 k = 0;
bool titlekey_found = false;
for (; k < _titlekey_count; k++) {
if (!memcmp(rights_ids + 0x10 * k, buffer + j + 0x2a0, 0x10)) {
titlekey_found = true;
break;
}
}
if (titlekey_found)
continue;
memcpy(rights_ids + 0x10 * _titlekey_count, buffer + j + 0x2a0, 0x10);
memcpy(titlekeys + 0x10 * _titlekey_count, buffer + j + 0x180, 0x10);
_titlekey_count++;
} else {
break;
}
minerva_periodic_training();
if (buffer[j] == 4 && buffer[j+1] == 0 && buffer[j+2] == 1 && buffer[j+3] == 0) {
memcpy(rights_ids + 0x10 * _titlekey_count, buffer + j + 0x2a0, 0x10);
memcpy(titlekeys + 0x10 * _titlekey_count, buffer + j + 0x180, 0x10);
_titlekey_count++;
} else {
break;
}
}
if (br < buf_size) break;
}
tui_pbar(save_x, save_y, 100, COLOR_GREEN, 0xFF155500);
f_close(&fp);
save_free_contexts(save_ctx);
memset(save_ctx, 0, sizeof(save_ctx_t));
memset(&fat_storage, 0, sizeof(allocation_table_storage_ctx_t));
gfx_con_setpos(0, save_y);
TPRINTFARGS("\n%kCommon... ", colors[(color_idx++) % 6]);
save_x = gfx_con.x + 16 * 17;
save_y = gfx_con.y;
gfx_printf("\n%kPersonalized... ", colors[color_idx % 6]);
u32 common_titlekey_count = _titlekey_count;
if (f_open(&fp, "emmc:/save/80000000000000E2", FA_READ | FA_OPEN_EXISTING)) {
@ -905,61 +820,94 @@ get_titlekeys:
free(buffer);
goto dismount;
}
f_lseek(&fp, 0x8000);
while (!f_read(&fp, buffer, buf_size, &br)) {
save_ctx->file = &fp;
save_ctx->tool_ctx.action = 0;
memcpy(save_ctx->save_mac_key, save_mac_key, 0x10);
save_process(save_ctx);
if (!save_hierarchical_file_table_get_file_entry_by_path(&save_ctx->save_filesystem_core.file_table, ticket_list_bin_path, &entry)) {
EPRINTF("Unable to locate ticket_list.bin in e2.");
goto dismount;
}
save_open_fat_storage(&save_ctx->save_filesystem_core, &fat_storage, entry.value.save_file_info.start_block);
total_br = 0;
file_tkey_count = 0;
while (br == buf_size && total_br < entry.value.save_file_info.length) {
br = save_allocation_table_storage_read(&fat_storage, buffer, total_br, buf_size);
if (buffer[0] == 0) break;
total_br += br;
for (u32 i = 0; i < br; i += 0x4000) {
pct = (u32)((total_br + i) * 100 / total_size);
minerva_periodic_training();
for (u32 j = 0; j < buf_size; j += 0x20) {
if (buffer[j] == 0xff && buffer[j+1] == 0xff && buffer[j+2] == 0xff && buffer[j+3] == 0xff) break;
file_tkey_count++;
}
}
if (!save_hierarchical_file_table_get_file_entry_by_path(&save_ctx->save_filesystem_core.file_table, ticket_bin_path, &entry)) {
EPRINTF("Unable to locate ticket.bin in e2.");
goto dismount;
}
save_open_fat_storage(&save_ctx->save_filesystem_core, &fat_storage, entry.value.save_file_info.start_block);
total_br = 0;
pct = 0;
last_pct = 0;
while (br == buf_size && total_br < entry.value.save_file_info.length) {
br = save_allocation_table_storage_read(&fat_storage, buffer, total_br, buf_size);
if (buffer[0] == 0) break;
total_br += br;
for (u32 j = 0; j < buf_size; j += 0x400) {
pct = (_titlekey_count - common_titlekey_count) * 100 / file_tkey_count;
if (pct > last_pct && pct <= 100) {
last_pct = pct;
tui_pbar(save_x, save_y, pct, COLOR_GREEN, 0xFF155500);
}
for (u32 j = i; j < i + 0x4000; j += 0x400) {
minerva_periodic_training();
if (buffer[j] == 4 && buffer[j+1] == 0 && buffer[j+2] == 1 && buffer[j+3] == 0) {
u32 k = common_titlekey_count;
bool titlekey_found = false;
for (; k < _titlekey_count; k++) {
if (!memcmp(rights_ids + 0x10 * k, buffer + j + 0x2a0, 0x10)) {
titlekey_found = true;
break;
}
}
if (titlekey_found)
continue;
memcpy(rights_ids + 0x10 * _titlekey_count, buffer + j + 0x2a0, 0x10);
minerva_periodic_training();
if (buffer[j] == 4 && buffer[j+1] == 0 && buffer[j+2] == 1 && buffer[j+3] == 0) {
memcpy(rights_ids + 0x10 * _titlekey_count, buffer + j + 0x2a0, 0x10);
u8 *titlekey_block = buffer + j + 0x180;
se_rsa_exp_mod(0, M, 0x100, titlekey_block, 0x100);
u8 *salt = M + 1;
u8 *db = M + 0x21;
_mgf1_xor(salt, 0x20, db, 0xdf);
_mgf1_xor(db, 0xdf, salt, 0x20);
if (memcmp(db, null_hash, 0x20))
continue;
memcpy(titlekeys + 0x10 * _titlekey_count, db + 0xcf, 0x10);
_titlekey_count++;
} else {
break;
}
u8 *titlekey_block = buffer + j + 0x180;
se_rsa_exp_mod(0, M, 0x100, titlekey_block, 0x100);
u8 *salt = M + 1;
u8 *db = M + 0x21;
_mgf1_xor(salt, 0x20, db, 0xdf);
_mgf1_xor(db, 0xdf, salt, 0x20);
if (memcmp(db, null_hash, 0x20))
continue;
memcpy(titlekeys + 0x10 * _titlekey_count, db + 0xcf, 0x10);
_titlekey_count++;
} else {
break;
}
}
if (br < buf_size) break;
}
tui_pbar(save_x, save_y, 100, COLOR_GREEN, 0xFF155500);
free(buffer);
f_close(&fp);
gfx_con_setpos(0, save_y);
TPRINTFARGS("\n%k ", colors[(color_idx++) % 6]);
TPRINTFARGS("\n%kPersonalized... ", colors[(color_idx++) % 6]);
gfx_printf("\n%k Found %d titlekeys.\n", colors[(color_idx++) % 6], _titlekey_count);
dismount:
dismount:;
if (save_ctx) {
save_free_contexts(save_ctx);
free(save_ctx);
}
f_mount(NULL, "emmc:", 1);
clear_sector_cache = true;
nx_emmc_gpt_free(&gpt);
key_output: ;
char *text_buffer = (char *)calloc(1, _titlekey_count * 68 < 0x3000 ? 0x3000 : _titlekey_count * 68 + 1);
char *text_buffer = NULL;
if (!sd_mount()) {
EPRINTF("Unable to mount SD.");
goto free_buffers;
}
u32 text_buffer_size = _titlekey_count * 68 < 0x3000 ? 0x3000 : _titlekey_count * 68 + 1;
text_buffer = (char *)calloc(1, text_buffer_size);
SAVE_KEY("aes_kek_generation_source", aes_kek_generation_source, 0x10);
SAVE_KEY("aes_key_generation_source", aes_key_generation_source, 0x10);
@ -1002,9 +950,12 @@ key_output: ;
SAVE_KEY("save_mac_kek_source", fs_keys[5], 0x10);
SAVE_KEY("save_mac_key", save_mac_key, 0x10);
SAVE_KEY("save_mac_key_source", fs_keys[6], 0x10);
SAVE_KEY("sd_card_kek_source", fs_keys[7], 0x10);
SAVE_KEY("sd_card_nca_key_source", fs_keys[8], 0x20);
SAVE_KEY("sd_card_save_key_source", fs_keys[9], 0x20);
SAVE_KEY("save_mac_sd_card_kek_source", fs_keys[7], 0x10);
SAVE_KEY("save_mac_sd_card_key_source", fs_keys[8], 0x10);
SAVE_KEY("sd_card_custom_storage_key_source", fs_keys[9], 0x20);
SAVE_KEY("sd_card_kek_source", fs_keys[10], 0x10);
SAVE_KEY("sd_card_nca_key_source", fs_keys[11], 0x20);
SAVE_KEY("sd_card_save_key_source", fs_keys[12], 0x20);
SAVE_KEY("sd_seed", sd_seed, 0x10);
SAVE_KEY("secure_boot_key", sbk, 0x10);
SAVE_KEY("ssl_rsa_kek", ssl_rsa_kek, 0x10);
@ -1029,14 +980,14 @@ key_output: ;
sprintf(&keyfile_path[11], "prod.keys");
else
sprintf(&keyfile_path[11], "dev.keys");
if (sd_mount() && !sd_save_to_file(text_buffer, strlen(text_buffer), keyfile_path) && !f_stat(keyfile_path, &fno)) {
if (!sd_save_to_file(text_buffer, strlen(text_buffer), keyfile_path) && !f_stat(keyfile_path, &fno)) {
gfx_printf("%kWrote %d bytes to %s\n", colors[(color_idx++) % 6], (u32)fno.fsize, keyfile_path);
} else
EPRINTF("Unable to save keys to SD.");
if (_titlekey_count == 0)
goto out_wait;
memset(text_buffer, 0, _titlekey_count * 68 + 1);
goto free_buffers;
memset(text_buffer, 0, text_buffer_size);
for (u32 i = 0; i < _titlekey_count; i++) {
for (u32 j = 0; j < 0x10; j++)
sprintf(&text_buffer[i * 68 + j * 2], "%02x", rights_ids[i * 0x10 + j]);
@ -1046,10 +997,12 @@ key_output: ;
sprintf(&text_buffer[i * 68 + 0x43], "\n");
}
sprintf(&keyfile_path[11], "title.keys");
if (sd_mount() && !sd_save_to_file(text_buffer, strlen(text_buffer), keyfile_path) && !f_stat(keyfile_path, &fno)) {
if (!sd_save_to_file(text_buffer, strlen(text_buffer), keyfile_path) && !f_stat(keyfile_path, &fno)) {
gfx_printf("%kWrote %d bytes to %s\n", colors[(color_idx++) % 6], (u32)fno.fsize, keyfile_path);
} else
EPRINTF("Unable to save titlekeys to SD.");
free_buffers:
free(rights_ids);
free(titlekeys);
free(text_buffer);
@ -1074,8 +1027,8 @@ static void _save_key(const char *name, const void *data, u32 len, char *outbuf)
static void _save_key_family(const char *name, const void *data, u32 start_key, u32 num_keys, u32 len, char *outbuf) {
char temp_name[0x40] = {0};
for (u32 i = start_key; i < num_keys + start_key; i++) {
sprintf(temp_name, "%s_%02x", name, i);
for (u32 i = 0; i < num_keys; i++) {
sprintf(temp_name, "%s_%02x", name, i + start_key);
_save_key(temp_name, data + i * len, len, outbuf);
}
}
@ -1103,8 +1056,10 @@ static void *_nca_process(u32 hk_ks1, u32 hk_ks2, FIL *fp, u32 key_offset, u32 l
u8 *temp_file = (u8*)malloc(0x400),
ctr[0x10] = {0};
if (f_lseek(fp, 0x200) || f_read(fp, temp_file, 0x400, &read_bytes) || read_bytes != 0x400)
if (f_lseek(fp, 0x200) || f_read(fp, temp_file, 0x400, &read_bytes) || read_bytes != 0x400) {
free(temp_file);
return NULL;
}
se_aes_xts_crypt(hk_ks1, hk_ks2, 0, 1, temp_file, temp_file, 0x200, 2);
// both 1.x and 2.x use master_key_00
temp_file[0x20] -= temp_file[0x20] ? 1 : 0;

821
source/keys/save.c Normal file
View File

@ -0,0 +1,821 @@
#include <string.h>
#include "save.h"
#include "../gfx/gfx.h"
#include "../mem/heap.h"
#include "../sec/se.h"
#include "../utils/types.h"
#include "../utils/util.h"
#define REMAP_ENTRY_LENGTH 0x20
static inline void save_bitmap_set_bit(void *buffer, size_t bit_offset) {
*((uint8_t *)buffer + (bit_offset >> 3)) |= 1 << (bit_offset & 7);
}
static inline void save_bitmap_clear_bit(void *buffer, size_t bit_offset) {
*((uint8_t *)buffer + (bit_offset >> 3)) &= ~(uint8_t)(1 << (bit_offset & 7));
}
static inline uint8_t save_bitmap_check_bit(const void *buffer, size_t bit_offset) {
return *((uint8_t *)buffer + (bit_offset >> 3)) & (1 << (bit_offset & 7));
}
void save_duplex_storage_init(duplex_storage_ctx_t *ctx, duplex_fs_layer_info_t *layer, void *bitmap, uint64_t bitmap_size) {
ctx->data_a = layer->data_a;
ctx->data_b = layer->data_b;
ctx->bitmap_storage = (uint8_t *)bitmap;
ctx->block_size = 1 << layer->info.block_size_power;
ctx->bitmap.data = ctx->bitmap_storage;
ctx->bitmap.bitmap = malloc(bitmap_size >> 3);
uint32_t bits_remaining = bitmap_size;
uint32_t bitmap_pos = 0;
uint32_t *buffer_pos = (uint32_t *)bitmap;
while (bits_remaining) {
uint32_t bits_to_read = bits_remaining < 32 ? bits_remaining : 32;
uint32_t val = *buffer_pos;
for (uint32_t i = 0; i < bits_to_read; i++) {
if (val & 0x80000000)
save_bitmap_set_bit(ctx->bitmap.bitmap, bitmap_pos);
else
save_bitmap_clear_bit(ctx->bitmap.bitmap, bitmap_pos);
bitmap_pos++;
bits_remaining--;
val <<= 1;
}
buffer_pos++;
}
}
uint32_t save_duplex_storage_read(duplex_storage_ctx_t *ctx, void *buffer, uint64_t offset, size_t count) {
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint32_t block_num = (uint32_t)(in_pos / ctx->block_size);
uint32_t block_pos = (uint32_t)(in_pos % ctx->block_size);
uint32_t bytes_to_read = ctx->block_size - block_pos < remaining ? ctx->block_size - block_pos : remaining;
uint8_t *data = save_bitmap_check_bit(ctx->bitmap.bitmap, block_num) ? ctx->data_b : ctx->data_a;
memcpy((uint8_t *)buffer + out_pos, data + in_pos, bytes_to_read);
out_pos += bytes_to_read;
in_pos += bytes_to_read;
remaining -= bytes_to_read;
}
return out_pos;
}
remap_segment_ctx_t *save_remap_init_segments(remap_header_t *header, remap_entry_ctx_t *map_entries, uint32_t num_map_entries) {
remap_segment_ctx_t *segments = malloc(sizeof(remap_segment_ctx_t) * header->map_segment_count);
unsigned int entry_idx = 0;
for (unsigned int i = 0; i < header->map_segment_count; i++) {
remap_segment_ctx_t *seg = &segments[i];
seg->entries = malloc(sizeof(remap_entry_ctx_t));
memcpy(seg->entries, &map_entries[entry_idx], sizeof(remap_entry_ctx_t));
seg->offset = map_entries[entry_idx].virtual_offset;
map_entries[entry_idx].segment = seg;
seg->entry_count = 1;
entry_idx++;
while (entry_idx < num_map_entries && map_entries[entry_idx - 1].virtual_offset_end == map_entries[entry_idx].virtual_offset) {
map_entries[entry_idx].segment = seg;
map_entries[entry_idx - 1].next = &map_entries[entry_idx];
seg->entries = malloc(sizeof(remap_entry_ctx_t));
memcpy(seg->entries, &map_entries[entry_idx], sizeof(remap_entry_ctx_t));
seg->entry_count++;
entry_idx++;
}
seg->length = seg->entries[seg->entry_count - 1].virtual_offset_end - seg->entries[0].virtual_offset;
}
return segments;
}
remap_entry_ctx_t *save_remap_get_map_entry(remap_storage_ctx_t *ctx, uint64_t offset) {
uint32_t segment_idx = (uint32_t)(offset >> (64 - ctx->header->segment_bits));
if (segment_idx < ctx->header->map_segment_count) {
for (unsigned int i = 0; i < ctx->segments[segment_idx].entry_count; i++)
if (ctx->segments[segment_idx].entries[i].virtual_offset_end > offset)
return &ctx->segments[segment_idx].entries[i];
}
return NULL;
}
uint32_t save_remap_read(remap_storage_ctx_t *ctx, void *buffer, uint64_t offset, size_t count) {
remap_entry_ctx_t *entry = save_remap_get_map_entry(ctx, offset);
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint64_t entry_pos = in_pos - entry->virtual_offset;
uint32_t bytes_to_read = entry->virtual_offset_end - in_pos < remaining ? (uint32_t)(entry->virtual_offset_end - in_pos) : remaining;
switch (ctx->type) {
case STORAGE_BYTES:
f_lseek(ctx->file, ctx->base_storage_offset + entry->physical_offset + entry_pos);
f_read(ctx->file, (uint8_t *)buffer + out_pos, bytes_to_read, NULL);
break;
case STORAGE_DUPLEX:
save_duplex_storage_read(ctx->duplex, (uint8_t *)buffer + out_pos, ctx->base_storage_offset + entry->physical_offset + entry_pos, bytes_to_read);
break;
default:
break;
}
out_pos += bytes_to_read;
in_pos += bytes_to_read;
remaining -= bytes_to_read;
if (in_pos >= entry->virtual_offset_end)
entry = entry->next;
}
return out_pos;
}
uint32_t save_journal_storage_read(journal_storage_ctx_t *ctx, remap_storage_ctx_t *remap, void *buffer, uint64_t offset, size_t count) {
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint32_t block_num = (uint32_t)(in_pos / ctx->block_size);
uint32_t block_pos = (uint32_t)(in_pos % ctx->block_size);
uint64_t physical_offset = ctx->map.entries[block_num].physical_index * ctx->block_size + block_pos;
uint32_t bytes_to_read = ctx->block_size - block_pos < remaining ? ctx->block_size - block_pos : remaining;
save_remap_read(remap, (uint8_t *)buffer + out_pos, ctx->journal_data_offset + physical_offset, bytes_to_read);
out_pos += bytes_to_read;
in_pos += bytes_to_read;
remaining -= bytes_to_read;
}
return out_pos;
}
void save_ivfc_storage_init(hierarchical_integrity_verification_storage_ctx_t *ctx, uint64_t master_hash_offset, ivfc_save_hdr_t *ivfc) {
ivfc_level_save_ctx_t *levels = ctx->levels;
levels[0].type = STORAGE_BYTES;
levels[0].hash_offset = master_hash_offset;
for (unsigned int i = 1; i < 4; i++) {
ivfc_level_hdr_t *level = &ivfc->level_headers[i - 1];
levels[i].type = STORAGE_REMAP;
levels[i].data_offset = level->logical_offset;
levels[i].data_size = level->hash_data_size;
}
if (ivfc->num_levels == 5) {
ivfc_level_hdr_t *data_level = &ivfc->level_headers[ivfc->num_levels - 2];
levels[ivfc->num_levels - 1].type = STORAGE_JOURNAL;
levels[ivfc->num_levels - 1].data_offset = data_level->logical_offset;
levels[ivfc->num_levels - 1].data_size = data_level->hash_data_size;
}
struct salt_source_t {
char string[50];
uint32_t length;
};
static struct salt_source_t salt_sources[6] = {
{"HierarchicalIntegrityVerificationStorage::Master", 48},
{"HierarchicalIntegrityVerificationStorage::L1", 44},
{"HierarchicalIntegrityVerificationStorage::L2", 44},
{"HierarchicalIntegrityVerificationStorage::L3", 44},
{"HierarchicalIntegrityVerificationStorage::L4", 44},
{"HierarchicalIntegrityVerificationStorage::L5", 44}
};
integrity_verification_info_ctx_t init_info[ivfc->num_levels];
init_info[0].data = &levels[0];
init_info[0].block_size = 0;
for (unsigned int i = 1; i < ivfc->num_levels; i++) {
init_info[i].data = &levels[i];
init_info[i].block_size = 1 << ivfc->level_headers[i - 1].block_size;
se_calc_hmac_sha256(init_info[i].salt, ivfc->salt_source, 0x20, salt_sources[i - 1].string, salt_sources[i - 1].length);
}
ctx->integrity_storages[0].next_level = NULL;
ctx->level_validities = malloc(sizeof(validity_t *) * (ivfc->num_levels - 1));
for (unsigned int i = 1; i < ivfc->num_levels; i++) {
integrity_verification_storage_ctx_t *level_data = &ctx->integrity_storages[i - 1];
level_data->hash_storage = &levels[i - 1];
level_data->base_storage = &levels[i];
level_data->sector_size = init_info[i].block_size;
level_data->_length = init_info[i].data->data_size;
level_data->sector_count = (level_data->_length + level_data->sector_size - 1) / level_data->sector_size;
memcpy(level_data->salt, init_info[i].salt, 0x20);
level_data->block_validities = calloc(1, sizeof(validity_t) * level_data->sector_count);
ctx->level_validities[i - 1] = level_data->block_validities;
if (i > 1) {
level_data->next_level = &ctx->integrity_storages[i - 2];
}
}
ctx->data_level = &levels[ivfc->num_levels - 1];
ctx->_length = ctx->integrity_storages[ivfc->num_levels - 2]._length;
}
size_t save_ivfc_level_fread(ivfc_level_save_ctx_t *ctx, void *buffer, uint64_t offset, size_t count) {
switch (ctx->type) {
case STORAGE_BYTES:
f_lseek(ctx->save_ctx->file, ctx->hash_offset + offset);
UINT br = 0;
f_read(ctx->save_ctx->file, buffer, count, &br);
return br;
case STORAGE_REMAP:
save_remap_read(&ctx->save_ctx->meta_remap_storage, buffer, ctx->data_offset + offset, count);
return count;
case STORAGE_JOURNAL:
save_journal_storage_read(&ctx->save_ctx->journal_storage, &ctx->save_ctx->data_remap_storage, buffer, ctx->data_offset + offset, count);
return count;
default:
return 0;
}
}
void save_ivfc_storage_read(integrity_verification_storage_ctx_t *ctx, void *buffer, uint64_t offset, size_t count, uint32_t verify) {
if (count > ctx->sector_size) {
EPRINTF("IVFC read exceeds sector size!\n");
}
uint64_t block_index = offset / ctx->sector_size;
if (ctx->block_validities[block_index] == VALIDITY_INVALID && verify) {
EPRINTFARGS("Hash error from previous check\n found at offset %x count %x!\n", (u32)offset, count);
}
uint8_t hash_buffer[0x20] = {0};
uint8_t zeroes[0x20] = {0};
uint64_t hash_pos = block_index * 0x20;
if (ctx->next_level) {
save_ivfc_storage_read(ctx->next_level, hash_buffer, hash_pos, 0x20, verify);
} else {
save_ivfc_level_fread(ctx->hash_storage, hash_buffer, hash_pos, 0x20);
}
if (!memcmp(hash_buffer, zeroes, 0x20)) {
memset(buffer, 0, count);
ctx->block_validities[block_index] = VALIDITY_VALID;
return;
}
save_ivfc_level_fread(ctx->base_storage, buffer, offset, count);
if (!(verify && ctx->block_validities[block_index] == VALIDITY_UNCHECKED)) {
return;
}
uint8_t hash[0x20] = {0};
uint8_t *data_buffer = calloc(1, ctx->sector_size + 0x20);
memcpy(data_buffer, ctx->salt, 0x20);
memcpy(data_buffer + 0x20, buffer, count);
se_calc_sha256(hash, data_buffer, ctx->sector_size + 0x20);
hash[0x1F] |= 0x80;
free(data_buffer);
if (memcmp(hash_buffer, hash, 0x20)) {
ctx->block_validities[block_index] = VALIDITY_INVALID;
} else {
ctx->block_validities[block_index] = VALIDITY_VALID;
}
if (ctx->block_validities[block_index] == VALIDITY_INVALID && verify) {
EPRINTFARGS("Hash error from current check\n found at offset %x count %x!\n", (u32)offset, count);
}
}
uint32_t save_allocation_table_read_entry_with_length(allocation_table_ctx_t *ctx, allocation_table_entry_t *entry) {
uint32_t length = 1;
uint32_t entry_index = allocation_table_block_to_entry_index(entry->next);
allocation_table_entry_t *entries = (allocation_table_entry_t *)((uint8_t *)(ctx->base_storage) + entry_index * SAVE_FAT_ENTRY_SIZE);
if ((entries[0].next & 0x80000000) == 0) {
if (entries[0].prev & 0x80000000 && entries[0].prev != 0x80000000) {
EPRINTF("Invalid range entry in allocation table!\n");
}
} else {
length = entries[1].next - entry_index + 1;
}
if (allocation_table_is_list_end(&entries[0])) {
entry->next = 0xFFFFFFFF;
} else {
entry->next = allocation_table_entry_index_to_block(allocation_table_get_next(&entries[0]));
}
if (allocation_table_is_list_start(&entries[0])) {
entry->prev = 0xFFFFFFFF;
} else {
entry->prev = allocation_table_entry_index_to_block(allocation_table_get_prev(&entries[0]));
}
return length;
}
uint32_t save_allocation_table_get_list_length(allocation_table_ctx_t *ctx, uint32_t block_index) {
allocation_table_entry_t entry;
entry.next = block_index;
uint32_t total_length = 0;
uint32_t table_size = ctx->header->allocation_table_block_count;
uint32_t nodes_iterated = 0;
while (entry.next != 0xFFFFFFFF) {
total_length += save_allocation_table_read_entry_with_length(ctx, &entry);
nodes_iterated++;
if (nodes_iterated > table_size) {
EPRINTF("Cycle detected in allocation table!\n");
return 0;
}
}
return total_length;
}
uint64_t save_allocation_table_get_free_space_size(save_filesystem_ctx_t *ctx) {
uint32_t free_list_start = save_allocation_table_get_free_list_block_index(&ctx->allocation_table);
if (free_list_start == 0xFFFFFFFF) return 0;
return ctx->header->block_size * save_allocation_table_get_list_length(&ctx->allocation_table, free_list_start);
}
void save_allocation_table_iterator_begin(allocation_table_iterator_ctx_t *ctx, allocation_table_ctx_t *table, uint32_t initial_block) {
ctx->fat = table;
ctx->physical_block = initial_block;
ctx->virtual_block = 0;
allocation_table_entry_t entry;
entry.next = initial_block;
ctx->current_segment_size = save_allocation_table_read_entry_with_length(ctx->fat, &entry);
ctx->next_block = entry.next;
ctx->prev_block = entry.prev;
if (ctx->prev_block != 0xFFFFFFFF) {
EPRINTFARGS("Attempted to start FAT iteration from\n invalid block %x!\n", initial_block);
}
}
int save_allocation_table_iterator_move_next(allocation_table_iterator_ctx_t *ctx) {
if (ctx->next_block == 0xFFFFFFFF) return 0;
ctx->virtual_block += ctx->current_segment_size;
ctx->physical_block = ctx->next_block;
allocation_table_entry_t entry;
entry.next = ctx->next_block;
ctx->current_segment_size = save_allocation_table_read_entry_with_length(ctx->fat, &entry);
ctx->next_block = entry.next;
ctx->prev_block = entry.prev;
return 1;
}
int save_allocation_table_iterator_move_prev(allocation_table_iterator_ctx_t *ctx) {
if (ctx->prev_block == 0xFFFFFFFF) return 0;
ctx->physical_block = ctx->prev_block;
allocation_table_entry_t entry;
entry.next = ctx->prev_block;
ctx->current_segment_size = save_allocation_table_read_entry_with_length(ctx->fat, &entry);
ctx->next_block = entry.next;
ctx->prev_block = entry.prev;
ctx->virtual_block -= ctx->current_segment_size;
return 1;
}
int save_allocation_table_iterator_seek(allocation_table_iterator_ctx_t *ctx, uint32_t block) {
while (1) {
if (block < ctx->virtual_block) {
if (!save_allocation_table_iterator_move_prev(ctx)) return 0;
} else if (block >= ctx->virtual_block + ctx->current_segment_size) {
if (!save_allocation_table_iterator_move_next(ctx)) return 0;
} else {
return 1;
}
}
}
uint32_t save_allocation_table_storage_read(allocation_table_storage_ctx_t *ctx, void *buffer, uint64_t offset, size_t count) {
allocation_table_iterator_ctx_t iterator;
save_allocation_table_iterator_begin(&iterator, ctx->fat, ctx->initial_block);
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint32_t block_num = (uint32_t)(in_pos / ctx->block_size);
save_allocation_table_iterator_seek(&iterator, block_num);
uint32_t segment_pos = (uint32_t)(in_pos - (uint64_t)iterator.virtual_block * ctx->block_size);
uint64_t physical_offset = iterator.physical_block * ctx->block_size + segment_pos;
uint32_t remaining_in_segment = iterator.current_segment_size * ctx->block_size - segment_pos;
uint32_t bytes_to_read = remaining < remaining_in_segment ? remaining : remaining_in_segment;
uint32_t sector_size = ctx->base_storage->integrity_storages[3].sector_size;
uint32_t chunk_remaining = bytes_to_read;
for (unsigned int i = 0; i < bytes_to_read; i += sector_size) {
uint32_t bytes_to_request = chunk_remaining < sector_size ? chunk_remaining : sector_size;
save_ivfc_storage_read(&ctx->base_storage->integrity_storages[3], (uint8_t *)buffer + out_pos + i, physical_offset + i, bytes_to_request, ctx->base_storage->data_level->save_ctx->tool_ctx.action & ACTION_VERIFY);
chunk_remaining -= bytes_to_request;
}
out_pos += bytes_to_read;
in_pos += bytes_to_read;
remaining -= bytes_to_read;
}
return out_pos;
}
uint32_t save_fs_list_get_capacity(save_filesystem_list_ctx_t *ctx) {
if (!ctx->capacity)
save_allocation_table_storage_read(&ctx->storage, &ctx->capacity, 4, 4);
return ctx->capacity;
}
uint32_t save_fs_list_read_entry(save_filesystem_list_ctx_t *ctx, uint32_t index, save_fs_list_entry_t *entry) {
return save_allocation_table_storage_read(&ctx->storage, entry, index * SAVE_FS_LIST_ENTRY_SIZE, SAVE_FS_LIST_ENTRY_SIZE);
}
int save_fs_list_get_value(save_filesystem_list_ctx_t *ctx, uint32_t index, save_fs_list_entry_t *value) {
if (index >= save_fs_list_get_capacity(ctx)) {
return 0;
}
save_fs_list_read_entry(ctx, index, value);
return 1;
}
uint32_t save_fs_get_index_from_key(save_filesystem_list_ctx_t *ctx, save_entry_key_t *key, uint32_t *prev_index) {
save_fs_list_entry_t entry;
uint32_t capacity = save_fs_list_get_capacity(ctx);
save_fs_list_read_entry(ctx, ctx->used_list_head_index, &entry);
uint32_t prev;
if (!prev_index) {
prev_index = &prev;
}
*prev_index = ctx->used_list_head_index;
uint32_t index = entry.next;
while (index) {
if (index > capacity) {
EPRINTFARGS("Save entry index %d out of range!", index);
}
save_fs_list_read_entry(ctx, index, &entry);
if (entry.parent == key->parent && !strcmp(entry.name, key->name)) {
return index;
}
*prev_index = index;
index = entry.next;
}
*prev_index = 0xFFFFFFFF;
return 0xFFFFFFFF;
}
int save_hierarchical_file_table_find_path_recursive(hierarchical_save_file_table_ctx_t *ctx, save_entry_key_t *key, char *path) {
key->parent = 0;
char *pos = strchr(path, '/');
while (pos) {
memset(key->name, 0, SAVE_FS_LIST_MAX_NAME_LENGTH);
char *tmp = strchr(pos, '/');
if (!tmp) {
memcpy(key->name, pos, strlen(pos));
break;
}
memcpy(key->name, pos, tmp - pos);
key->parent = save_fs_get_index_from_key(&ctx->directory_table, key, NULL);
if (key->parent == 0xFFFFFFFF)
return 0;
pos = tmp + 1;
}
return 1;
}
int save_hierarchical_file_table_find_next_file(hierarchical_save_file_table_ctx_t *ctx, save_find_position_t *position, save_file_info_t *info, char *name) {
if (position->next_file == 0) {
return 0;
}
save_fs_list_entry_t entry;
if(!save_fs_list_get_value(&ctx->file_table, position->next_file, &entry)) {
return 0;
}
position->next_file = entry.value.next_sibling;
memcpy(name, &entry.name, SAVE_FS_LIST_MAX_NAME_LENGTH);
memcpy(info, &entry.value.save_file_info, sizeof(save_file_info_t));
return 1;
}
int save_hierarchical_file_table_find_next_directory(hierarchical_save_file_table_ctx_t *ctx, save_find_position_t *position, char *name) {
if (position->next_directory == 0) {
return 0;
}
save_fs_list_entry_t entry;
if(!save_fs_list_get_value(&ctx->directory_table, position->next_directory, &entry)) {
return 0;
}
position->next_directory = entry.value.next_sibling;
memcpy(name, &entry.name, SAVE_FS_LIST_MAX_NAME_LENGTH);
return 1;
}
int save_hierarchical_file_table_get_file_entry_by_path(hierarchical_save_file_table_ctx_t *ctx, char *path, save_fs_list_entry_t *entry) {
save_entry_key_t key;
if (!save_hierarchical_file_table_find_path_recursive(ctx, &key, path)) {
EPRINTF("Unable to locate file.");
return 0;
}
u32 index = save_fs_get_index_from_key(&ctx->file_table, &key, NULL);
if (index == 0xFFFFFFFF) {
EPRINTF("Unable to get table index for file.");
return 0;
}
if (!save_fs_list_get_value(&ctx->file_table, index, entry)) {
EPRINTF("Unable to get file entry from index.");
return 0;
}
return 1;
}
void save_open_fat_storage(save_filesystem_ctx_t *ctx, allocation_table_storage_ctx_t *storage_ctx, uint32_t block_index) {
storage_ctx->base_storage = ctx->base_storage;
storage_ctx->fat = &ctx->allocation_table;
storage_ctx->block_size = (uint32_t)ctx->header->block_size;
storage_ctx->initial_block = block_index;
storage_ctx->_length = block_index == 0xFFFFFFFF ? 0 : save_allocation_table_get_list_length(storage_ctx->fat, block_index) * storage_ctx->block_size;
}
void save_filesystem_init(save_filesystem_ctx_t *ctx, void *fat, save_fs_header_t *save_fs_header, fat_header_t *fat_header) {
ctx->allocation_table.base_storage = fat;
ctx->allocation_table.header = fat_header;
ctx->allocation_table.free_list_entry_index = 0;
ctx->header = save_fs_header;
save_open_fat_storage(ctx, &ctx->file_table.directory_table.storage, fat_header->directory_table_block);
save_open_fat_storage(ctx, &ctx->file_table.file_table.storage, fat_header->file_table_block);
ctx->file_table.file_table.free_list_head_index = 0;
ctx->file_table.file_table.used_list_head_index = 1;
ctx->file_table.directory_table.free_list_head_index = 0;
ctx->file_table.directory_table.used_list_head_index = 1;
}
validity_t save_ivfc_validate(hierarchical_integrity_verification_storage_ctx_t *ctx, ivfc_save_hdr_t *ivfc) {
validity_t result = VALIDITY_VALID;
for (unsigned int i = 0; i < ivfc->num_levels - 1 && result != VALIDITY_INVALID; i++) {
integrity_verification_storage_ctx_t *storage = &ctx->integrity_storages[i];
uint64_t block_size = storage->sector_size;
uint32_t block_count = (uint32_t)((storage->_length + block_size - 1) / block_size);
uint8_t *buffer = malloc(block_size);
for (unsigned int j = 0; j < block_count; j++) {
if (ctx->level_validities[ivfc->num_levels - 2][j] == VALIDITY_UNCHECKED) {
uint32_t to_read = storage->_length - block_size * j < block_size ? storage->_length - block_size * j : block_size;
save_ivfc_storage_read(storage, buffer, block_size * j, to_read, 1);
}
if (ctx->level_validities[ivfc->num_levels - 2][j] == VALIDITY_INVALID) {
result = VALIDITY_INVALID;
break;
}
}
free(buffer);
}
return result;
}
void save_ivfc_set_level_validities(hierarchical_integrity_verification_storage_ctx_t *ctx, ivfc_save_hdr_t *ivfc) {
for (unsigned int i = 0; i < ivfc->num_levels - 1; i++) {
validity_t level_validity = VALIDITY_VALID;
for (unsigned int j = 0; j < ctx->integrity_storages[i].sector_count; j++) {
if (ctx->level_validities[i][j] == VALIDITY_INVALID) {
level_validity = VALIDITY_INVALID;
break;
}
if (ctx->level_validities[i][j] == VALIDITY_UNCHECKED && level_validity != VALIDITY_INVALID) {
level_validity = VALIDITY_UNCHECKED;
}
}
ctx->levels[i].hash_validity = level_validity;
}
}
validity_t save_filesystem_verify(save_ctx_t *ctx) {
validity_t journal_validity = save_ivfc_validate(&ctx->core_data_ivfc_storage, &ctx->header.data_ivfc_header);
save_ivfc_set_level_validities(&ctx->core_data_ivfc_storage, &ctx->header.data_ivfc_header);
if (!ctx->fat_ivfc_storage.levels[0].save_ctx) return journal_validity;
validity_t fat_validity = save_ivfc_validate(&ctx->fat_ivfc_storage, &ctx->header.fat_ivfc_header);
save_ivfc_set_level_validities(&ctx->fat_ivfc_storage, &ctx->header.fat_ivfc_header);
if (journal_validity != VALIDITY_VALID) return journal_validity;
if (fat_validity != VALIDITY_VALID) return fat_validity;
return journal_validity;
}
void save_process(save_ctx_t *ctx) {
/* Try to parse Header A. */
f_lseek(ctx->file, 0);
if (f_read(ctx->file, &ctx->header, sizeof(ctx->header), NULL)) {
EPRINTF("Failed to read save header!\n");
}
save_process_header(ctx);
if (ctx->header_hash_validity == VALIDITY_INVALID) {
/* Try to parse Header B. */
f_lseek(ctx->file, 0x4000);
if (f_read(ctx->file, &ctx->header, sizeof(ctx->header), NULL)) {
EPRINTF("Failed to read save header!\n");
}
save_process_header(ctx);
if (ctx->header_hash_validity == VALIDITY_INVALID) {
EPRINTF("Error: Save header is invalid!\n");
}
}
unsigned char cmac[0x10];
memset(cmac, 0, 0x10);
se_aes_key_set(3, ctx->save_mac_key, 0x10);
se_aes_cmac(3, cmac, 0x10, &ctx->header.layout, sizeof(ctx->header.layout));
if (memcmp(cmac, &ctx->header.cmac, 0x10) == 0) {
ctx->header_cmac_validity = VALIDITY_VALID;
} else {
ctx->header_cmac_validity = VALIDITY_INVALID;
}
/* Initialize remap storages. */
ctx->data_remap_storage.type = STORAGE_BYTES;
ctx->data_remap_storage.base_storage_offset = ctx->header.layout.file_map_data_offset;
ctx->data_remap_storage.header = &ctx->header.main_remap_header;
ctx->data_remap_storage.map_entries = malloc(sizeof(remap_entry_ctx_t) * ctx->data_remap_storage.header->map_entry_count);
ctx->data_remap_storage.file = ctx->file;
f_lseek(ctx->file, ctx->header.layout.file_map_entry_offset);
for (unsigned int i = 0; i < ctx->data_remap_storage.header->map_entry_count; i++) {
f_read(ctx->file, &ctx->data_remap_storage.map_entries[i], 0x20, NULL);
ctx->data_remap_storage.map_entries[i].physical_offset_end = ctx->data_remap_storage.map_entries[i].physical_offset + ctx->data_remap_storage.map_entries[i].size;
ctx->data_remap_storage.map_entries[i].virtual_offset_end = ctx->data_remap_storage.map_entries[i].virtual_offset + ctx->data_remap_storage.map_entries[i].size;
}
/* Initialize data remap storage. */
ctx->data_remap_storage.segments = save_remap_init_segments(ctx->data_remap_storage.header, ctx->data_remap_storage.map_entries, ctx->data_remap_storage.header->map_entry_count);
/* Initialize duplex storage. */
ctx->duplex_layers[0].data_a = (uint8_t *)&ctx->header + ctx->header.layout.duplex_master_offset_a;
ctx->duplex_layers[0].data_b = (uint8_t *)&ctx->header + ctx->header.layout.duplex_master_offset_b;
memcpy(&ctx->duplex_layers[0].info, &ctx->header.duplex_header.layers[0], sizeof(duplex_info_t));
ctx->duplex_layers[1].data_a = malloc(ctx->header.layout.duplex_l1_size);
save_remap_read(&ctx->data_remap_storage, ctx->duplex_layers[1].data_a, ctx->header.layout.duplex_l1_offset_a, ctx->header.layout.duplex_l1_size);
ctx->duplex_layers[1].data_b = malloc(ctx->header.layout.duplex_l1_size);
save_remap_read(&ctx->data_remap_storage, ctx->duplex_layers[1].data_b, ctx->header.layout.duplex_l1_offset_b, ctx->header.layout.duplex_l1_size);
memcpy(&ctx->duplex_layers[1].info, &ctx->header.duplex_header.layers[1], sizeof(duplex_info_t));
ctx->duplex_layers[2].data_a = malloc(ctx->header.layout.duplex_data_size);
save_remap_read(&ctx->data_remap_storage, ctx->duplex_layers[2].data_a, ctx->header.layout.duplex_data_offset_a, ctx->header.layout.duplex_data_size);
ctx->duplex_layers[2].data_b = malloc(ctx->header.layout.duplex_data_size);
save_remap_read(&ctx->data_remap_storage, ctx->duplex_layers[2].data_b, ctx->header.layout.duplex_data_offset_b, ctx->header.layout.duplex_data_size);
memcpy(&ctx->duplex_layers[2].info, &ctx->header.duplex_header.layers[2], sizeof(duplex_info_t));
/* Initialize hierarchical duplex storage. */
uint8_t *bitmap = ctx->header.layout.duplex_index == 1 ? ctx->duplex_layers[0].data_b : ctx->duplex_layers[0].data_a;
save_duplex_storage_init(&ctx->duplex_storage.layers[0], &ctx->duplex_layers[1], bitmap, ctx->header.layout.duplex_master_size);
ctx->duplex_storage.layers[0]._length = ctx->header.layout.duplex_l1_size;
bitmap = malloc(ctx->duplex_storage.layers[0]._length);
save_duplex_storage_read(&ctx->duplex_storage.layers[0], bitmap, 0, ctx->duplex_storage.layers[0]._length);
save_duplex_storage_init(&ctx->duplex_storage.layers[1], &ctx->duplex_layers[2], bitmap, ctx->duplex_storage.layers[0]._length);
ctx->duplex_storage.layers[1]._length = ctx->header.layout.duplex_data_size;
ctx->duplex_storage.data_layer = ctx->duplex_storage.layers[1];
/* Initialize meta remap storage. */
ctx->meta_remap_storage.type = STORAGE_DUPLEX;
ctx->meta_remap_storage.duplex = &ctx->duplex_storage.data_layer;
ctx->meta_remap_storage.header = &ctx->header.meta_remap_header;
ctx->meta_remap_storage.map_entries = malloc(sizeof(remap_entry_ctx_t) * ctx->meta_remap_storage.header->map_entry_count);
ctx->meta_remap_storage.file = ctx->file;
f_lseek(ctx->file, ctx->header.layout.meta_map_entry_offset);
for (unsigned int i = 0; i < ctx->meta_remap_storage.header->map_entry_count; i++) {
f_read(ctx->file, &ctx->meta_remap_storage.map_entries[i], 0x20, NULL);
ctx->meta_remap_storage.map_entries[i].physical_offset_end = ctx->meta_remap_storage.map_entries[i].physical_offset + ctx->meta_remap_storage.map_entries[i].size;
ctx->meta_remap_storage.map_entries[i].virtual_offset_end = ctx->meta_remap_storage.map_entries[i].virtual_offset + ctx->meta_remap_storage.map_entries[i].size;
}
ctx->meta_remap_storage.segments = save_remap_init_segments(ctx->meta_remap_storage.header, ctx->meta_remap_storage.map_entries, ctx->meta_remap_storage.header->map_entry_count);
/* Initialize journal map. */
ctx->journal_map_info.map_storage = malloc(ctx->header.layout.journal_map_table_size);
save_remap_read(&ctx->meta_remap_storage, ctx->journal_map_info.map_storage, ctx->header.layout.journal_map_table_offset, ctx->header.layout.journal_map_table_size);
/* Initialize journal storage. */
ctx->journal_storage.header = &ctx->header.journal_header;
ctx->journal_storage.journal_data_offset = ctx->header.layout.journal_data_offset;
ctx->journal_storage._length = ctx->journal_storage.header->total_size - ctx->journal_storage.header->journal_size;
ctx->journal_storage.file = ctx->file;
ctx->journal_storage.map.header = &ctx->header.map_header;
ctx->journal_storage.map.map_storage = ctx->journal_map_info.map_storage;
ctx->journal_storage.map.entries = malloc(sizeof(journal_map_entry_t) * ctx->journal_storage.map.header->main_data_block_count);
uint32_t *pos = (uint32_t *)ctx->journal_storage.map.map_storage;
for (unsigned int i = 0; i < ctx->journal_storage.map.header->main_data_block_count; i++) {
ctx->journal_storage.map.entries[i].virtual_index = i;
ctx->journal_storage.map.entries[i].physical_index = *pos & 0x7FFFFFFF;
pos += 2;
}
ctx->journal_storage.block_size = ctx->journal_storage.header->block_size;
ctx->journal_storage._length = ctx->journal_storage.header->total_size - ctx->journal_storage.header->journal_size;
/* Initialize core IVFC storage. */
for (unsigned int i = 0; i < 5; i++) {
ctx->core_data_ivfc_storage.levels[i].save_ctx = ctx;
}
save_ivfc_storage_init(&ctx->core_data_ivfc_storage, ctx->header.layout.ivfc_master_hash_offset_a, &ctx->header.data_ivfc_header);
/* Initialize FAT storage. */
if (ctx->header.layout.version < 0x50000) {
ctx->fat_storage = malloc(ctx->header.layout.fat_size);
save_remap_read(&ctx->meta_remap_storage, ctx->fat_storage, ctx->header.layout.fat_offset, ctx->header.layout.fat_size);
} else {
for (unsigned int i = 0; i < 5; i++) {
ctx->fat_ivfc_storage.levels[i].save_ctx = ctx;
}
save_ivfc_storage_init(&ctx->fat_ivfc_storage, ctx->header.layout.fat_ivfc_master_hash_a, &ctx->header.fat_ivfc_header);
ctx->fat_storage = malloc(ctx->fat_ivfc_storage._length);
save_remap_read(&ctx->meta_remap_storage, ctx->fat_storage, ctx->header.fat_ivfc_header.level_headers[ctx->header.fat_ivfc_header.num_levels - 2].logical_offset, ctx->fat_ivfc_storage._length);
}
if (ctx->tool_ctx.action & ACTION_VERIFY) {
save_filesystem_verify(ctx);
}
/* Initialize core save filesystem. */
ctx->save_filesystem_core.base_storage = &ctx->core_data_ivfc_storage;
save_filesystem_init(&ctx->save_filesystem_core, ctx->fat_storage, &ctx->header.save_header, &ctx->header.fat_header);
}
void save_process_header(save_ctx_t *ctx) {
if (ctx->header.layout.magic != MAGIC_DISF || ctx->header.duplex_header.magic != MAGIC_DPFS ||
ctx->header.data_ivfc_header.magic != MAGIC_IVFC || ctx->header.journal_header.magic != MAGIC_JNGL ||
ctx->header.save_header.magic != MAGIC_SAVE || ctx->header.main_remap_header.magic != MAGIC_RMAP ||
ctx->header.meta_remap_header.magic != MAGIC_RMAP) {
EPRINTF("Error: Save header is corrupt!\n");
}
ctx->data_ivfc_master = (uint8_t *)&ctx->header + ctx->header.layout.ivfc_master_hash_offset_a;
ctx->fat_ivfc_master = (uint8_t *)&ctx->header + ctx->header.layout.fat_ivfc_master_hash_a;
uint8_t hash[0x20];
se_calc_sha256(hash, &ctx->header.duplex_header, 0x3D00);
ctx->header_hash_validity = memcmp(hash, ctx->header.layout.hash, 0x20) == 0 ? VALIDITY_VALID : VALIDITY_INVALID;
ctx->header.data_ivfc_header.num_levels = 5;
if (ctx->header.layout.version >= 0x50000) {
ctx->header.fat_ivfc_header.num_levels = 4;
}
}
void save_free_contexts(save_ctx_t *ctx) {
for (unsigned int i = 0; i < ctx->data_remap_storage.header->map_segment_count; i++) {
for (unsigned int j = 0; j < ctx->data_remap_storage.segments[i].entry_count; j++) {
free(&ctx->data_remap_storage.segments[i].entries[j]);
}
}
free(ctx->data_remap_storage.segments);
for (unsigned int i = 0; i < ctx->meta_remap_storage.header->map_segment_count; i++) {
for (unsigned int j = 0; j < ctx->meta_remap_storage.segments[i].entry_count; j++) {
free(&ctx->meta_remap_storage.segments[i].entries[j]);
}
}
free(ctx->meta_remap_storage.segments);
free(ctx->data_remap_storage.map_entries);
free(ctx->meta_remap_storage.map_entries);
free(ctx->duplex_storage.layers[0].bitmap.bitmap);
free(ctx->duplex_storage.layers[1].bitmap.bitmap);
free(ctx->duplex_storage.layers[1].bitmap_storage);
for (unsigned int i = 1; i < 3; i++) {
free(ctx->duplex_layers[i].data_a);
free(ctx->duplex_layers[i].data_b);
}
free(ctx->journal_map_info.map_storage);
free(ctx->journal_storage.map.entries);
for (unsigned int i = 0; i < ctx->header.data_ivfc_header.num_levels - 1; i++) {
free(ctx->core_data_ivfc_storage.integrity_storages[i].block_validities);
}
free(ctx->core_data_ivfc_storage.level_validities);
if (ctx->header.layout.version >= 0x50000) {
for (unsigned int i = 0; i < ctx->header.fat_ivfc_header.num_levels - 1; i++) {
free(ctx->fat_ivfc_storage.integrity_storages[i].block_validities);
}
}
free(ctx->fat_ivfc_storage.level_validities);
free(ctx->fat_storage);
}

489
source/keys/save.h Normal file
View File

@ -0,0 +1,489 @@
#ifndef _SAVE_H
#define _SAVE_H
#include <stddef.h>
#include <stdint.h>
#include "../libs/fatfs/ff.h"
#define SAVE_HEADER_SIZE 0x4000
#define SAVE_FAT_ENTRY_SIZE 8
#define SAVE_FS_LIST_MAX_NAME_LENGTH 0x40
#define SAVE_FS_LIST_ENTRY_SIZE 0x60
#define IVFC_MAX_LEVEL 6
#define MAGIC_DISF 0x46534944
#define MAGIC_DPFS 0x53465044
#define MAGIC_JNGL 0x4C474E4A
#define MAGIC_SAVE 0x45564153
#define MAGIC_RMAP 0x50414D52
#define MAGIC_IVFC 0x43465649
typedef enum {
VALIDITY_UNCHECKED = 0,
VALIDITY_INVALID,
VALIDITY_VALID
} validity_t;
typedef struct save_ctx_t save_ctx_t;
typedef struct {
uint32_t magic; /* DISF */
uint32_t version;
uint8_t hash[0x20];
uint64_t file_map_entry_offset;
uint64_t file_map_entry_size;
uint64_t meta_map_entry_offset;
uint64_t meta_map_entry_size;
uint64_t file_map_data_offset;
uint64_t file_map_data_size;
uint64_t duplex_l1_offset_a;
uint64_t duplex_l1_offset_b;
uint64_t duplex_l1_size;
uint64_t duplex_data_offset_a;
uint64_t duplex_data_offset_b;
uint64_t duplex_data_size;
uint64_t journal_data_offset;
uint64_t journal_data_size_a;
uint64_t journal_data_size_b;
uint64_t journal_size;
uint64_t duplex_master_offset_a;
uint64_t duplex_master_offset_b;
uint64_t duplex_master_size;
uint64_t ivfc_master_hash_offset_a;
uint64_t ivfc_master_hash_offset_b;
uint64_t ivfc_master_hash_size;
uint64_t journal_map_table_offset;
uint64_t journal_map_table_size;
uint64_t journal_physical_bitmap_offset;
uint64_t journal_physical_bitmap_size;
uint64_t journal_virtual_bitmap_offset;
uint64_t journal_virtual_bitmap_size;
uint64_t journal_free_bitmap_offset;
uint64_t journal_free_bitmap_size;
uint64_t ivfc_l1_offset;
uint64_t ivfc_l1_size;
uint64_t ivfc_l2_offset;
uint64_t ivfc_l2_size;
uint64_t ivfc_l3_offset;
uint64_t ivfc_l3_size;
uint64_t fat_offset;
uint64_t fat_size;
uint64_t duplex_index;
uint64_t fat_ivfc_master_hash_a;
uint64_t fat_ivfc_master_hash_b;
uint64_t fat_ivfc_l1_offset;
uint64_t fat_ivfc_l1_size;
uint64_t fat_ivfc_l2_offset;
uint64_t fat_ivfc_l2_size;
uint8_t _0x190[0x70];
} fs_layout_t;
#pragma pack(push, 1)
typedef struct {
uint64_t offset;
uint64_t length;
uint32_t block_size_power;
} duplex_info_t;
#pragma pack(pop)
typedef struct {
uint32_t magic; /* DPFS */
uint32_t version;
duplex_info_t layers[3];
} duplex_header_t;
typedef struct {
uint32_t version;
uint32_t main_data_block_count;
uint32_t journal_block_count;
uint32_t _0x0C;
} journal_map_header_t;
typedef struct {
uint32_t magic; /* JNGL */
uint32_t version;
uint64_t total_size;
uint64_t journal_size;
uint64_t block_size;
} journal_header_t;
typedef struct {
uint32_t magic; /* SAVE */
uint32_t version;
uint64_t block_count;
uint64_t block_size;
} save_fs_header_t;
typedef struct {
uint64_t block_size;
uint64_t allocation_table_offset;
uint32_t allocation_table_block_count;
uint32_t _0x14;
uint64_t data_offset;
uint32_t data_block_count;
uint32_t _0x24;
uint32_t directory_table_block;
uint32_t file_table_block;
} fat_header_t;
typedef struct {
uint32_t magic; /* RMAP */
uint32_t version;
uint32_t map_entry_count;
uint32_t map_segment_count;
uint32_t segment_bits;
uint8_t _0x14[0x2C];
} remap_header_t;
typedef struct remap_segment_ctx_t remap_segment_ctx_t;
typedef struct remap_entry_ctx_t remap_entry_ctx_t;
#pragma pack(push, 1)
struct remap_entry_ctx_t {
uint64_t virtual_offset;
uint64_t physical_offset;
uint64_t size;
uint32_t alignment;
uint32_t _0x1C;
uint64_t virtual_offset_end;
uint64_t physical_offset_end;
remap_segment_ctx_t *segment;
remap_entry_ctx_t *next;
};
#pragma pack(pop)
struct remap_segment_ctx_t{
uint64_t offset;
uint64_t length;
remap_entry_ctx_t *entries;
uint64_t entry_count;
};
typedef struct {
uint8_t *data;
uint8_t *bitmap;
} duplex_bitmap_t;
typedef struct {
uint32_t block_size;
uint8_t *bitmap_storage;
uint8_t *data_a;
uint8_t *data_b;
duplex_bitmap_t bitmap;
uint64_t _length;
} duplex_storage_ctx_t;
enum base_storage_type {
STORAGE_BYTES = 0,
STORAGE_DUPLEX = 1,
STORAGE_REMAP = 2,
STORAGE_JOURNAL = 3
};
typedef struct {
remap_header_t *header;
remap_entry_ctx_t *map_entries;
remap_segment_ctx_t *segments;
enum base_storage_type type;
uint64_t base_storage_offset;
duplex_storage_ctx_t *duplex;
FIL *file;
} remap_storage_ctx_t;
typedef struct {
uint64_t title_id;
uint8_t user_id[0x10];
uint64_t save_id;
uint8_t save_data_type;
uint8_t _0x21[0x1F];
uint64_t save_owner_id;
uint64_t timestamp;
uint64_t _0x50;
uint64_t data_size;
uint64_t journal_size;
uint64_t commit_id;
} extra_data_t;
typedef struct {
uint64_t logical_offset;
uint64_t hash_data_size;
uint32_t block_size;
uint32_t reserved;
} ivfc_level_hdr_t;
typedef struct {
uint32_t magic;
uint32_t id;
uint32_t master_hash_size;
uint32_t num_levels;
ivfc_level_hdr_t level_headers[IVFC_MAX_LEVEL];
uint8_t salt_source[0x20];
} ivfc_save_hdr_t;
#pragma pack(push, 1)
typedef struct {
uint8_t cmac[0x10];
uint8_t _0x10[0xF0];
fs_layout_t layout;
duplex_header_t duplex_header;
ivfc_save_hdr_t data_ivfc_header;
uint32_t _0x404;
journal_header_t journal_header;
journal_map_header_t map_header;
uint8_t _0x438[0x1D0];
save_fs_header_t save_header;
fat_header_t fat_header;
remap_header_t main_remap_header, meta_remap_header;
uint64_t _0x6D0;
extra_data_t extra_data;
uint8_t _0x748[0x390];
ivfc_save_hdr_t fat_ivfc_header;
uint8_t _0xB98[0x3468];
} save_header_t;
#pragma pack(pop)
typedef struct {
duplex_storage_ctx_t layers[2];
duplex_storage_ctx_t data_layer;
uint64_t _length;
} hierarchical_duplex_storage_ctx_t;
typedef struct {
uint8_t *data_a;
uint8_t *data_b;
duplex_info_t info;
} duplex_fs_layer_info_t;
typedef struct {
uint8_t *map_storage;
uint8_t *physical_block_bitmap;
uint8_t *virtual_block_bitmap;
uint8_t *free_block_bitmap;
} journal_map_params_t;
typedef struct {
uint32_t physical_index;
uint32_t virtual_index;
} journal_map_entry_t;
typedef struct {
journal_map_header_t *header;
journal_map_entry_t *entries;
uint8_t *map_storage;
} journal_map_ctx_t;
typedef struct {
journal_map_ctx_t map;
journal_header_t *header;
uint32_t block_size;
uint64_t journal_data_offset;
uint64_t _length;
FIL *file;
} journal_storage_ctx_t;
typedef struct {
uint64_t data_offset;
uint64_t data_size;
uint64_t hash_offset;
uint32_t hash_block_size;
validity_t hash_validity;
enum base_storage_type type;
save_ctx_t *save_ctx;
} ivfc_level_save_ctx_t;
typedef struct {
ivfc_level_save_ctx_t *data;
uint32_t block_size;
uint8_t salt[0x20];
} integrity_verification_info_ctx_t;
typedef struct integrity_verification_storage_ctx_t integrity_verification_storage_ctx_t;
struct integrity_verification_storage_ctx_t {
ivfc_level_save_ctx_t *hash_storage;
ivfc_level_save_ctx_t *base_storage;
validity_t *block_validities;
uint8_t salt[0x20];
uint32_t sector_size;
uint32_t sector_count;
uint64_t _length;
integrity_verification_storage_ctx_t *next_level;
};
typedef struct {
ivfc_level_save_ctx_t levels[5];
ivfc_level_save_ctx_t *data_level;
validity_t **level_validities;
uint64_t _length;
integrity_verification_storage_ctx_t integrity_storages[4];
} hierarchical_integrity_verification_storage_ctx_t;
typedef struct {
uint32_t prev;
uint32_t next;
} allocation_table_entry_t;
typedef struct {
uint32_t free_list_entry_index;
void *base_storage;
fat_header_t *header;
} allocation_table_ctx_t;
typedef struct {
hierarchical_integrity_verification_storage_ctx_t *base_storage;
uint32_t block_size;
uint32_t initial_block;
allocation_table_ctx_t *fat;
uint64_t _length;
} allocation_table_storage_ctx_t;
typedef struct {
allocation_table_ctx_t *fat;
uint32_t virtual_block;
uint32_t physical_block;
uint32_t current_segment_size;
uint32_t next_block;
uint32_t prev_block;
} allocation_table_iterator_ctx_t;
typedef struct {
char name[SAVE_FS_LIST_MAX_NAME_LENGTH];
uint32_t parent;
} save_entry_key_t;
#pragma pack(push, 1)
typedef struct {
uint32_t start_block;
uint64_t length;
uint32_t _0xC[2];
} save_file_info_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
uint32_t next_directory;
uint32_t next_file;
uint32_t _0x8[3];
} save_find_position_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
uint32_t next_sibling;
union { /* Save table entry type. Size = 0x14. */
save_file_info_t save_file_info;
save_find_position_t save_find_position;
};
} save_table_entry_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
uint32_t parent;
char name[SAVE_FS_LIST_MAX_NAME_LENGTH];
save_table_entry_t value;
uint32_t next;
} save_fs_list_entry_t;
#pragma pack(pop)
typedef struct {
uint32_t free_list_head_index;
uint32_t used_list_head_index;
allocation_table_storage_ctx_t storage;
uint32_t capacity;
} save_filesystem_list_ctx_t;
typedef struct {
save_filesystem_list_ctx_t file_table;
save_filesystem_list_ctx_t directory_table;
} hierarchical_save_file_table_ctx_t;
typedef struct {
hierarchical_integrity_verification_storage_ctx_t *base_storage;
allocation_table_ctx_t allocation_table;
save_fs_header_t *header;
hierarchical_save_file_table_ctx_t file_table;
} save_filesystem_ctx_t;
#define ACTION_VERIFY (1<<2)
struct save_ctx_t {
save_header_t header;
FIL *file;
struct {
FIL *file;
uint32_t action;
} tool_ctx;
validity_t header_cmac_validity;
validity_t header_hash_validity;
uint8_t *data_ivfc_master;
uint8_t *fat_ivfc_master;
remap_storage_ctx_t data_remap_storage;
remap_storage_ctx_t meta_remap_storage;
duplex_fs_layer_info_t duplex_layers[3];
hierarchical_duplex_storage_ctx_t duplex_storage;
journal_storage_ctx_t journal_storage;
journal_map_params_t journal_map_info;
hierarchical_integrity_verification_storage_ctx_t core_data_ivfc_storage;
hierarchical_integrity_verification_storage_ctx_t fat_ivfc_storage;
uint8_t *fat_storage;
save_filesystem_ctx_t save_filesystem_core;
uint8_t save_mac_key[0x10];
};
static inline uint32_t allocation_table_entry_index_to_block(uint32_t entry_index) {
return entry_index - 1;
}
static inline uint32_t allocation_table_block_to_entry_index(uint32_t block_index) {
return block_index + 1;
}
static inline int allocation_table_is_list_end(allocation_table_entry_t *entry) {
return (entry->next & 0x7FFFFFFF) == 0;
}
static inline int allocation_table_is_list_start(allocation_table_entry_t *entry) {
return entry->prev == 0x80000000;
}
static inline int allocation_table_get_next(allocation_table_entry_t *entry) {
return entry->next & 0x7FFFFFFF;
}
static inline int allocation_table_get_prev(allocation_table_entry_t *entry) {
return entry->prev & 0x7FFFFFFF;
}
static inline allocation_table_entry_t *save_allocation_table_read_entry(allocation_table_ctx_t *ctx, uint32_t entry_index) {
return (allocation_table_entry_t *)((uint8_t *)ctx->base_storage + entry_index * SAVE_FAT_ENTRY_SIZE);
}
static inline uint32_t save_allocation_table_get_free_list_entry_index(allocation_table_ctx_t *ctx) {
return allocation_table_get_next(save_allocation_table_read_entry(ctx, ctx->free_list_entry_index));
}
static inline uint32_t save_allocation_table_get_free_list_block_index(allocation_table_ctx_t *ctx) {
return allocation_table_entry_index_to_block(save_allocation_table_get_free_list_entry_index(ctx));
}
void save_process(save_ctx_t *ctx);
void save_process_header(save_ctx_t *ctx);
void save_save(save_ctx_t *ctx);
void save_print(save_ctx_t *ctx);
void save_free_contexts(save_ctx_t *ctx);
void save_open_fat_storage(save_filesystem_ctx_t *ctx, allocation_table_storage_ctx_t *storage_ctx, uint32_t block_index);
uint32_t save_allocation_table_storage_read(allocation_table_storage_ctx_t *ctx, void *buffer, uint64_t offset, size_t count);
int save_fs_list_get_value(save_filesystem_list_ctx_t *ctx, uint32_t index, save_fs_list_entry_t *value);
uint32_t save_fs_get_index_from_key(save_filesystem_list_ctx_t *ctx, save_entry_key_t *key, uint32_t *prev_index);
int save_hierarchical_file_table_find_path_recursive(hierarchical_save_file_table_ctx_t *ctx, save_entry_key_t *key, char *path);
int save_hierarchical_file_table_get_file_entry_by_path(hierarchical_save_file_table_ctx_t *ctx, char *path, save_fs_list_entry_t *entry);
#endif

View File

@ -107,7 +107,7 @@ static inline int _emmc_xts(u32 ks1, u32 ks2, u32 enc, u8 *tweak, bool regen_twe
pdst += 0x10;
}
se_aes_crypt_ecb(ks2, 0, dst, secsize, src, secsize);
se_aes_crypt_ecb(ks2, enc, dst, secsize, src, secsize);
pdst = (u8 *)dst;
@ -150,12 +150,11 @@ DRESULT disk_read (
__attribute__ ((aligned (16))) static u8 tweak[0x10];
__attribute__ ((aligned (16))) static u64 prev_cluster = -1;
__attribute__ ((aligned (16))) static u32 prev_sector = 0;
u32 tweak_exp = 0;
bool regen_tweak = true, cache_sector = false;
bool needs_cache_sector = false;
if (secindex == 0 || clear_sector_cache) {
free(sector_cache);
sector_cache = (sector_cache_t *)malloc(sizeof(sector_cache_t) * MAX_SEC_CACHE_ENTRIES);
if (!sector_cache)
sector_cache = (sector_cache_t *)malloc(sizeof(sector_cache_t) * MAX_SEC_CACHE_ENTRIES);
clear_sector_cache = false;
secindex = 0;
}
@ -176,12 +175,14 @@ DRESULT disk_read (
if (s == secindex && s < MAX_SEC_CACHE_ENTRIES) {
sector_cache[s].sector = sector;
sector_cache[s].visit_count++;
cache_sector = true;
needs_cache_sector = true;
secindex++;
}
}
if (nx_emmc_part_read(&storage, system_part, sector, count, buff)) {
u32 tweak_exp = 0;
bool regen_tweak = true;
if (prev_cluster != sector / 0x20) { // sector in different cluster than last read
prev_cluster = sector / 0x20;
tweak_exp = sector % 0x20;
@ -194,7 +195,7 @@ DRESULT disk_read (
// fatfs will never pull more than a cluster
_emmc_xts(9, 8, 0, tweak, regen_tweak, tweak_exp, prev_cluster, buff, buff, count * 0x200);
if (cache_sector) {
if (needs_cache_sector) {
memcpy(sector_cache[s].cached_sector, buff, 0x200);
memcpy(sector_cache[s].tweak, tweak, 0x10);
}

View File

@ -97,7 +97,7 @@
*/
#define FF_USE_LFN 1
#define FF_USE_LFN 3
#define FF_MAX_LFN 255
/* The FF_USE_LFN switches the support for LFN (long file name).
/

View File

@ -56,6 +56,7 @@ static u32 _heap_alloc(heap_t *heap, u32 size, u32 alignment)
node->used = 1;
new->used = 0;
new->next = node->next;
new->next->prev = new;
new->prev = node;
node->next = new;
@ -82,7 +83,7 @@ static void _heap_free(heap_t *heap, u32 addr)
hnode_t *node = (hnode_t *)(addr - sizeof(hnode_t));
node->used = 0;
node = heap->first;
while (node)
while (1)
{
if (!node->used)
{
@ -94,7 +95,14 @@ static void _heap_free(heap_t *heap, u32 addr)
node->next->prev = node->prev;
}
}
node = node->next;
if (node->next)
node = node->next;
else
{
node->size = -1;
break;
}
}
}
@ -113,7 +121,7 @@ void *malloc(u32 size)
void *calloc(u32 num, u32 size)
{
void *res = (void *)_heap_alloc(&_heap, num * size, sizeof(hnode_t));
memset(res, 0, num * size);
memset(res, 0, ALIGN(num * size, sizeof(hnode_t)));
return res;
}

View File

@ -43,9 +43,6 @@
#define MAX17050_VMAX_TOLERANCE 50 /* 50 mV */
#pragma GCC push_options
#pragma GCC optimize ("Os")
int max17050_get_property(enum MAX17050_reg reg, int *value)
{
u16 data;
@ -267,5 +264,3 @@ int max17050_fix_configuration()
return 0;
}
#pragma GCC pop_options

View File

@ -94,17 +94,15 @@ static int _se_wait()
static int _se_execute(u32 op, void *dst, u32 dst_size, const void *src, u32 src_size)
{
se_ll_t *ll_dst = NULL, *ll_src = NULL;
se_ll_t *ll_dst = (se_ll_t *)0xECFFFFE0, *ll_src = (se_ll_t *)0xECFFFFF0;
if (dst)
{
ll_dst = (se_ll_t *)malloc(sizeof(se_ll_t));
_se_ll_init(ll_dst, (u32)dst, dst_size);
}
if (src)
{
ll_src = (se_ll_t *)malloc(sizeof(se_ll_t));
_se_ll_init(ll_src, (u32)src, src_size);
}
@ -120,11 +118,6 @@ static int _se_execute(u32 op, void *dst, u32 dst_size, const void *src, u32 src
bpmp_mmu_maintenance(BPMP_MMU_MAINT_CLN_INV_WAY);
if (src)
free(ll_src);
if (dst)
free(ll_dst);
return res;
}
@ -428,7 +421,7 @@ int se_calc_sha256(void *dst, const void *src, u32 src_size)
int res;
// Setup config for SHA256, size = BITS(src_size).
SE(SE_CONFIG_REG_OFFSET) = SE_CONFIG_ENC_MODE(MODE_SHA256) | SE_CONFIG_ENC_ALG(ALG_SHA) | SE_CONFIG_DST(DST_HASHREG);
SE(SE_SHA_CONFIG_REG_OFFSET) = SHA_ENABLE;
SE(SE_SHA_CONFIG_REG_OFFSET) = SHA_INIT_ENABLE;
SE(SE_SHA_MSG_LENGTH_REG_OFFSET) = (u32)(src_size << 3);
SE(SE_SHA_MSG_LENGTH_REG_OFFSET + 4 * 1) = 0;
SE(SE_SHA_MSG_LENGTH_REG_OFFSET + 4 * 2) = 0;
@ -448,3 +441,46 @@ int se_calc_sha256(void *dst, const void *src, u32 src_size)
return res;
}
int se_calc_hmac_sha256(void *dst, const void *src, u32 src_size, const void *key, u32 key_size) {
int res = 0;
u8 *secret = (u8 *)malloc(0x40);
u8 *ipad = (u8 *)malloc(0x40 + src_size);
u8 *opad = (u8 *)malloc(0x60);
if (key_size > 0x40)
{
if (!se_calc_sha256(secret, key, key_size))
goto out;
memset(secret + 0x20, 0, 0x20);
}
else
{
memcpy(secret, key, key_size);
memset(secret + key_size, 0, 0x40 - key_size);
}
u32 *secret32 = (u32 *)secret;
u32 *ipad32 = (u32 *)ipad;
u32 *opad32 = (u32 *)opad;
for (u32 i = 0; i < 0x10; i++)
{
ipad32[i] = secret32[i] ^ 0x36363636;
opad32[i] = secret32[i] ^ 0x5C5C5C5C;
}
memcpy(ipad + 0x40, src, src_size);
if (!se_calc_sha256(dst, ipad, 0x40 + src_size))
goto out;
memcpy(opad + 0x40, dst, 0x20);
if (!se_calc_sha256(dst, opad, 0x60))
goto out;
res = 1;
out:;
free(secret);
free(ipad);
free(opad);
return res;
}

View File

@ -35,5 +35,6 @@ int se_aes_xts_crypt_sec(u32 ks1, u32 ks2, u32 enc, u64 sec, void *dst, const vo
int se_aes_xts_crypt(u32 ks1, u32 ks2, u32 enc, u64 sec, void *dst, const void *src, u32 secsize, u32 num_secs);
int se_aes_cmac(u32 ks, void *dst, u32 dst_size, const void *src, u32 src_size);
int se_calc_sha256(void *dst, const void *src, u32 src_size);
int se_calc_hmac_sha256(void *dst, const void *src, u32 src_size, const void *key, u32 key_size);
#endif

View File

@ -231,8 +231,8 @@
#define SE_SPARE_0_REG_OFFSET 0x80c
#define SE_SHA_CONFIG_REG_OFFSET 0x200
#define SHA_DISABLE 0
#define SHA_ENABLE 1
#define SHA_INIT_DISABLE 0
#define SHA_INIT_ENABLE 1
#define SE_SHA_MSG_LENGTH_REG_OFFSET 0x204
#define SE_SHA_MSG_LEFT_REG_OFFSET 0x214

View File

@ -15,6 +15,7 @@
*/
#include "../soc/clock.h"
#include "../soc/kfuse.h"
#include "../soc/t210.h"
#include "../utils/util.h"
#include "../storage/sdmmc.h"
@ -188,6 +189,7 @@ void clock_enable_kfuse()
usleep(10);
CLOCK(CLK_RST_CONTROLLER_RST_DEVICES_H) &= 0xFFFFFEFF;
usleep(20);
kfuse_wait_ready();
}
void clock_disable_kfuse()

View File

@ -37,12 +37,31 @@
#define FUSE_WRITE_ACCESS_SW 0x30
#define FUSE_PWR_GOOD_SW 0x34
#define FUSE_SKU_INFO 0x110
#define FUSE_CPU_SPEEDO_0_CALIB 0x114
#define FUSE_CPU_IDDQ_CALIB 0x118
#define FUSE_OPT_FT_REV 0x128
#define FUSE_CPU_SPEEDO_1_CALIB 0x12C
#define FUSE_CPU_SPEEDO_2_CALIB 0x130
#define FUSE_SOC_SPEEDO_0_CALIB 0x134
#define FUSE_SOC_SPEEDO_1_CALIB 0x138
#define FUSE_SOC_SPEEDO_2_CALIB 0x13C
#define FUSE_SOC_IDDQ_CALIB 0x140
#define FUSE_OPT_CP_REV 0x190
#define FUSE_FIRST_BOOTROM_PATCH_SIZE 0x19c
#define FUSE_PRIVATE_KEY0 0x1A4
#define FUSE_PRIVATE_KEY1 0x1A8
#define FUSE_PRIVATE_KEY2 0x1AC
#define FUSE_PRIVATE_KEY3 0x1B0
#define FUSE_PRIVATE_KEY4 0x1B4
#define FUSE_RESERVED_SW 0x1C0
#define FUSE_OPT_VENDOR_CODE 0x200
#define FUSE_OPT_FAB_CODE 0x204
#define FUSE_OPT_LOT_CODE_0 0x208
#define FUSE_OPT_LOT_CODE_1 0x20C
#define FUSE_OPT_WAFER_ID 0x210
#define FUSE_OPT_X_COORDINATE 0x214
#define FUSE_OPT_Y_COORDINATE 0x218
#define FUSE_GPU_IDDQ_CALIB 0x228
/*! Fuse commands. */
#define FUSE_READ 0x1

57
source/soc/kfuse.c Normal file
View File

@ -0,0 +1,57 @@
/*
* Copyright (c) 2018 naehrwert
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../soc/kfuse.h"
#include "../soc/clock.h"
#include "../soc/t210.h"
#include "../utils/util.h"
int kfuse_read(u32 *buf)
{
int res = 0;
clock_enable_kfuse();
while (!(KFUSE(KFUSE_STATE) & KFUSE_STATE_DONE))
;
if (!(KFUSE(KFUSE_STATE) & KFUSE_STATE_CRCPASS))
goto out;
KFUSE(KFUSE_KEYADDR) = KFUSE_KEYADDR_AUTOINC;
for (int i = 0; i < KFUSE_NUM_WORDS; i++)
buf[i] = KFUSE(KFUSE_KEYS);
res = 1;
out:;
clock_disable_kfuse();
return res;
}
int kfuse_wait_ready()
{
// Wait for KFUSE to finish init and verification of data.
while (!(KFUSE(KFUSE_STATE) & KFUSE_STATE_DONE))
{
usleep(500);
}
if (!(KFUSE(KFUSE_STATE) & KFUSE_STATE_CRCPASS))
return 0;
return 1;
}

42
source/soc/kfuse.h Normal file
View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2018 naehrwert
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _KFUSE_H_
#define _KFUSE_H_
#include "../utils/types.h"
#define KFUSE_STATE_SOFTRESET (1 << 31)
#define KFUSE_STATE_STOP (1 << 25)
#define KFUSE_STATE_RESTART (1 << 24)
#define KFUSE_STATE_CRCPASS (1 << 17)
#define KFUSE_STATE_DONE (1 << 16)
#define KFUSE_STATE_ERRBLOCK_MASK 0x3F00
#define KFUSE_STATE_ERRBLOCK_SHIFT 8
#define KFUSE_STATE_CURBLOCK_MASK 0x3F
#define KFUSE_KEYADDR_AUTOINC (1<<16)
#define KFUSE_STATE 0x80
#define KFUSE_KEYADDR 0x88
#define KFUSE_KEYS 0x8C
#define KFUSE_NUM_WORDS 144
int kfuse_read(u32 *buf);
int kfuse_wait_ready();
#endif

View File

@ -37,8 +37,8 @@ void nx_emmc_gpt_parse(link_t *gpt, sdmmc_storage_t *storage)
part->attrs = ent->attrs;
//HACK
for (u32 i = 0; i < 36; i++)
part->name[i] = ent->name[i];
for (u32 j = 0; j < 36; j++)
part->name[j] = ent->name[j];
part->name[36] = 0;
list_append(gpt, &part->link);

View File

@ -32,10 +32,6 @@
//#define DPRINTF(...) gfx_printf(__VA_ARGS__)
#define DPRINTF(...)
#pragma GCC push_options
#pragma GCC target ("thumb")
#pragma GCC optimize ("Os")
/*! SCMMC controller base addresses. */
static const u32 _sdmmc_bases[4] = {
0x700B0000,
@ -81,7 +77,7 @@ static int _sdmmc_set_voltage(sdmmc_t *sdmmc, u32 power)
{
pwr |= TEGRA_MMC_PWRCTL_SD_BUS_POWER;
sdmmc->regs->pwrcon = pwr;
}
}
return 1;
}
@ -389,7 +385,7 @@ int sdmmc_get_rsp(sdmmc_t *sdmmc, u32 *rsp, u32 size, u32 type)
static void _sdmmc_reset(sdmmc_t *sdmmc)
{
sdmmc->regs->swrst |=
sdmmc->regs->swrst |=
TEGRA_MMC_SWRST_SW_RESET_FOR_CMD_LINE | TEGRA_MMC_SWRST_SW_RESET_FOR_DAT_LINE;
_sdmmc_get_clkcon(sdmmc);
u32 timeout = get_tmr_ms() + 2000;
@ -460,7 +456,7 @@ static int _sdmmc_setup_read_small_block(sdmmc_t *sdmmc)
static int _sdmmc_parse_cmdbuf(sdmmc_t *sdmmc, sdmmc_cmd_t *cmd, bool is_data_present)
{
u16 cmdflags = 0;
switch (cmd->rsp_type)
{
case SDMMC_RSP_TYPE_0:
@ -726,7 +722,7 @@ static int _sdmmc_check_mask_interrupt(sdmmc_t *sdmmc, u16 *pout, u16 mask)
sdmmc->regs->norintsts = norintsts & mask;
return SDMMC_MASKINT_MASKED;
}
return SDMMC_MASKINT_NOERROR;
}
@ -771,7 +767,7 @@ static int _sdmmc_stop_transmission_inner(sdmmc_t *sdmmc, u32 *rsp)
if (!res)
return 0;
_sdmmc_cache_rsp(sdmmc, rsp, 4, SDMMC_RSP_TYPE_1);
return _sdmmc_wait_prnsts_type1(sdmmc);
@ -905,7 +901,7 @@ static int _sdmmc_execute_cmd_inner(sdmmc_t *sdmmc, sdmmc_cmd_t *cmd, sdmmc_req_
_sdmmc_parse_cmdbuf(sdmmc, cmd, is_data_present);
int res = _sdmmc_wait_request(sdmmc);
DPRINTF("rsp(%d): %08X, %08X, %08X, %08X\n", res,
DPRINTF("rsp(%d): %08X, %08X, %08X, %08X\n", res,
sdmmc->regs->rspreg0, sdmmc->regs->rspreg1, sdmmc->regs->rspreg2, sdmmc->regs->rspreg3);
if (res)
{
@ -947,7 +943,7 @@ static int _sdmmc_config_sdmmc1()
gpio_output_enable(GPIO_PORT_Z, GPIO_PIN_1, GPIO_OUTPUT_DISABLE);
usleep(100);
// Check if SD card is inserted.
// Check if SD card is inserted.
if(!!gpio_read(GPIO_PORT_Z, GPIO_PIN_1))
return 0;
@ -1059,7 +1055,7 @@ void sdmmc_end(sdmmc_t *sdmmc)
if (!sdmmc->clock_stopped)
{
_sdmmc_sd_clock_disable(sdmmc);
// Disable SDMMC power.
// Disable SDMMC power.
_sdmmc_set_voltage(sdmmc, SDMMC_POWER_OFF);
// Disable SD card power.
@ -1138,7 +1134,7 @@ int sdmmc_enable_low_voltage(sdmmc_t *sdmmc)
_sdmmc_set_voltage(sdmmc, SDMMC_POWER_1_8);
_sdmmc_get_clkcon(sdmmc);
msleep(5);
if (sdmmc->regs->hostctl2 & SDHCI_CTRL_VDD_180)
{
sdmmc->regs->clkcon |= TEGRA_MMC_CLKCON_SD_CLOCK_ENABLE;
@ -1150,5 +1146,3 @@ int sdmmc_enable_low_voltage(sdmmc_t *sdmmc)
return 0;
}
#pragma GCC pop_options