mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 13:42:47 +00:00
Enable build CIA from TMD files
This commit is contained in:
parent
1f98f88d87
commit
2a904dc284
@ -50,6 +50,7 @@
|
|||||||
// buffer area defines (temporary, in use by various functions)
|
// buffer area defines (temporary, in use by various functions)
|
||||||
// -> godmode.c hexviewer
|
// -> godmode.c hexviewer
|
||||||
// -> ncch.c seed setup
|
// -> ncch.c seed setup
|
||||||
|
// -> cia.c ticket / titlekey setup
|
||||||
// -> gameutil.c various temporary stuff
|
// -> gameutil.c various temporary stuff
|
||||||
#define TEMP_BUFFER ((u8*)0x21100000)
|
#define TEMP_BUFFER ((u8*)0x21100000)
|
||||||
#define TEMP_BUFFER_SIZE (0x100000)
|
#define TEMP_BUFFER_SIZE (0x100000)
|
||||||
|
@ -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)
|
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;
|
size_t bytes_left = size;
|
||||||
|
@ -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 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 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_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_cmac(void* inbuf, void* outbuf, size_t size);
|
||||||
void aes_fifos(void* inbuf, void* outbuf, size_t blocks);
|
void aes_fifos(void* inbuf, void* outbuf, size_t blocks);
|
||||||
void set_aeswrfifo(uint32_t value);
|
void set_aeswrfifo(uint32_t value);
|
||||||
|
@ -15,5 +15,7 @@
|
|||||||
#define FTYPE_MOUNTABLE (IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS)
|
#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_VERIFICABLE (GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD)
|
||||||
#define FYTPE_DECRYPTABLE (GAME_CIA|GAME_NCSD|GAME_NCCH)
|
#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);
|
u32 IdentifyFileType(const char* path);
|
||||||
|
@ -1,8 +1,27 @@
|
|||||||
#include "cia.h"
|
#include "cia.h"
|
||||||
|
#include "ncch.h"
|
||||||
|
#include "exefs.h"
|
||||||
#include "ff.h"
|
#include "ff.h"
|
||||||
|
#include "sddata.h"
|
||||||
#include "aes.h"
|
#include "aes.h"
|
||||||
#include "sha.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) {
|
u32 ValidateCiaHeader(CiaHeader* header) {
|
||||||
if ((header->size_header != CIA_HEADER_SIZE) ||
|
if ((header->size_header != CIA_HEADER_SIZE) ||
|
||||||
(header->size_cert != CIA_CERT_SIZE) ||
|
(header->size_cert != CIA_CERT_SIZE) ||
|
||||||
@ -50,9 +69,9 @@ u32 GetCiaContentInfo(CiaContentInfo* contents, TitleMetaData* tmd) {
|
|||||||
return 0;
|
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
|
// 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
|
{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
|
{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
|
{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
|
{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
|
// 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
|
{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
|
{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
|
{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
|
{0xAA, 0xDA, 0x4C, 0xA8, 0xF6, 0xE5, 0xA9, 0x77, 0xE0, 0xA0, 0xF9, 0xE4, 0x76, 0xCF, 0x0D, 0x63} , // 5
|
||||||
};*/
|
};*/
|
||||||
|
|
||||||
// setup key 0x3D
|
u32 mode = (encrypt) ? AES_CNT_TITLEKEY_ENCRYPT_MODE : AES_CNT_TITLEKEY_DECRYPT_MODE;
|
||||||
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
|
|
||||||
u8 ctr[16] = { 0 };
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +135,35 @@ u32 FixTmdHashes(TitleMetaData* tmd) {
|
|||||||
return 0;
|
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) {
|
u32 BuildCiaCert(u8* ciacert) {
|
||||||
const u8 cert_hash_expected[0x20] = {
|
const u8 cert_hash_expected[0x20] = {
|
||||||
0xC7, 0x2E, 0x1C, 0xA5, 0x61, 0xDC, 0x9B, 0xC8, 0x05, 0x58, 0x58, 0x9C, 0x63, 0x08, 0x1C, 0x8A,
|
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) {
|
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
|
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, 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,
|
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;
|
ticket->version = 0x01;
|
||||||
memset(ticket->titlekey, 0xFF, 16);
|
memset(ticket->titlekey, 0xFF, 16);
|
||||||
memcpy(ticket->title_id, title_id, 8);
|
memcpy(ticket->title_id, title_id, 8);
|
||||||
ticket->commonkey_idx = 0x01;
|
ticket->commonkey_idx = 0x00; // eshop
|
||||||
ticket->audit = 0x01; // whatever
|
ticket->audit = 0x01; // whatever
|
||||||
memcpy(ticket->content_index, ticket_cnt_index, sizeof(ticket_cnt_index));
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents) {
|
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
|
// 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
|
// 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
|
// file TMD values
|
||||||
memcpy(tmd->sig_type, sig_type, 4);
|
memcpy(tmd->sig_type, sig_type, 4);
|
||||||
memset(tmd->signature, 0xFF, 0x100);
|
memset(tmd->signature, 0xFF, 0x100);
|
||||||
@ -185,6 +276,29 @@ u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents) {
|
|||||||
return 0;
|
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) {
|
u32 DecryptCiaContentSequential(u8* data, u32 size, u8* ctr, const u8* titlekey) {
|
||||||
// WARNING: size and offset of data have to be a multiple of 16
|
// WARNING: size and offset of data have to be a multiple of 16
|
||||||
u8 tik[16] __attribute__((aligned(32)));
|
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);
|
cbc_decrypt(data, data, size / 16, mode, ctr);
|
||||||
return 0;
|
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;
|
||||||
|
}
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#define CIA_TICKET_ISSUER "Root-CA00000003-XS0000000c"
|
#define CIA_TICKET_ISSUER "Root-CA00000003-XS0000000c"
|
||||||
#define CIA_TMD_ISSUER "Root-CA00000003-CP0000000b"
|
#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_MAX_CONTENTS (100+1) // theme CIAs contain maximum 100 themes + 1 index content
|
||||||
#define CIA_HEADER_SIZE sizeof(CiaHeader)
|
#define CIA_HEADER_SIZE sizeof(CiaHeader)
|
||||||
@ -153,12 +155,14 @@ u32 GetCiaContentInfo(CiaContentInfo* contents, TitleMetaData* tmd);
|
|||||||
u32 GetTitleKey(u8* titlekey, Ticket* ticket);
|
u32 GetTitleKey(u8* titlekey, Ticket* ticket);
|
||||||
u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk);
|
u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk);
|
||||||
u32 FixTmdHashes(TitleMetaData* tmd);
|
u32 FixTmdHashes(TitleMetaData* tmd);
|
||||||
|
u32 FixCiaHeaderForTmd(CiaHeader* header, TitleMetaData* tmd);
|
||||||
|
Ticket* ParseForTicket(u8* data, u32 size, u8* title_id);
|
||||||
|
|
||||||
u32 BuildCiaCert(u8* ciacert);
|
u32 BuildCiaCert(u8* ciacert);
|
||||||
u32 BuildFakeTicket(Ticket* ticket, u8* title_id);
|
u32 BuildFakeTicket(Ticket* ticket, u8* title_id);
|
||||||
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents);
|
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents);
|
||||||
|
u32 BuildCiaMeta(CiaMeta* meta, u8* exthdr, u8* smdh);
|
||||||
/*u32 BuildCiaMeta(Ncch ncch);
|
u32 BuildCiaHeader(CiaHeader* header);
|
||||||
u32 InsertCiaContent(const char* path, const char* content, bool encrypt);*/
|
|
||||||
|
|
||||||
u32 DecryptCiaContentSequential(u8* data, u32 size, u8* ctr, const u8* titlekey);
|
u32 DecryptCiaContentSequential(u8* data, u32 size, u8* ctr, const u8* titlekey);
|
||||||
|
u32 EncryptCiaContentSequential(u8* data, u32 size, u8* ctr, const u8* titlekey);
|
||||||
|
@ -35,7 +35,7 @@ u32 GetOutputPath(char* dest, const char* path, const char* ext) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 GetNcchFileHeaders(NcchHeader* ncch, ExeFsHeader* exefs, FIL* file) {
|
u32 GetNcchHeaders(NcchHeader* ncch, ExeFsHeader* exefs, FIL* file) {
|
||||||
u32 offset_ncch = f_tell(file);
|
u32 offset_ncch = f_tell(file);
|
||||||
UINT btr;
|
UINT btr;
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ u32 GetNcchFileHeaders(NcchHeader* ncch, ExeFsHeader* exefs, FIL* file) {
|
|||||||
return 0;
|
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;
|
u32 offset_data = f_tell(file) - offset_ncch;
|
||||||
u8 hash[32];
|
u8 hash[32];
|
||||||
|
|
||||||
@ -118,7 +118,69 @@ u32 LoadCiaStub(CiaStub* stub, const char* path) {
|
|||||||
return 0;
|
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) {
|
u32 LoadTmdFile(TitleMetaData* tmd, const char* path) {
|
||||||
|
const u8 magic[] = { CIA_SIG_TYPE };
|
||||||
FIL file;
|
FIL file;
|
||||||
UINT btr;
|
UINT btr;
|
||||||
|
|
||||||
@ -128,6 +190,7 @@ u32 LoadTmdFile(TitleMetaData* tmd, const char* path) {
|
|||||||
// full TMD file
|
// full TMD file
|
||||||
f_lseek(&file, 0);
|
f_lseek(&file, 0);
|
||||||
if ((fx_read(&file, tmd, CIA_TMD_SIZE_MAX, &btr) != FR_OK) ||
|
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)))) {
|
(btr < CIA_TMD_SIZE_N(getbe16(tmd->content_count)))) {
|
||||||
fx_close(&file);
|
fx_close(&file);
|
||||||
return 1;
|
return 1;
|
||||||
@ -157,6 +220,31 @@ u32 WriteCiaStub(CiaStub* stub, const char* path) {
|
|||||||
return 0;
|
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) {
|
u32 VerifyTmdContent(const char* path, u64 offset, TmdContentChunk* chunk, const u8* titlekey) {
|
||||||
u8 hash[32];
|
u8 hash[32];
|
||||||
u8 ctr[16];
|
u8 ctr[16];
|
||||||
@ -204,7 +292,7 @@ u32 VerifyNcchFile(const char* path, u32 offset, u32 size) {
|
|||||||
return 1;
|
return 1;
|
||||||
f_lseek(&file, offset);
|
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);
|
if (!offset) ShowPrompt(false, "%s\nError: Not a NCCH file", pathstr);
|
||||||
fx_close(&file);
|
fx_close(&file);
|
||||||
return 1;
|
return 1;
|
||||||
@ -232,19 +320,19 @@ u32 VerifyNcchFile(const char* path, u32 offset, u32 size) {
|
|||||||
// base hash check for extheader
|
// base hash check for extheader
|
||||||
if (ncch.size_exthdr > 0) {
|
if (ncch.size_exthdr > 0) {
|
||||||
f_lseek(&file, offset + NCCH_EXTHDR_OFFSET);
|
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
|
// base hash check for exefs
|
||||||
if (ncch.size_exefs > 0) {
|
if (ncch.size_exefs > 0) {
|
||||||
f_lseek(&file, offset + (ncch.offset_exefs * NCCH_MEDIA_UNIT));
|
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
|
// base hash check for romfs
|
||||||
if (ncch.size_romfs > 0) {
|
if (ncch.size_romfs > 0) {
|
||||||
f_lseek(&file, offset + (ncch.offset_romfs * NCCH_MEDIA_UNIT));
|
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
|
// thorough exefs verification
|
||||||
@ -254,7 +342,7 @@ u32 VerifyNcchFile(const char* path, u32 offset, u32 size) {
|
|||||||
u8* hash = exefs.hashes[9 - i];
|
u8* hash = exefs.hashes[9 - i];
|
||||||
if (!exefile->size) continue;
|
if (!exefile->size) continue;
|
||||||
f_lseek(&file, offset + (ncch.offset_exefs * NCCH_MEDIA_UNIT) + 0x200 + exefile->offset);
|
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;
|
u64 next_offset = info.offset_content;
|
||||||
for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) {
|
for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) {
|
||||||
TmdContentChunk* chunk = &(cia->content_list[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
|
return 0; // encryption found
|
||||||
next_offset += getbe64(chunk->size);
|
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);
|
if (cia_crypto) DecryptCiaContentSequential(MAIN_BUFFER, sizeof(NcchHeader), ctr, titlekey);
|
||||||
ncch_crypto = ((ValidateNcchHeader((NcchHeader*) (void*) MAIN_BUFFER) == 0) &&
|
ncch_crypto = ((ValidateNcchHeader((NcchHeader*) (void*) MAIN_BUFFER) == 0) &&
|
||||||
NCCH_ENCRYPTED((NcchHeader*) (void*) MAIN_BUFFER));
|
NCCH_ENCRYPTED((NcchHeader*) (void*) MAIN_BUFFER));
|
||||||
if (ncch_crypto && (SetupNcchCrypto((NcchHeader*) (void*) MAIN_BUFFER) != 0)) {
|
if (ncch_crypto && (SetupNcchCrypto((NcchHeader*) (void*) MAIN_BUFFER) != 0))
|
||||||
ShowPrompt(false, "Error: Cannot set up NCCH crypto");
|
ret = 1;
|
||||||
};
|
|
||||||
|
|
||||||
GetTmdCtr(ctr, chunk);
|
GetTmdCtr(ctr, chunk);
|
||||||
f_lseek(ofp, offset);
|
f_lseek(ofp, offset);
|
||||||
@ -605,3 +692,179 @@ u32 DecryptGameFile(const char* path, bool inplace) {
|
|||||||
|
|
||||||
return ret;
|
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;
|
||||||
|
}
|
||||||
|
@ -5,3 +5,4 @@
|
|||||||
u32 VerifyGameFile(const char* path);
|
u32 VerifyGameFile(const char* path);
|
||||||
u32 CheckEncryptedGameFile(const char* path);
|
u32 CheckEncryptedGameFile(const char* path);
|
||||||
u32 DecryptGameFile(const char* path, bool inplace);
|
u32 DecryptGameFile(const char* path, bool inplace);
|
||||||
|
u32 BuildCiaFromGameFile(const char* path, bool force_legit);
|
||||||
|
@ -97,7 +97,7 @@ u32 GetNcchSeed(u8* seed, NcchHeader* ncch) {
|
|||||||
nand_drv[i], sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3]);
|
nand_drv[i], sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3]);
|
||||||
|
|
||||||
// check seedsave for seed
|
// 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)
|
if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||||
continue;
|
continue;
|
||||||
f_read(&file, seedsave, 0x200, &btr);
|
f_read(&file, seedsave, 0x200, &btr);
|
||||||
@ -124,11 +124,11 @@ u32 GetNcchSeed(u8* seed, NcchHeader* ncch) {
|
|||||||
// not found -> try seeddb.bin
|
// not found -> try seeddb.bin
|
||||||
const char* base[] = { INPUT_PATHS };
|
const char* base[] = { INPUT_PATHS };
|
||||||
for (u32 i = 0; i < (sizeof(base)/sizeof(char*)); i++) {
|
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);
|
snprintf(path, 128, "%s/%s", base[i], SEEDDB_NAME);
|
||||||
if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||||
continue;
|
continue;
|
||||||
f_read(&file, seeddb, TEMP_BUFFER_SIZE, &btr);
|
f_read(&file, seeddb, TEMP_BUFFER_SIZE / 2, &btr);
|
||||||
f_close(&file);
|
f_close(&file);
|
||||||
if (seeddb->n_entries > (btr - 16) / 32)
|
if (seeddb->n_entries > (btr - 16) / 32)
|
||||||
continue; // filesize / seeddb size mismatch
|
continue; // filesize / seeddb size mismatch
|
||||||
@ -288,20 +288,25 @@ u32 DecryptNcchSequential(u8* data, u32 offset, u32 size) {
|
|||||||
// unexpected results otherwise
|
// unexpected results otherwise
|
||||||
static NcchHeader ncch = { 0 };
|
static NcchHeader ncch = { 0 };
|
||||||
static ExeFsHeader exefs = { 0 };
|
static ExeFsHeader exefs = { 0 };
|
||||||
|
static NcchHeader* ncchptr = NULL;
|
||||||
static ExeFsHeader* exefsptr = NULL;
|
static ExeFsHeader* exefsptr = NULL;
|
||||||
|
|
||||||
// fetch ncch header from data
|
// fetch ncch header from data
|
||||||
if ((offset == 0) && (size >= sizeof(NcchHeader))) {
|
if ((offset == 0) && (size >= sizeof(NcchHeader))) {
|
||||||
memcpy(&ncch, data, sizeof(NcchHeader));
|
memcpy(&ncch, data, sizeof(NcchHeader));
|
||||||
|
ncchptr = (ValidateNcchHeader(&ncch) == 0) ? &ncch : NULL;
|
||||||
exefsptr = NULL;
|
exefsptr = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// safety check, ncch pointer
|
||||||
|
if (!ncchptr) return 1;
|
||||||
|
|
||||||
// fetch exefs header from data
|
// fetch exefs header from data
|
||||||
if (!exefsptr) {
|
if (!exefsptr) {
|
||||||
u32 offset_exefs = ncch.offset_exefs * NCCH_MEDIA_UNIT;
|
u32 offset_exefs = ncchptr->offset_exefs * NCCH_MEDIA_UNIT;
|
||||||
if ((offset <= offset_exefs) &&
|
if ((offset <= offset_exefs) &&
|
||||||
((offset + size) >= offset_exefs + sizeof(ExeFsHeader))) {
|
((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;
|
return 1;
|
||||||
memcpy(&exefs, data + offset_exefs - offset, sizeof(ExeFsHeader));
|
memcpy(&exefs, data + offset_exefs - offset, sizeof(ExeFsHeader));
|
||||||
size -= offset_exefs + sizeof(ExeFsHeader) - offset;
|
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);
|
||||||
}
|
}
|
||||||
|
@ -570,6 +570,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
|
|||||||
bool verificable = (filetype & FYTPE_VERIFICABLE);
|
bool verificable = (filetype & FYTPE_VERIFICABLE);
|
||||||
bool decryptable = (filetype & FYTPE_DECRYPTABLE);
|
bool decryptable = (filetype & FYTPE_DECRYPTABLE);
|
||||||
bool decryptable_inplace = (decryptable && (drvtype & (DRV_SDCARD|DRV_RAMDRIVE)));
|
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];
|
char pathstr[32 + 1];
|
||||||
TruncateString(pathstr, curr_entry->path, 32, 8);
|
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_NCCH ) ? "NCCH image options..." :
|
||||||
(filetype == GAME_EXEFS) ? "Mount as EXEFS image" :
|
(filetype == GAME_EXEFS) ? "Mount as EXEFS image" :
|
||||||
(filetype == GAME_ROMFS) ? "Mount as ROMFS 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[hexviewer-1] = "Show in Hexeditor";
|
||||||
optionstr[calcsha-1] = "Calculate SHA-256";
|
optionstr[calcsha-1] = "Calculate SHA-256";
|
||||||
if (inject > 0) optionstr[inject-1] = "Inject data @offset";
|
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 mount = (mountable) ? ++n_opt : -1;
|
||||||
int decrypt = (decryptable) ? ++n_opt : -1;
|
int decrypt = (decryptable) ? ++n_opt : -1;
|
||||||
int decrypt_inplace = (decryptable_inplace) ? ++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;
|
int verify = (verificable) ? ++n_opt : -1;
|
||||||
if (mount > 0) optionstr[mount-1] = "Mount image to drive";
|
if (mount > 0) optionstr[mount-1] = "Mount image to drive";
|
||||||
if (decrypt > 0) optionstr[decrypt-1] = "Decrypt file (SD output)";
|
if (decrypt > 0) optionstr[decrypt-1] = "Decrypt file (SD output)";
|
||||||
if (decrypt_inplace > 0) optionstr[decrypt_inplace-1] = "Decrypt file (inplace)";
|
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";
|
if (verify > 0) optionstr[verify-1] = "Verify file";
|
||||||
|
|
||||||
u32 n_marked = 0;
|
u32 n_marked = 0;
|
||||||
@ -714,6 +720,36 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
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
|
} else if (user_select == verify) { // -> verify game file
|
||||||
if ((n_marked > 1) && ShowPrompt(true, "Try to verify all %lu selected files?", n_marked)) {
|
if ((n_marked > 1) && ShowPrompt(true, "Try to verify all %lu selected files?", n_marked)) {
|
||||||
u32 n_success = 0;
|
u32 n_success = 0;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user