Enable build CIA from TMD files

This commit is contained in:
d0k3 2016-12-15 11:46:00 +01:00
parent 1f98f88d87
commit 2a904dc284
10 changed files with 500 additions and 41 deletions

View File

@ -50,6 +50,7 @@
// buffer area defines (temporary, in use by various functions)
// -> godmode.c hexviewer
// -> ncch.c seed setup
// -> cia.c ticket / titlekey setup
// -> gameutil.c various temporary stuff
#define TEMP_BUFFER ((u8*)0x21100000)
#define TEMP_BUFFER_SIZE (0x100000)

View File

@ -171,6 +171,27 @@ void cbc_decrypt(void *inbuf, void *outbuf, size_t size, uint32_t mode, uint8_t
}
}
void cbc_encrypt(void *inbuf, void *outbuf, size_t size, uint32_t mode, uint8_t *ctr)
{
size_t blocks_left = size;
size_t blocks;
uint8_t *in = inbuf;
uint8_t *out = outbuf;
uint32_t i;
while (blocks_left)
{
set_ctr(ctr);
blocks = (blocks_left >= 0xFFFF) ? 0xFFFF : blocks_left;
aes_decrypt(in, out, blocks, mode);
for (i=0; i<AES_BLOCK_SIZE; i++)
ctr[i] = in[((blocks - 1) * AES_BLOCK_SIZE) + i];
in += blocks * AES_BLOCK_SIZE;
out += blocks * AES_BLOCK_SIZE;
blocks_left -= blocks;
}
}
void ctr_decrypt_byte(void *inbuf, void *outbuf, size_t size, size_t off, uint32_t mode, uint8_t *ctr)
{
size_t bytes_left = size;

View File

@ -58,6 +58,7 @@ void ctr_decrypt(void* inbuf, void* outbuf, size_t size, uint32_t mode, uint8_t
void ctr_decrypt_byte(void *inbuf, void *outbuf, size_t size, size_t off, uint32_t mode, uint8_t *ctr);
void ecb_decrypt(void *inbuf, void *outbuf, size_t size, uint32_t mode);
void cbc_decrypt(void *inbuf, void *outbuf, size_t size, uint32_t mode, uint8_t *ctr);
void cbc_encrypt(void *inbuf, void *outbuf, size_t size, uint32_t mode, uint8_t *ctr);
void aes_cmac(void* inbuf, void* outbuf, size_t size);
void aes_fifos(void* inbuf, void* outbuf, size_t blocks);
void set_aeswrfifo(uint32_t value);

View File

@ -15,5 +15,7 @@
#define FTYPE_MOUNTABLE (IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS)
#define FYTPE_VERIFICABLE (GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD)
#define FYTPE_DECRYPTABLE (GAME_CIA|GAME_NCSD|GAME_NCCH)
#define FTYPE_BUILDABLE (GAME_TMD)
#define FTYPE_BUILDABLE_L (FTYPE_BUILDABLE&(GAME_TMD))
u32 IdentifyFileType(const char* path);

View File

@ -1,8 +1,27 @@
#include "cia.h"
#include "ncch.h"
#include "exefs.h"
#include "ff.h"
#include "sddata.h"
#include "aes.h"
#include "sha.h"
#define TIKDB_NAME_ENC "encTitleKeys.bin"
#define TIKDB_NAME_DEC "decTitleKeys.bin"
typedef struct {
u32 commonkey_idx;
u8 reserved[4];
u8 title_id[8];
u8 titlekey[16];
} __attribute__((packed)) TitleKeyEntry;
typedef struct {
u32 n_entries;
u8 reserved[12];
TitleKeyEntry entries[256]; // this number is only a placeholder
} __attribute__((packed)) TitleKeysInfo;
u32 ValidateCiaHeader(CiaHeader* header) {
if ((header->size_header != CIA_HEADER_SIZE) ||
(header->size_cert != CIA_CERT_SIZE) ||
@ -50,9 +69,9 @@ u32 GetCiaContentInfo(CiaContentInfo* contents, TitleMetaData* tmd) {
return 0;
}
u32 GetTitleKey(u8* titlekey, Ticket* ticket) {
u32 CryptTitleKey(TitleKeyEntry* tik, bool encrypt) {
// From https://github.com/profi200/Project_CTR/blob/master/makerom/pki/prod.h#L19
static const u8 common_keyy[6][16] = {
static const u8 common_keyy[6][16] __attribute__((aligned(16))) = {
{0xD0, 0x7B, 0x33, 0x7F, 0x9C, 0xA4, 0x38, 0x59, 0x32, 0xA2, 0xE2, 0x57, 0x23, 0x23, 0x2E, 0xB9} , // 0 - eShop Titles
{0x0C, 0x76, 0x72, 0x30, 0xF0, 0x99, 0x8F, 0x1C, 0x46, 0x82, 0x82, 0x02, 0xFA, 0xAC, 0xBE, 0x4C} , // 1 - System Titles
{0xC4, 0x75, 0xCB, 0x3A, 0xB8, 0xC7, 0x88, 0xBB, 0x57, 0x5E, 0x12, 0xA1, 0x09, 0x07, 0xB8, 0xA4} , // 2
@ -61,7 +80,7 @@ u32 GetTitleKey(u8* titlekey, Ticket* ticket) {
{0x5E, 0x66, 0x99, 0x8A, 0xB4, 0xE8, 0x93, 0x16, 0x06, 0x85, 0x0F, 0xD7, 0xA1, 0x6D, 0xD7, 0x55} , // 5
};
// From https://github.com/profi200/Project_CTR/blob/master/makerom/pki/dev.h#L21
/* static const u8 common_key_devkit[6][16] = { // unused atm!
/* static const u8 common_key_devkit[6][16] __attribute__((aligned(16))) = { // unused atm!
{0x55, 0xA3, 0xF8, 0x72, 0xBD, 0xC8, 0x0C, 0x55, 0x5A, 0x65, 0x43, 0x81, 0x13, 0x9E, 0x15, 0x3B} , // 0 - eShop Titles
{0x44, 0x34, 0xED, 0x14, 0x82, 0x0C, 0xA1, 0xEB, 0xAB, 0x82, 0xC1, 0x6E, 0x7B, 0xEF, 0x0C, 0x25} , // 1 - System Titles
{0xF6, 0x2E, 0x3F, 0x95, 0x8E, 0x28, 0xA2, 0x1F, 0x28, 0x9E, 0xEC, 0x71, 0xA8, 0x66, 0x29, 0xDC} , // 2
@ -70,18 +89,29 @@ u32 GetTitleKey(u8* titlekey, Ticket* ticket) {
{0xAA, 0xDA, 0x4C, 0xA8, 0xF6, 0xE5, 0xA9, 0x77, 0xE0, 0xA0, 0xF9, 0xE4, 0x76, 0xCF, 0x0D, 0x63} , // 5
};*/
// setup key 0x3D
if (ticket->commonkey_idx >= 6) return 1;
setup_aeskeyY(0x3D, (void*) common_keyy[ticket->commonkey_idx >= 6]);
use_aeskey(0x3D);
// grab and decrypt the titlekey
u32 mode = (encrypt) ? AES_CNT_TITLEKEY_ENCRYPT_MODE : AES_CNT_TITLEKEY_DECRYPT_MODE;
u8 ctr[16] = { 0 };
memcpy(ctr, ticket->title_id, 8);
memcpy(titlekey, ticket->titlekey, 16);
set_ctr(ctr);
aes_decrypt(titlekey, titlekey, 1, AES_CNT_TITLEKEY_DECRYPT_MODE);
// setup key 0x3D // ctr
if (tik->commonkey_idx >= 6) return 1;
setup_aeskeyY(0x3D, (void*) common_keyy[tik->commonkey_idx]);
use_aeskey(0x3D);
memcpy(ctr, tik->title_id, 8);
set_ctr(ctr);
// decrypt / encrypt the titlekey
aes_decrypt(tik->titlekey, tik->titlekey, 1, mode);
return 0;
}
u32 GetTitleKey(u8* titlekey, Ticket* ticket) {
TitleKeyEntry tik = { 0 };
memcpy(tik.title_id, ticket->title_id, 8);
memcpy(tik.titlekey, ticket->titlekey, 16);
tik.commonkey_idx = ticket->commonkey_idx;
if (CryptTitleKey(&tik, false) != 0) return 0;
memcpy(titlekey, tik.titlekey, 16);
return 0;
}
@ -105,6 +135,35 @@ u32 FixTmdHashes(TitleMetaData* tmd) {
return 0;
}
u32 FixCiaHeaderForTmd(CiaHeader* header, TitleMetaData* tmd) {
TmdContentChunk* content_list = (TmdContentChunk*) (tmd + 1);
u32 content_count = getbe16(tmd->content_count);
header->size_content = 0;
header->size_tmd = CIA_TMD_SIZE_N(content_count);
memset(header->content_index, 0, sizeof(header->content_index));
for (u32 i = 0; i < content_count; i++) {
u16 index = getbe16(content_list[i].index);
header->size_content += getbe64(content_list[i].size);
header->content_index[index/8] |= (1 << (7-(index%8)));
}
return 0;
}
Ticket* ParseForTicket(u8* data, u32 size, u8* title_id) {
const u8 magic[] = { CIA_SIG_TYPE };
for (u32 i = 0; i + sizeof(Ticket) <= size; i++) {
Ticket* ticket = (Ticket*) (data + i);
if ((memcmp(ticket->sig_type, magic, sizeof(magic)) != 0) ||
((strncmp((char*) ticket->issuer, CIA_TICKET_ISSUER, 0x40) != 0) &&
(strncmp((char*) ticket->issuer, CIA_TICKET_ISSUER_DEV, 0x40) != 0)))
continue; // magics not found
if (title_id && (memcmp(title_id, ticket->title_id, 8) != 0))
continue; // title id not matching
return ticket;
}
return NULL;
}
u32 BuildCiaCert(u8* ciacert) {
const u8 cert_hash_expected[0x20] = {
0xC7, 0x2E, 0x1C, 0xA5, 0x61, 0xDC, 0x9B, 0xC8, 0x05, 0x58, 0x58, 0x9C, 0x63, 0x08, 0x1C, 0x8A,
@ -137,7 +196,7 @@ u32 BuildCiaCert(u8* ciacert) {
}
u32 BuildFakeTicket(Ticket* ticket, u8* title_id) {
const u8 sig_type[4] = { 0x00, 0x01, 0x00, 0x04 }; // RSA_2048 SHA256
const u8 sig_type[4] = { CIA_SIG_TYPE }; // RSA_2048 SHA256
const u8 ticket_cnt_index[] = { // whatever this is
0x00, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x14,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x84,
@ -155,19 +214,51 @@ u32 BuildFakeTicket(Ticket* ticket, u8* title_id) {
ticket->version = 0x01;
memset(ticket->titlekey, 0xFF, 16);
memcpy(ticket->title_id, title_id, 8);
ticket->commonkey_idx = 0x01;
ticket->commonkey_idx = 0x00; // eshop
ticket->audit = 0x01; // whatever
memcpy(ticket->content_index, ticket_cnt_index, sizeof(ticket_cnt_index));
// search for a titlekey inside encTitleKeys.bin / decTitleKeys.bin
for (u32 enc = 0; enc <= 1; enc++) {
const char* base[] = { INPUT_PATHS };
bool found = false;
for (u32 i = 0; (i < (sizeof(base)/sizeof(char*))) && !found; i++) {
TitleKeysInfo* tikdb = (TitleKeysInfo*) (TEMP_BUFFER + (TEMP_BUFFER_SIZE/2));
char path[64];
FIL file;
UINT btr;
snprintf(path, 64, "%s/%s", base[i], (enc) ? TIKDB_NAME_ENC : TIKDB_NAME_DEC);
if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
continue;
f_read(&file, tikdb, TEMP_BUFFER_SIZE / 2, &btr);
f_close(&file);
if (tikdb->n_entries > (btr - 16) / 32)
continue; // filesize / titlekey db size mismatch
for (u32 t = 0; t < tikdb->n_entries; t++) {
TitleKeyEntry* tik = tikdb->entries + t;
if (memcmp(title_id, tik->title_id, 8) != 0)
continue;
if (!enc && (CryptTitleKey(tik, true) != 0)) // encrypt the key first
continue;
memcpy(ticket->titlekey, tik->titlekey, 16);
ticket->commonkey_idx = tik->commonkey_idx;
found = true; // found, inserted
break;
}
}
if (found) break;
}
return 0;
}
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents) {
const u8 sig_type[4] = { 0x00, 0x01, 0x00, 0x04 }; // RSA_2048 SHA256
const u8 sig_type[4] = { CIA_SIG_TYPE };
// safety check: number of contents
if (n_contents > 64) return 1; // !!!
if (n_contents > CIA_MAX_CONTENTS) return 1; // !!!
// set TMD all zero for a clean start
memset(tmd, 0x00, sizeof(TitleMetaData) + (n_contents * sizeof(TmdContentChunk)));
memset(tmd, 0x00, CIA_TMD_SIZE_N(n_contents));
// file TMD values
memcpy(tmd->sig_type, sig_type, 4);
memset(tmd->signature, 0xFF, 0x100);
@ -185,6 +276,29 @@ u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents) {
return 0;
}
u32 BuildCiaMeta(CiaMeta* meta, u8* exthdr, u8* smdh) {
// init metadata with all zeroes and core version
memset(meta, 0x00, sizeof(CiaMeta));
meta->core_version = 2;
// copy dependencies from extheader
if (exthdr) memcpy(meta->dependencies, exthdr + 0x40, sizeof(meta->dependencies));
// copy smdh (icon file in exefs)
if (smdh) memcpy(meta->smdh, smdh, sizeof(meta->smdh));
return 0;
}
u32 BuildCiaHeader(CiaHeader* header) {
memset(header, 0, sizeof(CiaHeader));
// sizes in header - fill only known sizes, others zero
header->size_header = sizeof(CiaHeader);
header->size_cert = CIA_CERT_SIZE;
header->size_ticket = sizeof(Ticket);
header->size_tmd = 0;
header->size_meta = 0;
header->size_content = 0;
return 0;
}
u32 DecryptCiaContentSequential(u8* data, u32 size, u8* ctr, const u8* titlekey) {
// WARNING: size and offset of data have to be a multiple of 16
u8 tik[16] __attribute__((aligned(32)));
@ -195,3 +309,14 @@ u32 DecryptCiaContentSequential(u8* data, u32 size, u8* ctr, const u8* titlekey)
cbc_decrypt(data, data, size / 16, mode, ctr);
return 0;
}
u32 EncryptCiaContentSequential(u8* data, u32 size, u8* ctr, const u8* titlekey) {
// WARNING: size and offset of data have to be a multiple of 16
u8 tik[16] __attribute__((aligned(32)));
u32 mode = AES_CNT_TITLEKEY_ENCRYPT_MODE;
memcpy(tik, titlekey, 16);
setup_aeskey(0x11, tik);
use_aeskey(0x11);
cbc_encrypt(data, data, size / 16, mode, ctr);
return 0;
}

View File

@ -2,8 +2,10 @@
#include "common.h"
#define CIA_TICKET_ISSUER "Root-CA00000003-XS0000000c"
#define CIA_TMD_ISSUER "Root-CA00000003-CP0000000b"
#define CIA_TICKET_ISSUER "Root-CA00000003-XS0000000c"
#define CIA_TICKET_ISSUER_DEV "Root-CA00000004-XS00000009"
#define CIA_TMD_ISSUER "Root-CA00000003-CP0000000b"
#define CIA_SIG_TYPE 0x00, 0x01, 0x00, 0x04 // RSA_2048 SHA256
#define CIA_MAX_CONTENTS (100+1) // theme CIAs contain maximum 100 themes + 1 index content
#define CIA_HEADER_SIZE sizeof(CiaHeader)
@ -153,12 +155,14 @@ u32 GetCiaContentInfo(CiaContentInfo* contents, TitleMetaData* tmd);
u32 GetTitleKey(u8* titlekey, Ticket* ticket);
u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk);
u32 FixTmdHashes(TitleMetaData* tmd);
u32 FixCiaHeaderForTmd(CiaHeader* header, TitleMetaData* tmd);
Ticket* ParseForTicket(u8* data, u32 size, u8* title_id);
u32 BuildCiaCert(u8* ciacert);
u32 BuildFakeTicket(Ticket* ticket, u8* title_id);
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents);
/*u32 BuildCiaMeta(Ncch ncch);
u32 InsertCiaContent(const char* path, const char* content, bool encrypt);*/
u32 BuildCiaMeta(CiaMeta* meta, u8* exthdr, u8* smdh);
u32 BuildCiaHeader(CiaHeader* header);
u32 DecryptCiaContentSequential(u8* data, u32 size, u8* ctr, const u8* titlekey);
u32 EncryptCiaContentSequential(u8* data, u32 size, u8* ctr, const u8* titlekey);

View File

@ -35,7 +35,7 @@ u32 GetOutputPath(char* dest, const char* path, const char* ext) {
return 0;
}
u32 GetNcchFileHeaders(NcchHeader* ncch, ExeFsHeader* exefs, FIL* file) {
u32 GetNcchHeaders(NcchHeader* ncch, ExeFsHeader* exefs, FIL* file) {
u32 offset_ncch = f_tell(file);
UINT btr;
@ -55,7 +55,7 @@ u32 GetNcchFileHeaders(NcchHeader* ncch, ExeFsHeader* exefs, FIL* file) {
return 0;
}
u32 CheckNcchFileHash(u8* expected, FIL* file, u32 size_data, u32 offset_ncch, NcchHeader* ncch, ExeFsHeader* exefs) {
u32 CheckNcchHash(u8* expected, FIL* file, u32 size_data, u32 offset_ncch, NcchHeader* ncch, ExeFsHeader* exefs) {
u32 offset_data = f_tell(file) - offset_ncch;
u8 hash[32];
@ -118,7 +118,69 @@ u32 LoadCiaStub(CiaStub* stub, const char* path) {
return 0;
}
u32 LoadNcchMeta(CiaMeta* meta, const char* path, u64 offset) {
NcchHeader ncch;
ExeFsHeader exefs;
FIL file;
UINT btr;
u32 ret = 0;
// this uses the meta builder function only in part
if (BuildCiaMeta(meta, NULL, NULL) != 0) return 1;
// open file, get NCCH, ExeFS header
if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
return 1;
f_lseek(&file, offset);
if (GetNcchHeaders(&ncch, &exefs, &file) != 0) {
fx_close(&file);
return 1;
}
// dependencies
if (ncch.size_exthdr > 0) {
u8* dep = meta->dependencies;
u32 size_dep = sizeof(meta->dependencies);
f_lseek(&file, offset + NCCH_EXTHDR_OFFSET + 0x40); // offset to dependencies
if ((fx_read(&file, dep, size_dep, &btr) != FR_OK) ||
(DecryptNcch(dep, NCCH_EXTHDR_OFFSET + 0x40, size_dep, &ncch, NULL) != 0) ||
(btr != size_dep)) {
ret = 1;
}
} else ret = 1;
// smdh from exefs
if (ncch.size_exefs > 0) {
ExeFsFileHeader* icon = NULL;
u32 size_smdh = sizeof(meta->smdh);
for (u32 i = 0; i < 10; i++) {
u32 size = exefs.files[i].size;
if (!size || (size > size_smdh)) continue;
char* name = exefs.files[i].name;
if (strncmp(name, "icon", 8) == 0) {
icon = exefs.files + i;
break;
}
}
if (icon) {
u32 size_icon = icon->size;
u32 offset_icon = (ncch.offset_exefs * NCCH_MEDIA_UNIT) + sizeof(ExeFsHeader) + icon->offset;
u8* smdh = meta->smdh;
f_lseek(&file, offset + offset_icon); // offset to icon
if ((fx_read(&file, smdh, size_icon, &btr) != FR_OK) ||
(DecryptNcch(smdh, offset_icon, size_icon, &ncch, &exefs) != 0) ||
(btr != size_icon)) {
ret = 1;
}
} else ret = 1;
} else ret = 1;
fx_close(&file);
return ret;
}
u32 LoadTmdFile(TitleMetaData* tmd, const char* path) {
const u8 magic[] = { CIA_SIG_TYPE };
FIL file;
UINT btr;
@ -128,6 +190,7 @@ u32 LoadTmdFile(TitleMetaData* tmd, const char* path) {
// full TMD file
f_lseek(&file, 0);
if ((fx_read(&file, tmd, CIA_TMD_SIZE_MAX, &btr) != FR_OK) ||
(memcmp(tmd->sig_type, magic, sizeof(magic)) != 0) ||
(btr < CIA_TMD_SIZE_N(getbe16(tmd->content_count)))) {
fx_close(&file);
return 1;
@ -157,6 +220,31 @@ u32 WriteCiaStub(CiaStub* stub, const char* path) {
return 0;
}
u32 SearchTicket(Ticket* ticket, const char* path, u8* title_id, bool force_legit) {
FIL db;
UINT btr;
if (f_open(&db, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
return 1;
f_lseek(&db, 0);
u32 fsize = f_size(&db);
u32 offset = 0;
Ticket* tick = NULL;
ShowProgress(0, 0, path);
while (!tick && (f_read(&db, MAIN_BUFFER, MAIN_BUFFER_SIZE, &btr) == FR_OK) && (btr > 0)) {
offset += (btr == MAIN_BUFFER_SIZE) ? MAIN_BUFFER_SIZE - (sizeof(Ticket) - 1) : btr;
if (!ShowProgress(offset, fsize, path)) break;
f_lseek(&db, offset);
tick = ParseForTicket(MAIN_BUFFER, btr, title_id);
if (tick && force_legit && (getbe64(tick->ticket_id) == 0))
tick = NULL;
}
if (tick) memcpy(ticket, tick, sizeof(Ticket));
return (tick) ? 0 : 1;
}
u32 VerifyTmdContent(const char* path, u64 offset, TmdContentChunk* chunk, const u8* titlekey) {
u8 hash[32];
u8 ctr[16];
@ -204,7 +292,7 @@ u32 VerifyNcchFile(const char* path, u32 offset, u32 size) {
return 1;
f_lseek(&file, offset);
if (GetNcchFileHeaders(&ncch, &exefs, &file) != 0) {
if (GetNcchHeaders(&ncch, &exefs, &file) != 0) {
if (!offset) ShowPrompt(false, "%s\nError: Not a NCCH file", pathstr);
fx_close(&file);
return 1;
@ -232,19 +320,19 @@ u32 VerifyNcchFile(const char* path, u32 offset, u32 size) {
// base hash check for extheader
if (ncch.size_exthdr > 0) {
f_lseek(&file, offset + NCCH_EXTHDR_OFFSET);
ver_exthdr = CheckNcchFileHash(ncch.hash_exthdr, &file, 0x400, offset, &ncch, &exefs);
ver_exthdr = CheckNcchHash(ncch.hash_exthdr, &file, 0x400, offset, &ncch, NULL);
}
// base hash check for exefs
if (ncch.size_exefs > 0) {
f_lseek(&file, offset + (ncch.offset_exefs * NCCH_MEDIA_UNIT));
ver_exefs = CheckNcchFileHash(ncch.hash_exefs, &file, ncch.size_exefs_hash * NCCH_MEDIA_UNIT, offset, &ncch, &exefs);
ver_exefs = CheckNcchHash(ncch.hash_exefs, &file, ncch.size_exefs_hash * NCCH_MEDIA_UNIT, offset, &ncch, &exefs);
}
// base hash check for romfs
if (ncch.size_romfs > 0) {
f_lseek(&file, offset + (ncch.offset_romfs * NCCH_MEDIA_UNIT));
ver_romfs = CheckNcchFileHash(ncch.hash_romfs, &file, ncch.size_romfs_hash * NCCH_MEDIA_UNIT, offset, &ncch, &exefs);
ver_romfs = CheckNcchHash(ncch.hash_romfs, &file, ncch.size_romfs_hash * NCCH_MEDIA_UNIT, offset, &ncch, NULL);
}
// thorough exefs verification
@ -254,7 +342,7 @@ u32 VerifyNcchFile(const char* path, u32 offset, u32 size) {
u8* hash = exefs.hashes[9 - i];
if (!exefile->size) continue;
f_lseek(&file, offset + (ncch.offset_exefs * NCCH_MEDIA_UNIT) + 0x200 + exefile->offset);
ver_exefs = CheckNcchFileHash(hash, &file, exefile->size, offset, &ncch, &exefs);
ver_exefs = CheckNcchHash(hash, &file, exefile->size, offset, &ncch, &exefs);
}
}
@ -434,7 +522,7 @@ u32 CheckEncryptedCiaFile(const char* path) {
u64 next_offset = info.offset_content;
for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) {
TmdContentChunk* chunk = &(cia->content_list[i]);
if ((getbe16(chunk->type) & 0x1) || (CheckEncryptedNcchFile(path, next_offset)))
if ((getbe16(chunk->type) & 0x1) || (CheckEncryptedNcchFile(path, next_offset) == 0))
return 0; // encryption found
next_offset += getbe64(chunk->size);
}
@ -509,9 +597,8 @@ u32 DecryptNcchNcsdFile(const char* orig, const char* dest, u32 mode,
if (cia_crypto) DecryptCiaContentSequential(MAIN_BUFFER, sizeof(NcchHeader), ctr, titlekey);
ncch_crypto = ((ValidateNcchHeader((NcchHeader*) (void*) MAIN_BUFFER) == 0) &&
NCCH_ENCRYPTED((NcchHeader*) (void*) MAIN_BUFFER));
if (ncch_crypto && (SetupNcchCrypto((NcchHeader*) (void*) MAIN_BUFFER) != 0)) {
ShowPrompt(false, "Error: Cannot set up NCCH crypto");
};
if (ncch_crypto && (SetupNcchCrypto((NcchHeader*) (void*) MAIN_BUFFER) != 0))
ret = 1;
GetTmdCtr(ctr, chunk);
f_lseek(ofp, offset);
@ -605,3 +692,179 @@ u32 DecryptGameFile(const char* path, bool inplace) {
return ret;
}
u32 InsertCiaContent(const char* path_cia, const char* path_content, u32 offset, u32 size,
TmdContentChunk* chunk, const u8* titlekey, bool force_legit) {
// crypto types
bool ncch_crypto = (!force_legit && (CheckEncryptedNcchFile(path_content, offset) == 0));
bool cia_crypto = (force_legit && (getbe16(chunk->type) & 0x01));
if (!cia_crypto) chunk->type[1] &= ~0x01; // remove crypto flag
// open file(s)
FIL ofile;
FIL dfile;
FSIZE_t fsize;
UINT bytes_read, bytes_written;
if (fx_open(&ofile, path_content, FA_READ | FA_OPEN_EXISTING) != FR_OK)
return 1;
f_lseek(&ofile, offset);
fsize = f_size(&ofile); // for progress bar
if (fx_open(&dfile, path_cia, FA_WRITE | FA_OPEN_APPEND) != FR_OK) {
fx_close(&ofile);
return 1;
}
// check if NCCH crypto is available
if (ncch_crypto) {
NcchHeader ncch;
if ((fx_read(&ofile, &ncch, sizeof(NcchHeader), &bytes_read) != FR_OK) ||
(ValidateNcchHeader(&ncch) != 0) ||
(SetupNcchCrypto(&ncch) != 0))
ncch_crypto = false;
f_lseek(&ofile, offset);
}
// main loop starts here
u8 ctr[16];
u32 ret = 0;
GetTmdCtr(ctr, chunk);
sha_init(SHA256_MODE);
if (!ShowProgress(0, 0, path_content)) ret = 1;
for (u32 i = 0; (i < size) && (ret == 0); i += MAIN_BUFFER_SIZE) {
u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i));
if (fx_read(&ofile, MAIN_BUFFER, read_bytes, &bytes_read) != FR_OK) ret = 1;
if (ncch_crypto && (DecryptNcchSequential(MAIN_BUFFER, i, read_bytes) != 0)) ret = 1;
sha_update(MAIN_BUFFER, read_bytes);
if (cia_crypto && (EncryptCiaContentSequential(MAIN_BUFFER, read_bytes, ctr, titlekey) != 0)) ret = 1;
if (fx_write(&dfile, MAIN_BUFFER, read_bytes, &bytes_written) != FR_OK) ret = 1;
if ((read_bytes != bytes_read) || (bytes_read != bytes_written)) ret = 1;
if (!ShowProgress(offset + i + read_bytes, fsize, path_content)) ret = 1;
}
u8 hash[0x20];
sha_get(hash);
fx_close(&ofile);
fx_close(&dfile);
// force legit?
if (force_legit && (memcmp(hash, chunk->hash, 0x20) != 0)) return 1;
if (force_legit && (getbe64(chunk->size) != size)) return 1;
// chunk size / chunk hash
for (u32 i = 0; i < 8; i++) chunk->size[i] = (u8) (size >> (8*(7-i)));
memcpy(chunk->hash, hash, 0x20);
return ret;
}
u32 InsertCiaMeta(const char* path_cia, CiaMeta* meta) {
FIL file;
UINT btw;
if (fx_open(&file, path_cia, FA_WRITE | FA_OPEN_APPEND) != FR_OK)
return 1;
bool res = ((fx_write(&file, meta, CIA_META_SIZE, &btw) == FR_OK) && (btw == CIA_META_SIZE));
fx_close(&file);
return (res) ? 0 : 1;
}
u32 BuildCiaFromTmdFile(const char* path_tmd, const char* path_cia, bool force_legit) {
CiaStub* cia = (CiaStub*) TEMP_BUFFER;
CiaMeta* meta = (CiaMeta*) (TEMP_BUFFER + sizeof(CiaStub));
// build the CIA stub
memset(cia, 0, sizeof(CiaStub));
if ((BuildCiaHeader(&(cia->header)) != 0) ||
(LoadTmdFile(&(cia->tmd), path_tmd) != 0) ||
(FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) ||
(BuildCiaCert(cia->cert) != 0) ||
(BuildFakeTicket(&(cia->ticket), cia->tmd.title_id) != 0) ||
(WriteCiaStub(cia, path_cia) != 0)) {
return 1;
}
// extract info from TMD
TitleMetaData* tmd = &(cia->tmd);
TmdContentChunk* content_list = cia->content_list;
u32 content_count = getbe16(tmd->content_count);
u8* title_id = tmd->title_id;
if (!content_count) return 1;
// get (legit) ticket
const char* path_db = ((*path_tmd == 'B') || (*path_tmd == '4')) ?
"4:/dbs/ticket.db" : "1:/dbs/ticket.db"; // EmuNAND / SysNAND
const u8 titlekey_placeholder[16] = { 0xFF };
Ticket* ticket = &(cia->ticket);
if (force_legit && (SearchTicket(ticket, path_db, title_id, true) != 0)) {
ShowPrompt(false, "ID %016llX\nLegit ticket not found.", getbe64(title_id));
return 1;
} else if ((memcmp(ticket->titlekey, titlekey_placeholder, 16) == 0) &&
(SearchTicket(ticket, path_db, title_id, false) == 0)) {
// ticket placeholder found, try to find ticket
// if ticket found: wipe private data
if (getbe32(ticket->console_id) || getbe32(ticket->eshop_id)) {
memset(ticket->console_id, 0, 4); // zero out console id
memset(ticket->eshop_id, 0, 4); // zero out eshop id
memset(ticket->ticket_id, 0, 8); // zero out ticket id
}
}
// content path string
char path_content[256];
char* name_content;
strncpy(path_content, path_tmd, 256);
name_content = strrchr(path_content, '/');
if (!name_content) return 1; // will not happen
name_content++;
// try to build metadata
if (content_count) {
snprintf(name_content, 256 - (name_content - path_content), "%08lx.app", getbe32(content_list->id));
if (LoadNcchMeta(meta, path_content, 0) != 0) meta = NULL;
} else meta = NULL;
// insert contents
u8 titlekey[16] = { 0xFF };
if ((GetTitleKey(titlekey, &(cia->ticket)) != 0) && force_legit) return 1;
for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) {
TmdContentChunk* chunk = &(content_list[i]);
snprintf(name_content, 256 - (name_content - path_content), "%08lx.app", getbe32(chunk->id));
if (InsertCiaContent(path_cia, path_content, 0, (u32) getbe64(chunk->size), chunk, titlekey, force_legit) != 0) {
ShowPrompt(false, "ID %016llX.%08lX\nInsert content failed", getbe64(title_id), getbe32(chunk->id));
return 1;
}
}
// try to insert meta, but ignore result
if (meta && (InsertCiaMeta(path_cia, meta) == 0))
cia->header.size_meta = CIA_META_SIZE;
// write the CIA stub (take #2)
if ((FixTmdHashes(tmd) != 0) || (WriteCiaStub(cia, path_cia) != 0))
return 1;
return 0;
}
u32 BuildCiaFromGameFile(const char* path, bool force_legit) {
u32 filetype = IdentifyFileType(path);
char dest[256];
u32 ret = 0;
// destination path
if (GetOutputPath(dest, path, "cia") != 0) return 1;
if (!CheckWritePermissions(dest)) return 1;
// ensure the output dir exists
if ((f_stat(OUTPUT_PATH, NULL) != FR_OK) && (f_mkdir(OUTPUT_PATH) != FR_OK))
return 1;
// build CIA from game file
if (filetype & GAME_TMD)
ret = BuildCiaFromTmdFile(path, dest, force_legit);
else ret = 1;
// if (ret != 0) // try to get rid of the borked file
// f_unlink(dest);
return ret;
}

View File

@ -5,3 +5,4 @@
u32 VerifyGameFile(const char* path);
u32 CheckEncryptedGameFile(const char* path);
u32 DecryptGameFile(const char* path, bool inplace);
u32 BuildCiaFromGameFile(const char* path, bool force_legit);

View File

@ -97,7 +97,7 @@ u32 GetNcchSeed(u8* seed, NcchHeader* ncch) {
nand_drv[i], sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3]);
// check seedsave for seed
u8* seedsave = (u8*) TEMP_BUFFER;
u8* seedsave = (u8*) (TEMP_BUFFER + (TEMP_BUFFER_SIZE/2));
if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
continue;
f_read(&file, seedsave, 0x200, &btr);
@ -124,11 +124,11 @@ u32 GetNcchSeed(u8* seed, NcchHeader* ncch) {
// not found -> try seeddb.bin
const char* base[] = { INPUT_PATHS };
for (u32 i = 0; i < (sizeof(base)/sizeof(char*)); i++) {
SeedInfo* seeddb = (SeedInfo*) TEMP_BUFFER;
SeedInfo* seeddb = (SeedInfo*) (TEMP_BUFFER + (TEMP_BUFFER_SIZE/2));
snprintf(path, 128, "%s/%s", base[i], SEEDDB_NAME);
if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
continue;
f_read(&file, seeddb, TEMP_BUFFER_SIZE, &btr);
f_read(&file, seeddb, TEMP_BUFFER_SIZE / 2, &btr);
f_close(&file);
if (seeddb->n_entries > (btr - 16) / 32)
continue; // filesize / seeddb size mismatch
@ -288,20 +288,25 @@ u32 DecryptNcchSequential(u8* data, u32 offset, u32 size) {
// unexpected results otherwise
static NcchHeader ncch = { 0 };
static ExeFsHeader exefs = { 0 };
static NcchHeader* ncchptr = NULL;
static ExeFsHeader* exefsptr = NULL;
// fetch ncch header from data
if ((offset == 0) && (size >= sizeof(NcchHeader))) {
memcpy(&ncch, data, sizeof(NcchHeader));
ncchptr = (ValidateNcchHeader(&ncch) == 0) ? &ncch : NULL;
exefsptr = NULL;
}
// safety check, ncch pointer
if (!ncchptr) return 1;
// fetch exefs header from data
if (!exefsptr) {
u32 offset_exefs = ncch.offset_exefs * NCCH_MEDIA_UNIT;
u32 offset_exefs = ncchptr->offset_exefs * NCCH_MEDIA_UNIT;
if ((offset <= offset_exefs) &&
((offset + size) >= offset_exefs + sizeof(ExeFsHeader))) {
if (DecryptNcch(data, offset, offset_exefs + sizeof(ExeFsHeader) - offset, &ncch, NULL) != 0)
if (DecryptNcch(data, offset, offset_exefs + sizeof(ExeFsHeader) - offset, ncchptr, NULL) != 0)
return 1;
memcpy(&exefs, data + offset_exefs - offset, sizeof(ExeFsHeader));
size -= offset_exefs + sizeof(ExeFsHeader) - offset;
@ -311,5 +316,5 @@ u32 DecryptNcchSequential(u8* data, u32 offset, u32 size) {
}
}
return DecryptNcch(data, offset, size, &ncch, exefsptr);
return DecryptNcch(data, offset, size, ncchptr, exefsptr);
}

View File

@ -570,6 +570,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
bool verificable = (filetype & FYTPE_VERIFICABLE);
bool decryptable = (filetype & FYTPE_DECRYPTABLE);
bool decryptable_inplace = (decryptable && (drvtype & (DRV_SDCARD|DRV_RAMDRIVE)));
bool buildable = (filetype & FTYPE_BUILDABLE);
bool buildable_legit = (filetype & FTYPE_BUILDABLE_L);
char pathstr[32 + 1];
TruncateString(pathstr, curr_entry->path, 32, 8);
@ -596,7 +598,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
(filetype == GAME_NCCH ) ? "NCCH image options..." :
(filetype == GAME_EXEFS) ? "Mount as EXEFS image" :
(filetype == GAME_ROMFS) ? "Mount as ROMFS image" :
(filetype == GAME_TMD) ? "Verify TMD file" : "???";
(filetype == GAME_TMD) ? "TMD file options..." : "???";
optionstr[hexviewer-1] = "Show in Hexeditor";
optionstr[calcsha-1] = "Calculate SHA-256";
if (inject > 0) optionstr[inject-1] = "Inject data @offset";
@ -638,10 +640,14 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
int mount = (mountable) ? ++n_opt : -1;
int decrypt = (decryptable) ? ++n_opt : -1;
int decrypt_inplace = (decryptable_inplace) ? ++n_opt : -1;
int build = (buildable) ? ++n_opt : -1;
int build_legit = (buildable_legit) ? ++n_opt : -1;
int verify = (verificable) ? ++n_opt : -1;
if (mount > 0) optionstr[mount-1] = "Mount image to drive";
if (decrypt > 0) optionstr[decrypt-1] = "Decrypt file (SD output)";
if (decrypt_inplace > 0) optionstr[decrypt_inplace-1] = "Decrypt file (inplace)";
if (build > 0) optionstr[build-1] = (build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)";
if (build_legit > 0) optionstr[build_legit-1] = "Build CIA (legit)";
if (verify > 0) optionstr[verify-1] = "Verify file";
u32 n_marked = 0;
@ -714,6 +720,36 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
}
}
return 0;
} else if ((user_select == build) || (user_select == build_legit)) { // -> build CIA
bool force_legit = (user_select == build_legit);
if ((n_marked > 1) && ShowPrompt(true, "Try to process all %lu selected files?", n_marked)) {
u32 n_success = 0;
u32 n_other = 0;
for (u32 i = 0; i < current_dir->n_entries; i++) {
const char* path = current_dir->entry[i].path;
if (!current_dir->entry[i].marked)
continue;
if (IdentifyFileType(path) != filetype) {
n_other++;
continue;
}
current_dir->entry[i].marked = false;
if (BuildCiaFromGameFile(path, force_legit) == 0) n_success++;
else { // on failure: set *cursor on failed title, break;
*cursor = i;
break;
}
}
if (n_other) ShowPrompt(false, "%lu/%lu CIAs built ok\n%lu/%lu not of same type",
n_success, n_marked, n_other, n_marked);
else ShowPrompt(false, "%lu/%lu CIAs built ok", n_success, n_marked);
if (n_success) ShowPrompt(false, "%lu files written to %s", n_success, OUTPUT_PATH);
} else {
if (BuildCiaFromGameFile(curr_entry->path, force_legit) == 0)
ShowPrompt(false, "%s\nCIA built to %s", pathstr, OUTPUT_PATH);
else ShowPrompt(false, "%s\nCIA build failed", pathstr);
}
return 0;
} else if (user_select == verify) { // -> verify game file
if ((n_marked > 1) && ShowPrompt(true, "Try to verify all %lu selected files?", n_marked)) {
u32 n_success = 0;