diff --git a/arm9/source/filesys/filetype.c b/arm9/source/filesys/filetype.c index 19a81e2..baa090e 100644 --- a/arm9/source/filesys/filetype.c +++ b/arm9/source/filesys/filetype.c @@ -156,6 +156,9 @@ u64 IdentifyFileType(const char* path) { return BIN_KEYDB; // key database } else if ((sscanf(fname, "slot%02lXKey", &id) == 1) && (strncasecmp(ext, "bin", 4) == 0) && (fsize = 16) && (id < 0x40)) { return BIN_LEGKEY; // legacy key file + } else if ((strncmp((char*) data, CIFINISH_MAGIC, strlen(CIFINISH_MAGIC)) == 0) && + (fsize == CIFINISH_SIZE((void*) data)) && (fsize > sizeof(CifinishHeader))) { + return BIN_CIFNSH; } else if (ValidateText((char*) data, (fsize > 0x200) ? 0x200 : fsize)) { u64 type = 0; if ((fsize < SCRIPT_MAX_SIZE) && (strncasecmp(ext, SCRIPT_EXT, strnlen(SCRIPT_EXT, 16) + 1) == 0)) diff --git a/arm9/source/filesys/filetype.h b/arm9/source/filesys/filetype.h index d60e330..de963af 100644 --- a/arm9/source/filesys/filetype.h +++ b/arm9/source/filesys/filetype.h @@ -27,17 +27,18 @@ #define SYS_DISA (1ULL<<22) #define SYS_AGBSAVE (1ULL<<23) #define SYS_TICKDB (1ULL<<24) -#define BIN_NCCHNFO (1ULL<<25) -#define BIN_TIKDB (1ULL<<26) -#define BIN_KEYDB (1ULL<<27) -#define BIN_LEGKEY (1ULL<<28) -#define TXT_SCRIPT (1ULL<<29) -#define TXT_GENERIC (1ULL<<30) -#define GFX_PNG (1ULL<<31) -#define FONT_PBM (1ULL<<32) -#define FONT_RIFF (1ULL<<33) -#define NOIMG_NAND (1ULL<<34) -#define HDR_NAND (1ULL<<35) +#define BIN_CIFNSH (1ULL<<25) +#define BIN_NCCHNFO (1ULL<<26) +#define BIN_TIKDB (1ULL<<27) +#define BIN_KEYDB (1ULL<<28) +#define BIN_LEGKEY (1ULL<<29) +#define TXT_SCRIPT (1ULL<<30) +#define TXT_GENERIC (1ULL<<31) +#define GFX_PNG (1ULL<<32) +#define FONT_PBM (1ULL<<33) +#define FONT_RIFF (1ULL<<34) +#define NOIMG_NAND (1ULL<<35) +#define HDR_NAND (1ULL<<36) #define TYPE_BASE 0xFFFFFFFFFFULL // 40 bit reserved for base types // #define FLAG_FIRM (1ULL<<58) // <--- for CXIs containing FIRMs @@ -55,6 +56,7 @@ #define FTYPE_CIABUILD_L(tp) (tp&(GAME_TMD|GAME_CDNTMD|GAME_TIE|GAME_TAD)) #define FTYPE_CIAINSTALL(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_CIA|GAME_CDNTMD|GAME_TWLTMD)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW)))) #define FTYPE_TIKINSTALL(tp) (tp&(GAME_TICKET)) +#define FTYPE_CIFINSTALL(tp) (tp&(BIN_CIFNSH)) #define FTYPE_TIKDUMP(tp) (tp&(GAME_TIE)) #define FTYPE_CXIDUMP(tp) (tp&(GAME_TMD|GAME_TIE)) #define FTYPE_UNINSTALL(tp) (tp&(GAME_TIE)) diff --git a/arm9/source/game/cifinish.h b/arm9/source/game/cifinish.h new file mode 100644 index 0000000..d844249 --- /dev/null +++ b/arm9/source/game/cifinish.h @@ -0,0 +1,23 @@ +#pragma once + +#include "common.h" + +#define CIFINISH_MAGIC "CIFINISH" +#define CIFINISH_TITLE_MAGIC "TITLE" +#define CIFINISH_SIZE(c) (sizeof(CifinishHeader) + ((((CifinishHeader*)(c))->n_entries) * sizeof(CifinishTitle))) + +// see: https://github.com/ihaveamac/custom-install/blob/ac0be9d61d7ebef9356df23036dc53e8e862011a/custominstall.py#L163 +typedef struct { + char magic[8]; + u32 version; + u32 n_entries; +} __attribute__((packed, aligned(4))) CifinishHeader; + +typedef struct { + char magic[5]; + u8 padding0; + u8 has_seed; // 1 if it does, otherwise 0 + u8 padding1; + u64 title_id; + u8 seed[16]; +} __attribute__((packed, aligned(4))) CifinishTitle; diff --git a/arm9/source/game/game.h b/arm9/source/game/game.h index 24a534f..24eeedb 100644 --- a/arm9/source/game/game.h +++ b/arm9/source/game/game.h @@ -20,3 +20,4 @@ #include "bdri.h" #include "ticketdb.h" #include "ncchinfo.h" +#include "cifinish.h" diff --git a/arm9/source/game/ticket.c b/arm9/source/game/ticket.c index 00e61b6..24005cd 100644 --- a/arm9/source/game/ticket.c +++ b/arm9/source/game/ticket.c @@ -74,7 +74,7 @@ u32 BuildVariableFakeTicket(Ticket** ticket, u32* ticket_size, const u8* title_i memset(_ticket->ecdsa, 0xFF, 0x3C); _ticket->version = 0x01; memset(_ticket->titlekey, 0xFF, 16); - memcpy(_ticket->title_id, title_id, 8); + if (title_id) memcpy(_ticket->title_id, title_id, 8); _ticket->commonkey_idx = 0x00; // eshop _ticket->audit = 0x01; // whatever diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index 2408f45..256a0c2 100644 --- a/arm9/source/godmode.c +++ b/arm9/source/godmode.c @@ -1107,6 +1107,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan !(drvtype & DRV_TWLNAND) && !(drvtype & DRV_ALIAS) && !(drvtype & DRV_IMAGE); bool tik_installable = (FTYPE_TIKINSTALL(filetype)) && !(drvtype & DRV_IMAGE); bool tik_dumpable = (FTYPE_TIKDUMP(filetype)); + bool cif_installable = (FTYPE_CIFINSTALL(filetype)) && !(drvtype & DRV_IMAGE); bool uninstallable = (FTYPE_UNINSTALL(filetype)); bool cxi_dumpable = (FTYPE_CXIDUMP(filetype)); bool tik_buildable = (FTYPE_TIKBUILD(filetype)) && !in_output_path; @@ -1149,7 +1150,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || trimable || transferable || hsinjectable || restorable || xorpadable || ebackupable || ncsdfixable || extrcodeable || keyinitable || keyinstallable || bootable || scriptable || fontable || viewable || installable || agbexportable || - agbimportable || cia_installable || tik_installable || tik_dumpable; + agbimportable || cia_installable || tik_installable || tik_dumpable || cif_installable; char pathstr[UTF_BUFFER_BYTESIZE(32)]; TruncateString(pathstr, file_path, 32, 8); @@ -1211,6 +1212,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan (filetype & SYS_TICKDB) ? "Ticket.db options..." : (filetype & SYS_DIFF) ? "Mount as DIFF image" : (filetype & SYS_DISA) ? "Mount as DISA image" : + (filetype & BIN_CIFNSH) ? "Install cifinish.bin" : (filetype & BIN_TIKDB) ? "Titlekey options..." : (filetype & BIN_KEYDB) ? "AESkeydb options..." : (filetype & BIN_LEGKEY) ? "Build " KEYDB_NAME : @@ -1359,6 +1361,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan int cia_install = (cia_installable) ? ++n_opt : -1; int tik_install = (tik_installable) ? ++n_opt : -1; int tik_dump = (tik_dumpable) ? ++n_opt : -1; + int cif_install = (cif_installable) ? ++n_opt : -1; int uninstall = (uninstallable) ? ++n_opt : -1; int tik_build_enc = (tik_buildable) ? ++n_opt : -1; int tik_build_dec = (tik_buildable) ? ++n_opt : -1; @@ -1394,6 +1397,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan if (cia_install > 0) optionstr[cia_install-1] = "Install game image"; if (tik_install > 0) optionstr[tik_install-1] = "Install ticket"; if (tik_dump > 0) optionstr[tik_dump-1] = "Dump ticket file"; + if (cif_install > 0) optionstr[cif_install-1] = "Install cifinish.bin"; if (uninstall > 0) optionstr[uninstall-1] = "Uninstall title"; if (tik_build_enc > 0) optionstr[tik_build_enc-1] = "Build " TIKDB_NAME_ENC; if (tik_build_dec > 0) optionstr[tik_build_dec-1] = "Build " TIKDB_NAME_DEC; @@ -1602,8 +1606,11 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan } return 0; } - else if ((user_select == cia_install) || (user_select == tik_install)) { // -> install game/ticket file - bool install_tik = (user_select == tik_install); + else if ((user_select == cia_install) || (user_select == tik_install) || + (user_select == cif_install)) { // -> install game/ticket/cifinish file + u32 (*InstallFunction)(const char*, bool) = + (user_select == cia_install) ? &InstallGameFile : + (user_select == tik_install) ? &InstallTicketFile : &InstallCifinishFile; bool to_emunand = false; if (CheckVirtualDrive("E:")) { optionstr[0] = "Install to SysNAND"; @@ -1626,8 +1633,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan continue; } DrawDirContents(current_dir, (*cursor = i), scroll); - if ((!install_tik && (InstallGameFile(path, to_emunand) == 0)) || - (install_tik && (InstallTicketFile(path, to_emunand) == 0))) + if ((*InstallFunction)(path, to_emunand) == 0) n_success++; else { // on failure: show error, continue char lpathstr[UTF_BUFFER_BYTESIZE(32)]; @@ -1642,8 +1648,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan n_success, n_marked, n_other, n_marked); } else ShowPrompt(false, "%lu/%lu files installed ok", n_success, n_marked); } else { - u32 ret = install_tik ? InstallTicketFile(file_path, to_emunand) : - InstallGameFile(file_path, to_emunand); + u32 ret = (*InstallFunction)(file_path, to_emunand); ShowPrompt(false, "%s\nInstall %s", pathstr, (ret == 0) ? "success" : "failed"); if ((ret != 0) && (filetype & (GAME_NCCH|GAME_NCSD)) && ShowPrompt(true, "%s\nfile failed install.\n \nVerify now?", pathstr)) { diff --git a/arm9/source/utils/gameutil.c b/arm9/source/utils/gameutil.c index 333995d..6ec9c4c 100644 --- a/arm9/source/utils/gameutil.c +++ b/arm9/source/utils/gameutil.c @@ -2891,6 +2891,82 @@ u32 InstallGameFile(const char* path, bool to_emunand) { return ret; } +u32 InstallCifinishFile(const char* path, bool to_emunand) { + u8 ALIGN(4) seeddb_storage[sizeof(SeedInfo) + sizeof(SeedInfoEntry)]; + SeedInfo* seeddb = (SeedInfo*) (void*) seeddb_storage; + TicketCommon ticket; + u32 ret = 0; + + // sanity check / preparations + if (!(IdentifyFileType(path) & BIN_CIFNSH)) return 1; + if (BuildFakeTicket((Ticket*) &ticket, NULL) != 0) return 1; + seeddb->n_entries = 1; + + // check ticket db + char path_ticketdb[32]; + if ((GetInstallDbsPath(path_ticketdb, to_emunand ? "4:" : "1:", "ticket.db") != 0) || !fvx_qsize(path_ticketdb)) { + ShowPrompt(false, "Install error:\nThis system is missing the\nticket.db file."); + return 1; + } + + // check permissions for SysNAND (this includes everything we need) + if (!CheckWritePermissions(to_emunand ? "4:" : "1:")) return 1; + + // store mount path + char path_store[256] = { 0 }; + char* path_bak = NULL; + strncpy(path_store, GetMountPath(), 256); + if (*path_store) path_bak = path_store; + + // load the entire cifinish file into memory + CifinishHeader* cifinish = (CifinishHeader*) malloc(fvx_qsize(path)); + CifinishTitle* cftitle = (CifinishTitle*) (cifinish+1); + if (!cifinish) return 1; + if (fvx_qread(path, cifinish, 0, fvx_qsize(path), NULL) != FR_OK) { + free(cifinish); + return 1; + } + + // process tickets for the entire cifinish file + if (!ShowProgress(0, 0, path)) ret = 1; + if (!InitImgFS(path_ticketdb)) ret = 1; + for (u32 i = 0; !ret && (i < cifinish->n_entries); i++) { + // sanity + if (strncmp(cftitle[i].magic, CIFINISH_TITLE_MAGIC, strlen(CIFINISH_TITLE_MAGIC)) != 0) { + ret = 1; + continue; + } + // check for forbidden title id (the "too large dlc") + if ((TITLE_MAX_CONTENTS <= 1024) && (cftitle[i].title_id == 0x0004008C000CBD00)) { + ShowPrompt(false, "Skipped title:\nTitle with id 0004008C000CBD00\nneeds special compiler flags."); + ShowProgress(0, 0, path); + continue; + } + if (!ShowProgress(i, cifinish->n_entries, path)) ret = 1; + // insert ticket with correct title id + for (u32 t = 0; t < 8; t++) + ticket.title_id[7-t] = (cftitle[i].title_id >> (8*t)) & 0xFF; + AddTicketToDB(PART_PATH, ticket.title_id, (Ticket*) &ticket, false); + } + + // process seeds for the entire cifinish file + if (!ShowProgress(0, 0, path)) ret = 1; + for (u32 i = 0; !ret && (i < cifinish->n_entries); i++) { + if (!ShowProgress(i, cifinish->n_entries, path)) ret = 1; + if ((TITLE_MAX_CONTENTS <= 1024) && (cftitle[i].title_id == 0x0004008C000CBD00)) continue; + if (!cftitle[i].has_seed) continue; + seeddb->entries[0].titleId = cftitle[i].title_id; + memcpy(&(seeddb->entries[0].seed), cftitle[i].seed, sizeof(Seed)); + ret = InstallSeedDbToSystem(seeddb, to_emunand); + } + + // cleanup + InitImgFS(path_bak); + free(cifinish); + + return ret; +} + u32 InstallTicketFile(const char* path, bool to_emunand) { // sanity check if (!(IdentifyFileType(path) & GAME_TICKET)) diff --git a/arm9/source/utils/gameutil.h b/arm9/source/utils/gameutil.h index 89ed407..c001482 100644 --- a/arm9/source/utils/gameutil.h +++ b/arm9/source/utils/gameutil.h @@ -7,6 +7,7 @@ u32 CheckEncryptedGameFile(const char* path); u32 CryptGameFile(const char* path, bool inplace, bool encrypt); u32 BuildCiaFromGameFile(const char* path, bool force_legit); u32 InstallGameFile(const char* path, bool to_emunand); +u32 InstallCifinishFile(const char* path, bool to_emunand); u32 InstallTicketFile(const char* path, bool to_emunand); u32 DumpTicketForGameFile(const char* path, bool force_legit); u32 DumpCxiSrlFromGameFile(const char* path);