mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 05:32: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)
|
||||
// -> 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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -3,7 +3,9 @@
|
||||
#include "common.h"
|
||||
|
||||
#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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user