Code reorganization / cleanup

This commit is contained in:
d0k3 2016-12-19 13:50:03 +01:00
parent 32c5cd2196
commit c0c13d2f7d
8 changed files with 121 additions and 113 deletions

View File

@ -41,7 +41,7 @@ u32 IdentifyFileType(const char* path) {
} else if (memcmp(header, romfs_magic, sizeof(romfs_magic)) == 0) { } else if (memcmp(header, romfs_magic, sizeof(romfs_magic)) == 0) {
return GAME_ROMFS; // RomFS file (check could be better) return GAME_ROMFS; // RomFS file (check could be better)
} else if (strncmp(TMD_ISSUER, (char*) (header + 0x140), 0x40) == 0) { } else if (strncmp(TMD_ISSUER, (char*) (header + 0x140), 0x40) == 0) {
if (fsize >= CIA_TMD_SIZE_N(getbe16(header + 0x1DE))) if (fsize >= TMD_SIZE_N(getbe16(header + 0x1DE)))
return GAME_TMD; // TMD file return GAME_TMD; // TMD file
} }

View File

@ -9,9 +9,9 @@
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) ||
(header->size_ticket != CIA_TICKET_SIZE) || (header->size_ticket != TICKET_SIZE) ||
(header->size_tmd < CIA_TMD_SIZE_MIN) || (header->size_tmd < TMD_SIZE_MIN) ||
(header->size_tmd > CIA_TMD_SIZE_MAX) || (header->size_tmd > TMD_SIZE_MAX) ||
(header->size_content == 0) || (header->size_content == 0) ||
((header->size_meta != 0) && (header->size_meta != CIA_META_SIZE))) ((header->size_meta != 0) && (header->size_meta != CIA_META_SIZE)))
return 1; return 1;
@ -37,31 +37,11 @@ u32 GetCiaInfo(CiaInfo* info, CiaHeader* header) {
return 0; return 0;
} }
u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk) {
memset(ctr, 0, 16);
memcpy(ctr, chunk->index, 2);
return 0;
}
u32 FixTmdHashes(TitleMetaData* tmd) {
TmdContentChunk* content_list = (TmdContentChunk*) (tmd + 1);
u32 content_count = getbe16(tmd->content_count);
// recalculate content info hashes
for (u32 i = 0, kc = 0; i < 64 && kc < content_count; i++) {
TmdContentInfo* info = tmd->contentinfo + i;
u32 k = getbe16(info->cmd_count);
sha_quick(info->hash, content_list + kc, k * sizeof(TmdContentChunk), SHA256_MODE);
kc += k;
}
sha_quick(tmd->contentinfo_hash, (u8*)tmd->contentinfo, 64 * sizeof(TmdContentInfo), SHA256_MODE);
return 0;
}
u32 FixCiaHeaderForTmd(CiaHeader* header, TitleMetaData* tmd) { u32 FixCiaHeaderForTmd(CiaHeader* header, TitleMetaData* tmd) {
TmdContentChunk* content_list = (TmdContentChunk*) (tmd + 1); TmdContentChunk* content_list = (TmdContentChunk*) (tmd + 1);
u32 content_count = getbe16(tmd->content_count); u32 content_count = getbe16(tmd->content_count);
header->size_content = 0; header->size_content = 0;
header->size_tmd = CIA_TMD_SIZE_N(content_count); header->size_tmd = TMD_SIZE_N(content_count);
memset(header->content_index, 0, sizeof(header->content_index)); memset(header->content_index, 0, sizeof(header->content_index));
for (u32 i = 0; i < content_count; i++) { for (u32 i = 0; i < content_count; i++) {
u16 index = getbe16(content_list[i].index); u16 index = getbe16(content_list[i].index);
@ -102,29 +82,6 @@ u32 BuildCiaCert(u8* ciacert) {
return 0; return 0;
} }
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size) {
const u8 sig_type[4] = { TMD_SIG_TYPE };
// safety check: number of contents
if (n_contents > CIA_MAX_CONTENTS) return 1; // !!!
// set TMD all zero for a clean start
memset(tmd, 0x00, CIA_TMD_SIZE_N(n_contents));
// file TMD values
memcpy(tmd->sig_type, sig_type, 4);
memset(tmd->signature, 0xFF, 0x100);
snprintf((char*) tmd->issuer, 0x40, TMD_ISSUER);
tmd->version = 0x01;
memcpy(tmd->title_id, title_id, 8);
tmd->title_type[3] = 0x40; // whatever
for (u32 i = 0; i < 4; i++) tmd->save_size[i] = (save_size >> (i*8)) & 0xFF; // little endian?
tmd->content_count[1] = (u8) n_contents;
memset(tmd->contentinfo_hash, 0xFF, 0x20); // placeholder (hash)
tmd->contentinfo[0].cmd_count[1] = (u8) n_contents;
memset(tmd->contentinfo[0].hash, 0xFF, 0x20); // placeholder (hash)
// nothing to do for content list (yet)
return 0;
}
u32 BuildCiaMeta(CiaMeta* meta, void* exthdr, void* smdh) { u32 BuildCiaMeta(CiaMeta* meta, void* exthdr, void* smdh) {
// init metadata with all zeroes and core version // init metadata with all zeroes and core version
memset(meta, 0x00, sizeof(CiaMeta)); memset(meta, 0x00, sizeof(CiaMeta));

View File

@ -2,20 +2,11 @@
#include "common.h" #include "common.h"
#include "ticket.h" #include "ticket.h"
#include "tmd.h"
#define TICKET_ISSUER "Root-CA00000003-XS0000000c"
#define TICKET_ISSUER_DEV "Root-CA00000004-XS00000009"
#define TMD_ISSUER "Root-CA00000003-CP0000000b"
#define TMD_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) #define CIA_HEADER_SIZE sizeof(CiaHeader)
#define CIA_CERT_SIZE 0xA00 #define CIA_CERT_SIZE 0xA00
#define CIA_META_SIZE sizeof(CiaMeta) #define CIA_META_SIZE sizeof(CiaMeta)
#define CIA_TICKET_SIZE sizeof(Ticket)
#define CIA_TMD_SIZE_MIN sizeof(TitleMetaData)
#define CIA_TMD_SIZE_MAX (sizeof(TitleMetaData) + (CIA_MAX_CONTENTS*sizeof(TmdContentChunk)))
#define CIA_TMD_SIZE_N(n) (sizeof(TitleMetaData) + (n*sizeof(TmdContentChunk)))
// see: https://www.3dbrew.org/wiki/CIA#Meta // see: https://www.3dbrew.org/wiki/CIA#Meta
typedef struct { typedef struct {
@ -26,48 +17,6 @@ typedef struct {
u8 smdh[0x36C0]; // from ExeFS u8 smdh[0x36C0]; // from ExeFS
} __attribute__((packed)) CiaMeta; } __attribute__((packed)) CiaMeta;
// from: https://github.com/profi200/Project_CTR/blob/02159e17ee225de3f7c46ca195ff0f9ba3b3d3e4/ctrtool/tmd.h#L18-L59;
typedef struct {
u8 id[4];
u8 index[2];
u8 type[2];
u8 size[8];
u8 hash[0x20];
} __attribute__((packed)) TmdContentChunk;
typedef struct {
u8 index[2];
u8 cmd_count[2];
u8 hash[0x20];
} __attribute__((packed)) TmdContentInfo;
typedef struct {
u8 sig_type[4];
u8 signature[0x100];
u8 padding[0x3C];
u8 issuer[0x40];
u8 version;
u8 ca_crl_version;
u8 signer_crl_version;
u8 reserved0;
u8 system_version[8];
u8 title_id[8];
u8 title_type[4];
u8 group_id[2];
u8 save_size[4];
u8 twl_privsave_size[4];
u8 reserved1[4];
u8 twl_flag;
u8 reserved2[0x31];
u8 access_rights[4];
u8 title_version[2];
u8 content_count[2];
u8 boot_content[2];
u8 reserved3[2];
u8 contentinfo_hash[0x20];
TmdContentInfo contentinfo[64];
} __attribute__((packed)) TitleMetaData;
typedef struct { typedef struct {
u32 size_header; u32 size_header;
u16 type; u16 type;
@ -86,9 +35,9 @@ typedef struct {
u8 cert[CIA_CERT_SIZE]; u8 cert[CIA_CERT_SIZE];
// cert is aligned and needs no padding // cert is aligned and needs no padding
Ticket ticket; Ticket ticket;
u8 ticket_padding[0x40 - (CIA_TICKET_SIZE % 0x40)]; u8 ticket_padding[0x40 - (TICKET_SIZE % 0x40)];
TitleMetaData tmd; TitleMetaData tmd;
TmdContentChunk content_list[CIA_MAX_CONTENTS]; TmdContentChunk content_list[TMD_MAX_CONTENTS];
} __attribute__((packed)) CiaStub; } __attribute__((packed)) CiaStub;
typedef struct { // first 0x20 bytes are identical with CIA header typedef struct { // first 0x20 bytes are identical with CIA header
@ -113,12 +62,9 @@ typedef struct { // first 0x20 bytes are identical with CIA header
u32 ValidateCiaHeader(CiaHeader* header); u32 ValidateCiaHeader(CiaHeader* header);
u32 GetCiaInfo(CiaInfo* info, CiaHeader* header); u32 GetCiaInfo(CiaInfo* info, CiaHeader* header);
u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk);
u32 FixTmdHashes(TitleMetaData* tmd);
u32 FixCiaHeaderForTmd(CiaHeader* header, TitleMetaData* tmd); u32 FixCiaHeaderForTmd(CiaHeader* header, TitleMetaData* tmd);
u32 BuildCiaCert(u8* ciacert); u32 BuildCiaCert(u8* ciacert);
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size);
u32 BuildCiaMeta(CiaMeta* meta, void* exthdr, void* smdh); u32 BuildCiaMeta(CiaMeta* meta, void* exthdr, void* smdh);
u32 BuildCiaHeader(CiaHeader* header); u32 BuildCiaHeader(CiaHeader* header);

View File

@ -209,9 +209,9 @@ 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, TMD_SIZE_MAX, &btr) != FR_OK) ||
(memcmp(tmd->sig_type, magic, sizeof(magic)) != 0) || (memcmp(tmd->sig_type, magic, sizeof(magic)) != 0) ||
(btr < CIA_TMD_SIZE_N(getbe16(tmd->content_count)))) { (btr < TMD_SIZE_N(getbe16(tmd->content_count)))) {
fx_close(&file); fx_close(&file);
return 1; return 1;
} }
@ -401,7 +401,7 @@ u32 VerifyCiaFile(const char* path) {
// verify contents // verify contents
u32 content_count = getbe16(cia->tmd.content_count); u32 content_count = getbe16(cia->tmd.content_count);
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 < TMD_MAX_CONTENTS); i++) {
TmdContentChunk* chunk = &(cia->content_list[i]); TmdContentChunk* chunk = &(cia->content_list[i]);
if (VerifyTmdContent(path, next_offset, chunk, titlekey) != 0) { if (VerifyTmdContent(path, next_offset, chunk, titlekey) != 0) {
ShowPrompt(false, "%s\nID %08lX (%08llX@%08llX)\nVerification failed", ShowPrompt(false, "%s\nID %08lX (%08llX@%08llX)\nVerification failed",
@ -438,7 +438,7 @@ u32 VerifyTmdFile(const char* path) {
// verify contents // verify contents
u32 content_count = getbe16(tmd->content_count); u32 content_count = getbe16(tmd->content_count);
for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) { for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++) {
TmdContentChunk* chunk = &(content_list[i]); TmdContentChunk* chunk = &(content_list[i]);
chunk->type[1] &= ~0x01; // remove crypto flag chunk->type[1] &= ~0x01; // remove crypto flag
snprintf(name_content, 256 - (name_content - path_content), "%08lx.app", getbe32(chunk->id)); snprintf(name_content, 256 - (name_content - path_content), "%08lx.app", getbe32(chunk->id));
@ -515,7 +515,7 @@ u32 CheckEncryptedCiaFile(const char* path) {
// check for encryption in CIA contents // check for encryption in CIA contents
u32 content_count = getbe16(cia->tmd.content_count); u32 content_count = getbe16(cia->tmd.content_count);
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 < TMD_MAX_CONTENTS); i++) {
TmdContentChunk* chunk = &(cia->content_list[i]); TmdContentChunk* chunk = &(cia->content_list[i]);
if ((getbe16(chunk->type) & 0x1) || (CheckEncryptedNcchFile(path, next_offset) == 0)) if ((getbe16(chunk->type) & 0x1) || (CheckEncryptedNcchFile(path, next_offset) == 0))
return 0; // encryption found return 0; // encryption found
@ -641,7 +641,7 @@ u32 DecryptCiaFile(const char* orig, const char* dest) {
// decrypt CIA contents // decrypt CIA contents
u32 content_count = getbe16(cia->tmd.content_count); u32 content_count = getbe16(cia->tmd.content_count);
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 < TMD_MAX_CONTENTS); i++) {
TmdContentChunk* chunk = &(cia->content_list[i]); TmdContentChunk* chunk = &(cia->content_list[i]);
u64 size = getbe64(chunk->size); u64 size = getbe64(chunk->size);
if (DecryptNcchNcsdFile(orig, dest, GAME_CIA, next_offset, size, chunk, titlekey) != 0) if (DecryptNcchNcsdFile(orig, dest, GAME_CIA, next_offset, size, chunk, titlekey) != 0)
@ -820,7 +820,7 @@ u32 BuildCiaFromTmdFile(const char* path_tmd, const char* path_cia, bool force_l
// insert contents // insert contents
u8 titlekey[16] = { 0xFF }; u8 titlekey[16] = { 0xFF };
if ((GetTitleKey(titlekey, &(cia->ticket)) != 0) && force_legit) return 1; if ((GetTitleKey(titlekey, &(cia->ticket)) != 0) && force_legit) return 1;
for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) { for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++) {
TmdContentChunk* chunk = &(content_list[i]); TmdContentChunk* chunk = &(content_list[i]);
snprintf(name_content, 256 - (name_content - path_content), "%08lx.app", getbe32(chunk->id)); 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, false) != 0) { if (InsertCiaContent(path_cia, path_content, 0, (u32) getbe64(chunk->size), chunk, titlekey, force_legit, false) != 0) {

View File

@ -2,6 +2,8 @@
#include "common.h" #include "common.h"
#define TICKET_SIZE sizeof(Ticket)
#define TICKET_ISSUER "Root-CA00000003-XS0000000c" #define TICKET_ISSUER "Root-CA00000003-XS0000000c"
#define TICKET_ISSUER_DEV "Root-CA00000004-XS00000009" #define TICKET_ISSUER_DEV "Root-CA00000004-XS00000009"
#define TICKET_SIG_TYPE 0x00, 0x01, 0x00, 0x04 // RSA_2048 SHA256 #define TICKET_SIG_TYPE 0x00, 0x01, 0x00, 0x04 // RSA_2048 SHA256

45
source/game/tmd.c Normal file
View File

@ -0,0 +1,45 @@
#include "tmd.h"
#include "sha.h"
u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk) {
memset(ctr, 0, 16);
memcpy(ctr, chunk->index, 2);
return 0;
}
u32 FixTmdHashes(TitleMetaData* tmd) {
TmdContentChunk* content_list = (TmdContentChunk*) (tmd + 1);
u32 content_count = getbe16(tmd->content_count);
// recalculate content info hashes
for (u32 i = 0, kc = 0; i < 64 && kc < content_count; i++) {
TmdContentInfo* info = tmd->contentinfo + i;
u32 k = getbe16(info->cmd_count);
sha_quick(info->hash, content_list + kc, k * sizeof(TmdContentChunk), SHA256_MODE);
kc += k;
}
sha_quick(tmd->contentinfo_hash, (u8*)tmd->contentinfo, 64 * sizeof(TmdContentInfo), SHA256_MODE);
return 0;
}
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size) {
const u8 sig_type[4] = { TMD_SIG_TYPE };
// safety check: number of contents
if (n_contents > TMD_MAX_CONTENTS) return 1; // !!!
// set TMD all zero for a clean start
memset(tmd, 0x00, TMD_SIZE_N(n_contents));
// file TMD values
memcpy(tmd->sig_type, sig_type, 4);
memset(tmd->signature, 0xFF, 0x100);
snprintf((char*) tmd->issuer, 0x40, TMD_ISSUER);
tmd->version = 0x01;
memcpy(tmd->title_id, title_id, 8);
tmd->title_type[3] = 0x40; // whatever
for (u32 i = 0; i < 4; i++) tmd->save_size[i] = (save_size >> (i*8)) & 0xFF; // little endian?
tmd->content_count[1] = (u8) n_contents;
memset(tmd->contentinfo_hash, 0xFF, 0x20); // placeholder (hash)
tmd->contentinfo[0].cmd_count[1] = (u8) n_contents;
memset(tmd->contentinfo[0].hash, 0xFF, 0x20); // placeholder (hash)
// nothing to do for content list (yet)
return 0;
}

58
source/game/tmd.h Normal file
View File

@ -0,0 +1,58 @@
#pragma once
#include "common.h"
#define TMD_MAX_CONTENTS (100+1) // theme CIAs contain maximum 100 themes + 1 index content
#define TMD_SIZE_MIN sizeof(TitleMetaData)
#define TMD_SIZE_MAX (sizeof(TitleMetaData) + (TMD_MAX_CONTENTS*sizeof(TmdContentChunk)))
#define TMD_SIZE_N(n) (sizeof(TitleMetaData) + (n*sizeof(TmdContentChunk)))
#define TMD_ISSUER "Root-CA00000003-CP0000000b"
#define TMD_SIG_TYPE 0x00, 0x01, 0x00, 0x04 // RSA_2048 SHA256
// from: https://github.com/profi200/Project_CTR/blob/02159e17ee225de3f7c46ca195ff0f9ba3b3d3e4/ctrtool/tmd.h#L18-L59;
typedef struct {
u8 id[4];
u8 index[2];
u8 type[2];
u8 size[8];
u8 hash[0x20];
} __attribute__((packed)) TmdContentChunk;
typedef struct {
u8 index[2];
u8 cmd_count[2];
u8 hash[0x20];
} __attribute__((packed)) TmdContentInfo;
typedef struct {
u8 sig_type[4];
u8 signature[0x100];
u8 padding[0x3C];
u8 issuer[0x40];
u8 version;
u8 ca_crl_version;
u8 signer_crl_version;
u8 reserved0;
u8 system_version[8];
u8 title_id[8];
u8 title_type[4];
u8 group_id[2];
u8 save_size[4];
u8 twl_privsave_size[4];
u8 reserved1[4];
u8 twl_flag;
u8 reserved2[0x31];
u8 access_rights[4];
u8 title_version[2];
u8 content_count[2];
u8 boot_content[2];
u8 reserved3[2];
u8 contentinfo_hash[0x20];
TmdContentInfo contentinfo[64];
} __attribute__((packed)) TitleMetaData;
u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk);
u32 FixTmdHashes(TitleMetaData* tmd);
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size);

View File

@ -293,7 +293,7 @@ bool BuildVGameCiaDir(void) {
TmdContentChunk* content_list = cia->content_list; TmdContentChunk* content_list = cia->content_list;
u32 content_count = getbe16(cia->tmd.content_count); u32 content_count = getbe16(cia->tmd.content_count);
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 < TMD_MAX_CONTENTS); i++) {
u64 size = getbe64(content_list[i].size); u64 size = getbe64(content_list[i].size);
bool is_ncch = false; // is unencrypted NCCH? bool is_ncch = false; // is unencrypted NCCH?
if (!(getbe16(content_list[i].type) & 0x1)) { if (!(getbe16(content_list[i].type) & 0x1)) {