Add LARGEDLC mode for titles with > 1024 contents

Fixes #703 and is only active with `make LARGEDLC=1` and will break compatibility with other titles and CIAs. Thanks @luigoalma for new ticket builder code!
This commit is contained in:
d0k3 2021-07-07 18:00:11 +02:00
parent 33a115b75c
commit c9b6a335f7
6 changed files with 137 additions and 59 deletions

View File

@ -25,6 +25,12 @@ else ifeq ($(FLAVOR),ZuishMode9)
CFLAGS += -DDEFAULT_FONT=\"font_zuish_8x8.pbm\"
endif
ifeq ($(LARGEDLC),1)
CFLAGS += -DTITLE_MAX_CONTENTS=1536
else
CFLAGS += -DTITLE_MAX_CONTENTS=1024
endif
ifeq ($(SALTMODE),1)
CFLAGS += -DSALTMODE
endif

View File

@ -41,28 +41,91 @@ u32 ValidateTicketSignature(Ticket* ticket) {
return ret;
}
u32 BuildFakeTicket(Ticket* ticket, u8* title_id) {
static const u8 sig_type[4] = { TICKET_SIG_TYPE }; // RSA_2048 SHA256
static 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,
0x00, 0x00, 0x00, 0x84, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// set ticket all zero for a clean start
memset(ticket, 0x00, TICKET_COMMON_SIZE); // 0xAC being size of this fake ticket's content index
// fill ticket values
memcpy(ticket->sig_type, sig_type, 4);
memset(ticket->signature, 0xFF, 0x100);
snprintf((char*) ticket->issuer, 0x40, IS_DEVKIT ? TICKET_ISSUER_DEV : TICKET_ISSUER);
memset(ticket->ecdsa, 0xFF, 0x3C);
ticket->version = 0x01;
memset(ticket->titlekey, 0xFF, 16);
memcpy(ticket->title_id, title_id, 8);
ticket->commonkey_idx = 0x00; // eshop
ticket->audit = 0x01; // whatever
memcpy(ticket->content_index, ticket_cnt_index, sizeof(ticket_cnt_index));
memset(&ticket->content_index[sizeof(ticket_cnt_index)], 0xFF, 0x80); // 1024 content indexes
u32 BuildVariableFakeTicket(Ticket** ticket, u32* ticket_size, const u8* title_id, u32 index_max) {
if (!ticket || !ticket_size)
return 1;
static const u8 sig_type[4] = { TICKET_SIG_TYPE }; // RSA_2048 SHA256
// calculate sizes and determine pointers to use
u32 rights_field_count = (min(index_max, 0x10000) + 1023) >> 10; // round up to 1024 and cap at 65536, and div by 1024
u32 content_index_size = sizeof(TicketContentIndexMainHeader) + sizeof(TicketContentIndexDataHeader) + sizeof(TicketRightsField) * rights_field_count;
u32 _ticket_size = sizeof(Ticket) + content_index_size;
Ticket *_ticket;
if (*ticket) { // if a pointer was pregiven
if (*ticket_size < _ticket_size) { // then check given boundary size
*ticket_size = _ticket_size; // if not enough, inform the actual needed size
return 2; // indicate a size error
}
_ticket = *ticket; // get the pointer if we good to go
} else // if not pregiven, allocate one
_ticket = (Ticket*)malloc(_ticket_size);
if (!_ticket)
return 1;
// set ticket all zero for a clean start
memset(_ticket, 0x00, _ticket_size);
// fill ticket values
memcpy(_ticket->sig_type, sig_type, 4);
memset(_ticket->signature, 0xFF, 0x100);
snprintf((char*) _ticket->issuer, 0x40, IS_DEVKIT ? TICKET_ISSUER_DEV : TICKET_ISSUER);
memset(_ticket->ecdsa, 0xFF, 0x3C);
_ticket->version = 0x01;
memset(_ticket->titlekey, 0xFF, 16);
memcpy(_ticket->title_id, title_id, 8);
_ticket->commonkey_idx = 0x00; // eshop
_ticket->audit = 0x01; // whatever
// fill in rights
TicketContentIndexMainHeader* mheader = (TicketContentIndexMainHeader*)&_ticket->content_index[0];
TicketContentIndexDataHeader* dheader = (TicketContentIndexDataHeader*)&_ticket->content_index[0x14];
TicketRightsField* rights = (TicketRightsField*)&_ticket->content_index[0x28];
// first main data header
mheader->unk1[1] = 0x1; mheader->unk2[1] = 0x14;
mheader->content_index_size[3] = (u8)(content_index_size >> 0);
mheader->content_index_size[2] = (u8)(content_index_size >> 8);
mheader->content_index_size[1] = (u8)(content_index_size >> 16);
mheader->content_index_size[0] = (u8)(content_index_size >> 24);
mheader->data_header_relative_offset[3] = 0x14; // relative offset for TicketContentIndexDataHeader
mheader->unk3[1] = 0x1; mheader->unk4[1] = 0x14;
// then the data header
dheader->data_relative_offset[3] = 0x28; // relative offset for TicketRightsField
dheader->max_entry_count[3] = (u8)(rights_field_count >> 0);
dheader->max_entry_count[2] = (u8)(rights_field_count >> 8);
dheader->max_entry_count[1] = (u8)(rights_field_count >> 16);
dheader->max_entry_count[0] = (u8)(rights_field_count >> 24);
dheader->size_per_entry[3] = (u8)sizeof(TicketRightsField); // sizeof should be 0x84
dheader->total_size_used[3] = (u8)((sizeof(TicketRightsField) * rights_field_count) >> 0);
dheader->total_size_used[2] = (u8)((sizeof(TicketRightsField) * rights_field_count) >> 8);
dheader->total_size_used[1] = (u8)((sizeof(TicketRightsField) * rights_field_count) >> 16);
dheader->total_size_used[0] = (u8)((sizeof(TicketRightsField) * rights_field_count) >> 24);
dheader->data_type[1] = 3; // right fields
// now the right fields
// indexoffets must be in accending order to have the desired effect
for (u32 i = 0; i < rights_field_count; ++i) {
rights[i].indexoffset[1] = (u8)((1024 * i) >> 0);
rights[i].indexoffset[0] = (u8)((1024 * i) >> 8);
memset(&rights[i].rightsbitfield[0], 0xFF, sizeof(rights[0].rightsbitfield));
}
*ticket = _ticket;
*ticket_size = _ticket_size;
return 0;
}
u32 BuildFakeTicket(Ticket* ticket, const u8* title_id) {
Ticket* tik;
u32 ticket_size = sizeof(TicketCommon);
u32 res = BuildVariableFakeTicket(&tik, &ticket_size, title_id, TICKET_MAX_CONTENTS);
if (res != 0) return res;
memcpy(ticket, tik, ticket_size);
free(tik);
return 0;
}

View File

@ -1,11 +1,14 @@
#pragma once
#include "common.h"
#include "tmd.h"
#define TICKET_COMMON_SIZE sizeof(TicketCommon)
#define TICKET_MINIMUM_SIZE sizeof(TicketMinimum)
#define TICKET_TWL_SIZE sizeof(Ticket)
#define TICKET_CDNCERT_SIZE 0x700
#define TICKET_MAX_CONTENTS TITLE_MAX_CONTENTS // should be TMD_MAX_CONTENTS
#define TICKET_COMMON_CNT_INDEX_SIZE (0x28 + (((TICKET_MAX_CONTENTS + 1023) >> 10) * 0x84))
#define TICKET_ISSUER "Root-CA00000003-XS0000000c"
#define TICKET_ISSUER_DEV "Root-CA00000004-XS00000009"
@ -54,7 +57,7 @@ typedef struct {
typedef struct {
TICKETBASE;
u8 content_index[0xAC];
u8 content_index[TICKET_COMMON_CNT_INDEX_SIZE];
} PACKED_STRUCT TicketCommon;
// minimum allowed content_index is 0x14
@ -97,7 +100,7 @@ typedef struct {
u32 ValidateTicket(Ticket* ticket);
u32 ValidateTwlTicket(Ticket* ticket);
u32 ValidateTicketSignature(Ticket* ticket);
u32 BuildFakeTicket(Ticket* ticket, u8* title_id);
u32 BuildFakeTicket(Ticket* ticket, const u8* title_id);
u32 GetTicketContentIndexSize(const Ticket* ticket);
u32 GetTicketSize(const Ticket* ticket);
u32 BuildTicketCert(u8* tickcert);

View File

@ -2,7 +2,7 @@
#include "common.h"
#define TMD_MAX_CONTENTS 1000 // 383 // theme CIAs contain maximum 100 themes + 1 index content
#define TMD_MAX_CONTENTS TITLE_MAX_CONTENTS // 1024 // 383 // 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)))

View File

@ -225,6 +225,10 @@ u32 LoadTmdFile(TitleMetaData* tmd, const char* path) {
if (fvx_qread(path, tmd, 0, TMD_SIZE_STUB, NULL) != FR_OK)
return 1;
// sanity check
if (getbe16(tmd->content_count) > TMD_MAX_CONTENTS)
return 1;
// second part (read full size)
if (ValidateTmd(tmd) == 0) {
if (fvx_qread(path, tmd, 0, TMD_SIZE_N(getbe16(tmd->content_count)), NULL) != FR_OK)
@ -348,7 +352,7 @@ u32 GetTmdContentPath(char* path_content, const char* path_tmd) {
free(tmd);
return 1;
}
snprintf(name_content, 256 - (name_content - path_content), cdn ? "%08lx" :
snprintf(name_content, 255 - (name_content - path_content), cdn ? "%08lx" :
(memcmp(tmd->title_id, dlc_tid_high, sizeof(dlc_tid_high)) == 0) ? "00000000/%08lx.app" : "%08lx.app", getbe32(chunk->id));
free(tmd);
@ -2173,19 +2177,12 @@ u32 BuildCiaLegitTicket(Ticket* ticket, u8* title_id, const char* path_cnt, bool
bool copy = true;
if ((cdn && (LoadCdnTicketFile(&ticket_tmp, path_cnt) != 0)) ||
(!cdn && (FindTicket(&ticket_tmp, title_id, true, src_emunand) != 0))) {
(!cdn && (FindTicket(&ticket_tmp, title_id, true, src_emunand) != 0)) ||
(GetTicketSize(ticket_tmp) != TICKET_COMMON_SIZE)) {
FindTitleKey(ticket, title_id);
copy = false;
}
// either, it's a ticket without ways to check ownership data, smaller sized
// or, it's title ticket with > 1024 contents, of which can't make it work with current CiaStub
if (copy && GetTicketSize(ticket_tmp) != TICKET_COMMON_SIZE) {
ShowPrompt(false, "ID %016llX\nLegit ticket of unsupported size.", getbe64(title_id));
free(ticket_tmp);
return 1;
}
// check the tickets' console id, warn if it isn't zero
if (copy && getbe32(ticket_tmp->console_id)) {
static u32 default_action = 0;
@ -2392,9 +2389,9 @@ u32 BuildInstallFromTmdFileBuffered(const char* path_tmd, const char* path_dest,
TicketRightsCheck rights_ctx;
TicketRightsCheck_InitContext(&rights_ctx, (Ticket*)&(cia->ticket));
snprintf(name_content, 256 - (name_content - path_content),
(cdn) ? "%08lx" : (dlc && !cdn) ? "00000000/%08lx.app" : "%08lx.app", getbe32(chunk->id));
(cdn) ? "%08lx" : (dlc) ? "00000000/%08lx.app" : "%08lx.app", getbe32(chunk->id));
if ((fvx_stat(path_content, &fno) != FR_OK) || (fno.fsize != (u32) getbe64(chunk->size)) ||
!TicketRightsCheck_CheckIndex(&rights_ctx, getbe16(chunk->index))) {
(!cdn && !TicketRightsCheck_CheckIndex(&rights_ctx, getbe16(chunk->index)))) {
present[i / 8] ^= 1 << (i % 8);
u16 index = getbe16(chunk->index);

View File

@ -108,11 +108,11 @@ static u64 offset_ccnt = (u64) -1;
static u64 offset_tad = (u64) -1;
static u32 index_ccnt = (u32) -1;
static CiaStub* cia = NULL;
// static CiaStub* cia = NULL; *unused*
static TwlHeader* twl = NULL;
static NcsdHeader* ncsd = NULL;
static FirmA9LHeader* a9l = NULL;
static FirmHeader* firm = NULL;
static NcsdHeader* ncsd = NULL;
static NcchHeader* ncch = NULL;
static ExeFsHeader* exefs = NULL;
static RomFsLv3Index lv3idx;
@ -357,7 +357,7 @@ bool BuildVGameNcsdDir(void) {
return true;
}
bool BuildVGameCiaDir(void) {
bool BuildVGameCiaDir(CiaStub* cia) {
CiaInfo info;
VirtualFile* templates = templates_cia;
u32 n = 0;
@ -777,21 +777,20 @@ u64 InitVGameDrive(void) { // prerequisite: game file mounted as image
vgame_buffer = (void*) malloc(0x40000);
if (!vgame_buffer) return 0;
templates_cia = (void*) ((u8*) vgame_buffer); // first 184kb reserved (enough for 3364 entries)
templates_firm = (void*) (((u8*) vgame_buffer) + 0x2E000); // 2kb reserved (enough for 36 entries)
templates_ncsd = (void*) (((u8*) vgame_buffer) + 0x2E800); // 2kb reserved (enough for 36 entries)
templates_ncch = (void*) (((u8*) vgame_buffer) + 0x2F000); // 1kb reserved (enough for 18 entries)
templates_nds = (void*) (((u8*) vgame_buffer) + 0x2F400); // 1kb reserved (enough for 18 entries)
templates_exefs = (void*) (((u8*) vgame_buffer) + 0x2F800); // 1kb reserved (enough for 18 entries)
templates_tad = (void*) (((u8*) vgame_buffer) + 0x2FC00); // 1kb reserved (enough for 18 entries)
cia = (CiaStub*) (void*) (((u8*) vgame_buffer) + 0x30000); // 61kB reserved - should be enough by far
twl = (TwlHeader*) (void*) (((u8*) vgame_buffer) + 0x3F400); // 512 byte reserved (not the full thing)
a9l = (FirmA9LHeader*) (void*) (((u8*) vgame_buffer) + 0x3F600); // 512 byte reserved
firm = (FirmHeader*) (void*) (((u8*) vgame_buffer) + 0x3F800); // 512 byte reserved
ncsd = (NcsdHeader*) (void*) (((u8*) vgame_buffer) + 0x3FA00); // 512 byte reserved
ncch = (NcchHeader*) (void*) (((u8*) vgame_buffer) + 0x3FC00); // 512 byte reserved
exefs = (ExeFsHeader*) (void*) (((u8*) vgame_buffer) + 0x3FE00); // 512 byte reserved
// filesystem stuff (RomFS / NitroFS) will be allocated on demand
templates_cia = (void*) ((u8*) vgame_buffer); // first 180kb reserved (enough for 3291 entries)
templates_firm = (void*) (((u8*) vgame_buffer) + 0x2D000); // 2kb reserved (enough for 36 entries)
templates_ncsd = (void*) (((u8*) vgame_buffer) + 0x2D800); // 2kb reserved (enough for 36 entries)
templates_ncch = (void*) (((u8*) vgame_buffer) + 0x2E000); // 1kb reserved (enough for 18 entries)
templates_nds = (void*) (((u8*) vgame_buffer) + 0x2E400); // 1kb reserved (enough for 18 entries)
templates_exefs = (void*) (((u8*) vgame_buffer) + 0x2E800); // 1kb reserved (enough for 18 entries)
templates_tad = (void*) (((u8*) vgame_buffer) + 0x2EC00); // 1kb reserved (enough for 18 entries)
twl = (TwlHeader*) (void*) (((u8*) vgame_buffer) + 0x2F000); // 512 byte reserved (not the full thing)
a9l = (FirmA9LHeader*) (void*) (((u8*) vgame_buffer) + 0x2F200); // 512 byte reserved
firm = (FirmHeader*) (void*) (((u8*) vgame_buffer) + 0x2F400); // 512 byte reserved
ncsd = (NcsdHeader*) (void*) (((u8*) vgame_buffer) + 0x2F600); // 512 byte reserved
ncch = (NcchHeader*) (void*) (((u8*) vgame_buffer) + 0x2F800); // 512 byte reserved
exefs = (ExeFsHeader*) (void*) (((u8*) vgame_buffer) + 0x2FA00); // 512 byte reserved (1kb reserve)
// filesystem stuff (RomFS / NitroFS) and CIA/TADX will be allocated on demand
vgame_type = type;
return type;
@ -842,14 +841,24 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) {
if (!BuildVGameTadDir()) return false;
} else if ((vdir->flags & VFLAG_CIA) && (offset_cia != vdir->offset)) {
CiaInfo info;
if ((ReadImageBytes((u8*) cia, 0, 0x20) != 0) ||
(ValidateCiaHeader(&(cia->header)) != 0) ||
(GetCiaInfo(&info, &(cia->header)) != 0) ||
(ReadImageBytes((u8*) cia, 0, info.offset_content) != 0))
CiaStub* cia;
u8 __attribute__((aligned(32))) hdr[0x20];
if ((ReadImageBytes(hdr, 0, 0x20) != 0) ||
(ValidateCiaHeader((CiaHeader*) (void*) hdr) != 0) ||
(GetCiaInfo(&info, (CiaHeader*) (void*) hdr) != 0) ||
!(cia = (CiaStub*) malloc(info.offset_content)))
return false;
if (ReadImageBytes((u8*) cia, 0, info.offset_content) != 0) {
free(cia);
return false;
}
offset_cia = vdir->offset; // always zero(!)
GetTitleKey(cia_titlekey, (Ticket*)&(cia->ticket));
if (!BuildVGameCiaDir()) return false;
if (!BuildVGameCiaDir(cia)) {
free(cia);
return false;
}
free(cia);
} else if ((vdir->flags & VFLAG_NCSD) && (offset_ncsd != vdir->offset)) {
if ((ReadImageBytes((u8*) ncsd, 0, sizeof(NcsdHeader)) != 0) ||
(ValidateNcsdHeader(ncsd) != 0))