mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 21:52:48 +00:00
Code reorganization / cleanup
This commit is contained in:
parent
32c5cd2196
commit
c0c13d2f7d
@ -41,7 +41,7 @@ u32 IdentifyFileType(const char* path) {
|
||||
} else if (memcmp(header, romfs_magic, sizeof(romfs_magic)) == 0) {
|
||||
return GAME_ROMFS; // RomFS file (check could be better)
|
||||
} 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
|
||||
}
|
||||
|
||||
|
@ -9,9 +9,9 @@
|
||||
u32 ValidateCiaHeader(CiaHeader* header) {
|
||||
if ((header->size_header != CIA_HEADER_SIZE) ||
|
||||
(header->size_cert != CIA_CERT_SIZE) ||
|
||||
(header->size_ticket != CIA_TICKET_SIZE) ||
|
||||
(header->size_tmd < CIA_TMD_SIZE_MIN) ||
|
||||
(header->size_tmd > CIA_TMD_SIZE_MAX) ||
|
||||
(header->size_ticket != TICKET_SIZE) ||
|
||||
(header->size_tmd < TMD_SIZE_MIN) ||
|
||||
(header->size_tmd > TMD_SIZE_MAX) ||
|
||||
(header->size_content == 0) ||
|
||||
((header->size_meta != 0) && (header->size_meta != CIA_META_SIZE)))
|
||||
return 1;
|
||||
@ -37,31 +37,11 @@ u32 GetCiaInfo(CiaInfo* info, CiaHeader* header) {
|
||||
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) {
|
||||
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);
|
||||
header->size_tmd = 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);
|
||||
@ -102,29 +82,6 @@ u32 BuildCiaCert(u8* ciacert) {
|
||||
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) {
|
||||
// init metadata with all zeroes and core version
|
||||
memset(meta, 0x00, sizeof(CiaMeta));
|
||||
|
@ -2,20 +2,11 @@
|
||||
|
||||
#include "common.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_CERT_SIZE 0xA00
|
||||
#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
|
||||
typedef struct {
|
||||
@ -26,48 +17,6 @@ typedef struct {
|
||||
u8 smdh[0x36C0]; // from ExeFS
|
||||
} __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 {
|
||||
u32 size_header;
|
||||
u16 type;
|
||||
@ -86,9 +35,9 @@ typedef struct {
|
||||
u8 cert[CIA_CERT_SIZE];
|
||||
// cert is aligned and needs no padding
|
||||
Ticket ticket;
|
||||
u8 ticket_padding[0x40 - (CIA_TICKET_SIZE % 0x40)];
|
||||
u8 ticket_padding[0x40 - (TICKET_SIZE % 0x40)];
|
||||
TitleMetaData tmd;
|
||||
TmdContentChunk content_list[CIA_MAX_CONTENTS];
|
||||
TmdContentChunk content_list[TMD_MAX_CONTENTS];
|
||||
} __attribute__((packed)) CiaStub;
|
||||
|
||||
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 GetCiaInfo(CiaInfo* info, CiaHeader* header);
|
||||
u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk);
|
||||
u32 FixTmdHashes(TitleMetaData* tmd);
|
||||
u32 FixCiaHeaderForTmd(CiaHeader* header, TitleMetaData* tmd);
|
||||
|
||||
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 BuildCiaHeader(CiaHeader* header);
|
||||
|
||||
|
@ -209,9 +209,9 @@ 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) ||
|
||||
if ((fx_read(&file, tmd, TMD_SIZE_MAX, &btr) != FR_OK) ||
|
||||
(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);
|
||||
return 1;
|
||||
}
|
||||
@ -401,7 +401,7 @@ u32 VerifyCiaFile(const char* path) {
|
||||
// verify contents
|
||||
u32 content_count = getbe16(cia->tmd.content_count);
|
||||
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]);
|
||||
if (VerifyTmdContent(path, next_offset, chunk, titlekey) != 0) {
|
||||
ShowPrompt(false, "%s\nID %08lX (%08llX@%08llX)\nVerification failed",
|
||||
@ -438,7 +438,7 @@ u32 VerifyTmdFile(const char* path) {
|
||||
|
||||
// verify contents
|
||||
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]);
|
||||
chunk->type[1] &= ~0x01; // remove crypto flag
|
||||
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
|
||||
u32 content_count = getbe16(cia->tmd.content_count);
|
||||
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]);
|
||||
if ((getbe16(chunk->type) & 0x1) || (CheckEncryptedNcchFile(path, next_offset) == 0))
|
||||
return 0; // encryption found
|
||||
@ -641,7 +641,7 @@ u32 DecryptCiaFile(const char* orig, const char* dest) {
|
||||
// decrypt CIA contents
|
||||
u32 content_count = getbe16(cia->tmd.content_count);
|
||||
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]);
|
||||
u64 size = getbe64(chunk->size);
|
||||
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
|
||||
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++) {
|
||||
for (u32 i = 0; (i < content_count) && (i < TMD_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, false) != 0) {
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#define TICKET_SIZE sizeof(Ticket)
|
||||
|
||||
#define TICKET_ISSUER "Root-CA00000003-XS0000000c"
|
||||
#define TICKET_ISSUER_DEV "Root-CA00000004-XS00000009"
|
||||
#define TICKET_SIG_TYPE 0x00, 0x01, 0x00, 0x04 // RSA_2048 SHA256
|
||||
|
45
source/game/tmd.c
Normal file
45
source/game/tmd.c
Normal 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
58
source/game/tmd.h
Normal 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);
|
@ -293,7 +293,7 @@ bool BuildVGameCiaDir(void) {
|
||||
TmdContentChunk* content_list = cia->content_list;
|
||||
u32 content_count = getbe16(cia->tmd.content_count);
|
||||
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);
|
||||
bool is_ncch = false; // is unencrypted NCCH?
|
||||
if (!(getbe16(content_list[i].type) & 0x1)) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user