diff --git a/arm9/source/filesys/filetype.h b/arm9/source/filesys/filetype.h index ac42d5d..6384c0e 100644 --- a/arm9/source/filesys/filetype.h +++ b/arm9/source/filesys/filetype.h @@ -46,11 +46,11 @@ #define FLAG_CXI (1ULL<<63) #define FTYPE_MOUNTABLE(tp) (tp&(IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|GAME_NDS|GAME_TAD|SYS_FIRM|SYS_DIFF|SYS_DISA|SYS_TICKDB|BIN_KEYDB)) -#define FTYPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_TIE|GAME_TICKET|GAME_BOSS|SYS_FIRM)) +#define FTYPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_TIE|GAME_TAD|GAME_TICKET|GAME_BOSS|SYS_FIRM)) #define FTYPE_DECRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|GAME_NUSCDN|SYS_FIRM|BIN_KEYDB)) #define FTYPE_ENCRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|BIN_KEYDB)) -#define FTYPE_CIABUILD(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_TIE)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW)))) -#define FTYPE_CIABUILD_L(tp) (FTYPE_CIABUILD(tp) && (tp&(GAME_TMD|GAME_TIE))) +#define FTYPE_CIABUILD(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_TIE|GAME_TAD)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW)))) +#define FTYPE_CIABUILD_L(tp) (FTYPE_CIABUILD(tp) && (tp&(GAME_TMD|GAME_TIE|GAME_TAD))) #define FTYPE_CIAINSTALL(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_CIA)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW))) || (tp&(GAME_TMD)&&(tp&(FLAG_NUSCDN)))) #define FTYPE_TIKINSTALL(tp) (tp&(GAME_TICKET)) #define FTYPE_CXIDUMP(tp) (tp&(GAME_TMD)) diff --git a/arm9/source/game/tad.c b/arm9/source/game/tad.c index 91c5d5e..8dc5928 100644 --- a/arm9/source/game/tad.c +++ b/arm9/source/game/tad.c @@ -1,6 +1,20 @@ #include "tad.h" +#include "sha.h" +u32 VerifyTadStub(TadStub* tad) { + TadFooter* ftr = &(tad->footer); + TadHeader* hdr = &(tad->header); + TadBanner* bnr = &(tad->banner); + + if ((strncmp(hdr->magic, TAD_HEADER_MAGIC, strlen(TAD_HEADER_MAGIC)) != 0) || + (sha_cmp(ftr->banner_sha256, bnr, sizeof(TadBanner), SHA256_MODE) != 0) || + (sha_cmp(ftr->header_sha256, hdr, sizeof(TadHeader), SHA256_MODE) != 0)) + return 1; + + return 0; +} + u32 BuildTadContentTable(void* table, void* header) { TadHeader* hdr = (TadHeader*) header; TadContentTable* tbl = (TadContentTable*) table; diff --git a/arm9/source/game/tad.h b/arm9/source/game/tad.h index acce880..2c99c72 100644 --- a/arm9/source/game/tad.h +++ b/arm9/source/game/tad.h @@ -56,4 +56,14 @@ typedef struct { u8 padding[0x4]; } PACKED_STRUCT TadFooter; +typedef struct { + TadBanner banner; + TadBlockMetaData banner_bmd; + TadHeader header; + TadBlockMetaData header_bmd; + TadFooter footer; + TadBlockMetaData footer_bmd; +} PACKED_STRUCT TadStub; + +u32 VerifyTadStub(TadStub* tad); u32 BuildTadContentTable(void* table, void* header); diff --git a/arm9/source/utils/gameutil.c b/arm9/source/utils/gameutil.c index 71d7e81..6db18ef 100644 --- a/arm9/source/utils/gameutil.c +++ b/arm9/source/utils/gameutil.c @@ -1,6 +1,7 @@ #include "gameutil.h" #include "nandcmac.h" #include "disadiff.h" +#include "fsutil.h" // for TAD verification #include "game.h" #include "nand.h" // so that we can trim NAND images #include "itcm.h" // we need access to part of the OTP @@ -781,6 +782,32 @@ u32 VerifyTieFile(const char* path) { return VerifyTmdFile(path_tmd, false); } +u32 VerifyTadFile(const char* path) { + TadStub tad; + TadHeader* hdr = &(tad.header); + TadFooter* ftr = &(tad.footer); + + // only works for GM9 decrypted TAD files + if (!ShowProgress(0, 0, path)) return 1; + if ((fvx_qread(path, &tad, 0, sizeof(TadStub), NULL) != FR_OK) || + (VerifyTadStub(&tad) != 0)) + return 1; + + // verify contents + u32 content_start = sizeof(TadStub); + for (u32 i = 0; i < TAD_NUM_CONTENT; i++) { + u8 hash[32]; + u32 len = align(hdr->content_size[i], 0x10); + if (!len) continue; // non-existant section + if (!FileGetSha256(path, hash, content_start, len) || + (memcmp(hash, ftr->content_sha256[i], 32) != 0)) + return 1; + content_start += len + sizeof(TadBlockMetaData); + } + + return 0; +} + u32 VerifyFirmFile(const char* path) { char pathstr[32 + 1]; TruncateString(pathstr, path, 32, 8); @@ -921,6 +948,8 @@ u32 VerifyGameFile(const char* path) { return VerifyTmdFile(path, filetype & FLAG_NUSCDN); else if (filetype & GAME_TIE) return VerifyTieFile(path); + else if (filetype & GAME_TAD) + return VerifyTadFile(path); else if (filetype & GAME_BOSS) return VerifyBossFile(path); else if (filetype & SYS_FIRM) @@ -1923,6 +1952,98 @@ u32 InstallFromCiaFile(const char* path_cia, const char* path_dest) { return 0; } +u32 BuildCiaFromTadFile(const char* path_tad, const char* path_dest, bool force_legit) { + TadStub tad; + TadHeader* hdr = &(tad.header); + + // only works for GM9 decrypted TAD files + // fetch the TAD stub + if (!ShowProgress(0, 0, path_tad)) return 1; + if ((fvx_qread(path_tad, &tad, 0, sizeof(TadStub), NULL) != FR_OK) || + (VerifyTadStub(&tad) != 0) || (hdr->content_size[0] < TMD_SIZE_N(1))) + return 1; + + // build the CIA stub + CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub)); + if (!cia) return 1; + TitleMetaData* tmd = &(cia->tmd); + TicketCommon* ticket = &(cia->ticket); + memset(cia, 0, sizeof(CiaStub)); + if ((BuildCiaHeader(&(cia->header), TICKET_COMMON_SIZE) != 0) || + (BuildCiaCert(cia->cert) != 0) || + (fvx_qread(path_tad, tmd, sizeof(TadStub), hdr->content_size[0], NULL) != FR_OK) || + (BuildFakeTicket((Ticket*) ticket, tmd->title_id) != 0) || + (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || + (WriteCiaStub(cia, path_dest) != 0)) { + free(cia); + return 1; + } + + // extract info from TMD + u32 content_count = getbe16(tmd->content_count); + u8* title_id = tmd->title_id; + if (!content_count || (content_count > 8)) { + free(cia); + return 1; + } + + // check for legit TMD + if (force_legit && (ValidateTmdSignature(&(cia->tmd)) != 0)) { + ShowPrompt(false, "ID %016llX\nTMD in TAD is not legit.", getbe64(title_id)); + free(cia); + return 1; + } + + // compare TMD with TAD content table + u32 tad_content_count = 0; + for (u32 i = 1, ci = 0; i < 9; i++) { + u32 size_in_tad = hdr->content_size[i]; + if (size_in_tad) { + u64 size = 0; + if (ci < content_count) { + TmdContentChunk* chunk = &(cia->content_list[ci]); + size = getbe64(chunk->size); + ci++; + } + if (size != size_in_tad) break; + tad_content_count++; + } + } + if (tad_content_count != content_count) { + free(cia); + return 1; + } + + // attempt to find a titlekey + u8 titlekey[16] = { 0xFF }; + FindTitleKey((Ticket*) ticket, title_id); + GetTitleKey(titlekey, (Ticket*)&(cia->ticket)); + + // insert TAD contents into CIA + u64 next_offset = sizeof(TadStub) + align(hdr->content_size[0], 0x10) + sizeof(TadBlockMetaData); + for (u32 i = 0; i < content_count; i++) { + TmdContentChunk* chunk = &(cia->content_list[i]); + u64 size = getbe64(chunk->size); + if (InsertCiaContent(path_dest, path_tad, next_offset, size, + chunk, titlekey, force_legit, false, false) != 0) { + free(cia); + return 1; + } + next_offset += align(size, 0x10) + sizeof(TadBlockMetaData); + } + + // verify TMD / write CIA stub / install system data (take #2) + if ((force_legit && (VerifyTmd(tmd) != 0)) || + (!force_legit && (FixTmdHashes(tmd) != 0)) || + (WriteCiaStub(cia, path_dest) != 0)) { + free(cia); + return 1; + } + + free(cia); + return 0; +} + u32 BuildInstallFromTmdFileBuffered(const char* path_tmd, const char* path_dest, bool force_legit, bool cdn, void* buffer, bool install) { const u8 dlc_tid_high[] = { DLC_TID_HIGH }; CiaStub* cia = (CiaStub*) buffer; @@ -2437,6 +2558,8 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) { ret = BuildInstallFromNcsdFile(path, dest, false); else if ((filetype & GAME_NDS) && (filetype & FLAG_DSIW)) ret = BuildInstallFromNdsFile(path, dest, false); + else if (filetype & GAME_TAD) + ret = BuildCiaFromTadFile(path, dest, force_legit); else ret = 1; if (ret != 0) // try to get rid of the borked file diff --git a/arm9/source/virtual/vgame.c b/arm9/source/virtual/vgame.c index d04aa16..1ccff43 100644 --- a/arm9/source/virtual/vgame.c +++ b/arm9/source/virtual/vgame.c @@ -68,7 +68,7 @@ #define NAME_TAD_TYPES "tmd", "srl.app", "02.unk", \ "03.unk", "04.unk", "05.unk", \ "06.unk", "07.unk", "08.unk", \ - "public.sav", "banner.sav", "11.unk" + "public.sav", "banner.sav", "private.sav" #define NAME_TAD_CONTENT "%016llX.%s" // titleid.type