forked from Mirror/GodMode9
On-the-fly decryption for DSiWare Exports
This commit is contained in:
parent
83b2fba142
commit
fc97df6466
@ -128,6 +128,9 @@ u64 IdentifyFileType(const char* path) {
|
||||
if (fsize < TEMP_BUFFER_SIZE) type |= TXT_GENERIC;
|
||||
return type;
|
||||
} else if ((strnlen(fname, 16) == 8) && (sscanf(fname, "%08lx", &id) == 1)) {
|
||||
if (strncmp(path + 2, "/Nintendo DSiWare/", 18) == 0)
|
||||
return GAME_DSIWE;
|
||||
|
||||
char path_cdn[256];
|
||||
char* name_cdn = path_cdn + (fname - path);
|
||||
strncpy(path_cdn, path, 256);
|
||||
|
@ -17,17 +17,18 @@
|
||||
#define GAME_3DSX (1ULL<<12)
|
||||
#define GAME_NDS (1ULL<<13)
|
||||
#define GAME_GBA (1ULL<<14)
|
||||
#define SYS_FIRM (1ULL<<15)
|
||||
#define SYS_AGBSAVE (1ULL<<16)
|
||||
#define SYS_TICKDB (1ULL<<17)
|
||||
#define BIN_NCCHNFO (1ULL<<18)
|
||||
#define BIN_TIKDB (1ULL<<19)
|
||||
#define BIN_KEYDB (1ULL<<20)
|
||||
#define BIN_LEGKEY (1ULL<<21)
|
||||
#define TXT_SCRIPT (1ULL<<22)
|
||||
#define TXT_GENERIC (1ULL<<23)
|
||||
#define NOIMG_NAND (1ULL<<24)
|
||||
#define HDR_NAND (1ULL<<25)
|
||||
#define GAME_DSIWE (1ULL<<15)
|
||||
#define SYS_FIRM (1ULL<<16)
|
||||
#define SYS_AGBSAVE (1ULL<<17)
|
||||
#define SYS_TICKDB (1ULL<<18)
|
||||
#define BIN_NCCHNFO (1ULL<<19)
|
||||
#define BIN_TIKDB (1ULL<<20)
|
||||
#define BIN_KEYDB (1ULL<<21)
|
||||
#define BIN_LEGKEY (1ULL<<22)
|
||||
#define TXT_SCRIPT (1ULL<<23)
|
||||
#define TXT_GENERIC (1ULL<<24)
|
||||
#define NOIMG_NAND (1ULL<<25)
|
||||
#define HDR_NAND (1ULL<<26)
|
||||
#define TYPE_BASE 0xFFFFFFFFULL // 32 bit reserved for base types
|
||||
|
||||
// #define FLAG_FIRM (1ULL<<58) // <--- for CXIs containing FIRMs
|
||||
|
@ -1,7 +1,9 @@
|
||||
#include "sddata.h"
|
||||
#include "dsiwareexp.h"
|
||||
#include "aes.h"
|
||||
#include "sha.h"
|
||||
|
||||
#define DSIWARE_MAGIC "Nintendo DSiWare" // must be exactly 16 chars
|
||||
#define NUM_ALIAS_DRV 2
|
||||
#define NUM_FILCRYPTINFO 16
|
||||
|
||||
@ -52,28 +54,117 @@ FilCryptInfo* fx_find_cryptinfo(FIL* fptr) {
|
||||
return info;
|
||||
}
|
||||
|
||||
FRESULT fx_decrypt_dsiware (FIL* fp, void* buff, FSIZE_t ofs, UINT len) {
|
||||
const u32 mode = AES_CNT_TITLEKEY_DECRYPT_MODE;
|
||||
const u32 num_tbl = sizeof(DsiWareExpContentTable) / sizeof(u32);
|
||||
const FSIZE_t ofs0 = f_tell(fp);
|
||||
|
||||
u8 __attribute__((aligned(16))) iv[AES_BLOCK_SIZE];
|
||||
u32 tbl[num_tbl];
|
||||
u8 hdr[DSIWEXP_HEADER_LEN];
|
||||
|
||||
FRESULT res;
|
||||
UINT br;
|
||||
|
||||
|
||||
// read and decrypt header
|
||||
if ((res = f_lseek(fp, DSIWEXP_HEADER_OFFSET)) != FR_OK) return res;
|
||||
if ((res = f_read(fp, hdr, DSIWEXP_HEADER_LEN, &br)) != FR_OK) return res;
|
||||
if (br != DSIWEXP_HEADER_LEN) return FR_DENIED;
|
||||
memcpy(iv, hdr + DSIWEXP_HEADER_LEN - AES_BLOCK_SIZE, AES_BLOCK_SIZE);
|
||||
cbc_decrypt(hdr, hdr, sizeof(DsiWareExpHeader) / AES_BLOCK_SIZE, mode, iv);
|
||||
|
||||
// setup the table
|
||||
if (BuildDsiWareExportContentTable(tbl, hdr) != 0) return FR_DENIED;
|
||||
if (tbl[num_tbl-1] > f_size(fp)) return FR_DENIED; // obviously missing data
|
||||
|
||||
|
||||
// process sections
|
||||
u32 sct_start = 0;
|
||||
u32 sct_end = 0;
|
||||
for (u32 i = 0; i < num_tbl; i++, sct_start = sct_end) {
|
||||
sct_end = tbl[i];
|
||||
if (sct_start == sct_end) continue; // nothing in section
|
||||
if ((ofs + len <= sct_start) || (ofs >= sct_end)) continue; // section not in data
|
||||
|
||||
const u32 crypt_end = sct_end - (AES_BLOCK_SIZE * 2);
|
||||
const u32 data_end = min(crypt_end, ofs + len);
|
||||
u32 data_pos = max(ofs, sct_start);
|
||||
if (ofs >= crypt_end) continue; // nothing to do
|
||||
|
||||
if ((sct_start < ofs) || (sct_end > ofs + len)) { // incomplete section, ugh
|
||||
u8 __attribute__((aligned(16))) block[AES_BLOCK_SIZE];
|
||||
|
||||
// load iv0
|
||||
FSIZE_t block0_ofs = data_pos - (data_pos % AES_BLOCK_SIZE);
|
||||
FSIZE_t iv0_ofs = ((block0_ofs > sct_start) ? block0_ofs : sct_end) - AES_BLOCK_SIZE;
|
||||
if ((res = f_lseek(fp, iv0_ofs)) != FR_OK) return res;
|
||||
if ((res = f_read(fp, iv, AES_BLOCK_SIZE, &br)) != FR_OK) return res;
|
||||
|
||||
// load and decrypt block0 (if misaligned)
|
||||
if (data_pos % AES_BLOCK_SIZE) {
|
||||
if ((res = f_lseek(fp, block0_ofs)) != FR_OK) return res;
|
||||
if ((res = f_read(fp, block, AES_BLOCK_SIZE, &br)) != FR_OK) return res;
|
||||
cbc_decrypt(block, block, 1, mode, iv);
|
||||
data_pos = min(block0_ofs + AES_BLOCK_SIZE, data_end);
|
||||
memcpy(buff, block + (ofs - block0_ofs), data_pos - ofs);
|
||||
}
|
||||
|
||||
// decrypt blocks in between
|
||||
u32 num_blocks = (data_end - data_pos) / AES_BLOCK_SIZE;
|
||||
if (num_blocks) {
|
||||
u8* blocks = (u8*) buff + (data_pos - ofs);
|
||||
cbc_decrypt(blocks, blocks, num_blocks, mode, iv);
|
||||
data_pos += num_blocks * AES_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
// decrypt last block
|
||||
if (data_pos < data_end) {
|
||||
u8* lbuff = (u8*) buff + (data_pos - ofs);
|
||||
memcpy(block, lbuff, data_end - data_pos);
|
||||
cbc_decrypt(block, block, 1, mode, iv);
|
||||
memcpy(lbuff, block, data_end - data_pos);
|
||||
data_pos = data_end;
|
||||
}
|
||||
} else { // complete section (thank god for these!)
|
||||
u8* blocks = (u8*) buff + (sct_start - ofs);
|
||||
u8* iv0 = (u8*) buff + (sct_end - ofs) - AES_BLOCK_SIZE;
|
||||
u32 num_blocks = (crypt_end - sct_start) / AES_BLOCK_SIZE;
|
||||
memcpy(iv, iv0, AES_BLOCK_SIZE);
|
||||
cbc_decrypt(blocks, blocks, num_blocks, mode, iv);
|
||||
}
|
||||
}
|
||||
|
||||
return f_lseek(fp, ofs0);
|
||||
}
|
||||
|
||||
FRESULT fx_open (FIL* fp, const TCHAR* path, BYTE mode) {
|
||||
int num = alias_num(path);
|
||||
FilCryptInfo* info = fx_find_cryptinfo(fp);
|
||||
if (info) info->fptr = NULL;
|
||||
|
||||
if (info && (num >= 0)) {
|
||||
// get AES counter, see: http://www.3dbrew.org/wiki/Extdata#Encryption
|
||||
// path is the part of the full path after //Nintendo 3DS/<ID0>/<ID1>
|
||||
u8 hashstr[256];
|
||||
u8 sha256sum[32];
|
||||
u32 plen = 0;
|
||||
// poor man's UTF-8 -> UTF-16 / uppercase -> lowercase
|
||||
for (plen = 0; plen < 128; plen++) {
|
||||
u8 symbol = path[2 + plen];
|
||||
if ((symbol >= 'A') && (symbol <= 'Z')) symbol += ('a' - 'A');
|
||||
hashstr[2*plen] = symbol;
|
||||
hashstr[2*plen+1] = 0;
|
||||
if (symbol == 0) break;
|
||||
// DSIWare Export, mark with the magic number
|
||||
if (strncmp(path + 2, "/" DSIWARE_MAGIC, 1 + 16) == 0) {
|
||||
memcpy(info->ctr, DSIWARE_MAGIC, 16);
|
||||
} else {
|
||||
// get AES counter, see: http://www.3dbrew.org/wiki/Extdata#Encryption
|
||||
// path is the part of the full path after //Nintendo 3DS/<ID0>/<ID1>
|
||||
u8 hashstr[256];
|
||||
u8 sha256sum[32];
|
||||
u32 plen = 0;
|
||||
// poor man's UTF-8 -> UTF-16 / uppercase -> lowercase
|
||||
for (plen = 0; plen < 128; plen++) {
|
||||
u8 symbol = path[2 + plen];
|
||||
if ((symbol >= 'A') && (symbol <= 'Z')) symbol += ('a' - 'A');
|
||||
hashstr[2*plen] = symbol;
|
||||
hashstr[2*plen+1] = 0;
|
||||
if (symbol == 0) break;
|
||||
}
|
||||
sha_quick(sha256sum, hashstr, (plen + 1) * 2, SHA256_MODE);
|
||||
for (u32 i = 0; i < 16; i++)
|
||||
info->ctr[i] = sha256sum[i] ^ sha256sum[i+16];
|
||||
}
|
||||
sha_quick(sha256sum, hashstr, (plen + 1) * 2, SHA256_MODE);
|
||||
for (u32 i = 0; i < 16; i++)
|
||||
info->ctr[i] = sha256sum[i] ^ sha256sum[i+16];
|
||||
// copy over key, FIL pointer
|
||||
memcpy(info->keyy, sd_keyy[num], 16);
|
||||
info->fptr = fp;
|
||||
@ -89,7 +180,8 @@ FRESULT fx_read (FIL* fp, void* buff, UINT btr, UINT* br) {
|
||||
if (info && info->fptr) {
|
||||
setup_aeskeyY(0x34, info->keyy);
|
||||
use_aeskey(0x34);
|
||||
ctr_decrypt_byte(buff, buff, btr, off, AES_CNT_CTRNAND_MODE, info->ctr);
|
||||
if (memcmp(info->ctr, DSIWARE_MAGIC, 16) == 0) fx_decrypt_dsiware(fp, buff, off, btr);
|
||||
else ctr_decrypt_byte(buff, buff, btr, off, AES_CNT_CTRNAND_MODE, info->ctr);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@ -99,6 +191,7 @@ FRESULT fx_write (FIL* fp, const void* buff, UINT btw, UINT* bw) {
|
||||
FSIZE_t off = f_tell(fp);
|
||||
FRESULT res = FR_OK;
|
||||
if (info && info->fptr) {
|
||||
if (memcmp(info->ctr, DSIWARE_MAGIC, 16) == 0) return FR_DENIED;
|
||||
setup_aeskeyY(0x34, info->keyy);
|
||||
use_aeskey(0x34);
|
||||
*bw = 0;
|
||||
|
24
arm9/source/game/dsiwareexp.c
Normal file
24
arm9/source/game/dsiwareexp.c
Normal file
@ -0,0 +1,24 @@
|
||||
#include "dsiwareexp.h"
|
||||
|
||||
|
||||
u32 BuildDsiWareExportContentTable(void* table, void* header) {
|
||||
DsiWareExpHeader* hdr = (DsiWareExpHeader*) header;
|
||||
DsiWareExpContentTable* tbl = (DsiWareExpContentTable*) table;
|
||||
|
||||
if (strncmp(hdr->magic, DSIWEXP_HEADER_MAGIC, strlen(DSIWEXP_HEADER_MAGIC)) != 0)
|
||||
return 1;
|
||||
|
||||
tbl->banner_end = 0 + sizeof(DsiWareExpBanner) + sizeof(DsiWareExpBlockMetaData);
|
||||
tbl->header_end = tbl->banner_end + sizeof(DsiWareExpHeader) + sizeof(DsiWareExpBlockMetaData);
|
||||
tbl->footer_end = tbl->header_end + sizeof(DsiWareExpFooter) + sizeof(DsiWareExpBlockMetaData);
|
||||
|
||||
u32 content_end_last = tbl->footer_end;
|
||||
for (u32 i = 0; i < DSIWEXP_NUM_CONTENT; i++) {
|
||||
tbl->content_end[i] = content_end_last;
|
||||
if (!hdr->content_size[i]) continue; // non-existant section
|
||||
tbl->content_end[i] += align(hdr->content_size[i], 0x10) + sizeof(DsiWareExpBlockMetaData);
|
||||
content_end_last = tbl->content_end[i];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
59
arm9/source/game/dsiwareexp.h
Normal file
59
arm9/source/game/dsiwareexp.h
Normal file
@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include "nds.h"
|
||||
|
||||
#define DSIWEXP_NUM_CONTENT 11
|
||||
#define DSIWEXP_HEADER_MAGIC "3FDT"
|
||||
#define DSIWEXP_BANNER_OFFSET 0
|
||||
#define DSIWEXP_BANNER_LEN (sizeof(DsiWareExpBanner) + sizeof(DsiWareExpBlockMetaData))
|
||||
#define DSIWEXP_HEADER_OFFSET (DSIWEXP_BANNER_OFFSET + DSIWEXP_BANNER_LEN)
|
||||
#define DSIWEXP_HEADER_LEN (sizeof(DsiWareExpHeader) + sizeof(DsiWareExpBlockMetaData))
|
||||
|
||||
|
||||
typedef struct {
|
||||
u32 banner_end;
|
||||
u32 header_end;
|
||||
u32 footer_end;
|
||||
u32 content_end[DSIWEXP_NUM_CONTENT];
|
||||
} __attribute__((packed)) DsiWareExpContentTable;
|
||||
|
||||
// see: https://www.3dbrew.org/wiki/DSiWare_Exports#Block_Metadata
|
||||
typedef struct {
|
||||
u8 cmac[16];
|
||||
u8 iv0[16];
|
||||
} __attribute__((packed)) DsiWareExpBlockMetaData;
|
||||
|
||||
// see: https://www.3dbrew.org/wiki/DSiWare_Exports#File_Structure_v2
|
||||
typedef struct {
|
||||
TwlIconData icon_data;
|
||||
u8 unknown[0x4000 - sizeof(TwlIconData)];
|
||||
} __attribute__((packed)) DsiWareExpBanner;
|
||||
|
||||
// see: https://www.3dbrew.org/wiki/DSiWare_Exports#Header_2
|
||||
typedef struct {
|
||||
char magic[4]; // "3FDT"
|
||||
u16 group_id;
|
||||
u16 title_version;
|
||||
u8 movable_enc_sha256[0x20];
|
||||
u8 cbc_test_block[0x10];
|
||||
u64 title_id;
|
||||
u64 unknown0;
|
||||
u32 content_size[DSIWEXP_NUM_CONTENT];
|
||||
u8 unknown1[0x30];
|
||||
u8 tmd_reserved[0x3E];
|
||||
u8 padding[0x0E];
|
||||
} __attribute__((packed)) DsiWareExpHeader;
|
||||
|
||||
// see: https://www.3dbrew.org/wiki/DSiWare_Exports#Footer
|
||||
typedef struct {
|
||||
u8 banner_sha256[0x20];
|
||||
u8 header_sha256[0x20];
|
||||
u8 content_sha256[DSIWEXP_NUM_CONTENT][0x20];
|
||||
u8 ecdsa_signature[0x3C];
|
||||
u8 ecdsa_apcert[0x180];
|
||||
u8 ecdsa_ctcert[0x180];
|
||||
u8 padding[0x4];
|
||||
} __attribute__((packed)) DsiWareExpFooter;
|
||||
|
||||
u32 BuildDsiWareExportContentTable(void* table, void* header);
|
@ -11,5 +11,6 @@
|
||||
#include "codelzss.h"
|
||||
#include "nds.h"
|
||||
#include "gba.h"
|
||||
#include "dsiwareexp.h"
|
||||
#include "3dsx.h"
|
||||
#include "ncchinfo.h"
|
||||
|
Loading…
x
Reference in New Issue
Block a user