diff --git a/arm9/source/filesys/filetype.c b/arm9/source/filesys/filetype.c index cf2d135..2d57af8 100644 --- a/arm9/source/filesys/filetype.c +++ b/arm9/source/filesys/filetype.c @@ -98,8 +98,12 @@ u64 IdentifyFileType(const char* path) { } else if (memcmp(header, smdh_magic, sizeof(smdh_magic)) == 0) { return GAME_SMDH; // SMDH file } else if (ValidateTwlHeader((TwlHeader*) data) == 0) { - if (((TwlHeader*)data)->ntr_rom_size <= fsize) + TwlHeader* twl = (TwlHeader*) data; + if (twl->ntr_rom_size <= fsize) { // NDS rom file + if ((twl->unit_code == 0x03) && !twl->twl_rom_region_start) + return GAME_NDS | FLAG_DSIW; // NDS DSiWare rom file return GAME_NDS; // NDS rom file + } } } diff --git a/arm9/source/filesys/filetype.h b/arm9/source/filesys/filetype.h index e5d3109..eea5112 100644 --- a/arm9/source/filesys/filetype.h +++ b/arm9/source/filesys/filetype.h @@ -35,8 +35,9 @@ #define HDR_NAND (1ULL<<30) #define TYPE_BASE 0xFFFFFFFFULL // 32 bit reserved for base types -// #define FLAG_FIRM (1ULL<<58) // <--- for CXIs containing FIRMs -// #define FLAG_GBAVC (1ULL<<59) // <--- for GBAVC CXIs +// #define FLAG_FIRM (1ULL<<57) // <--- for CXIs containing FIRMs +// #define FLAG_GBAVC (1ULL<<58) // <--- for GBAVC CXIs +#define FLAG_DSIW (1ULL<<59) #define FLAG_ENC (1ULL<<60) #define FLAG_CTR (1ULL<<61) #define FLAG_NUSCDN (1ULL<<62) @@ -46,7 +47,7 @@ #define FTYPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|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)) +#define FTYPE_CIABUILD(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_TMD)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW)))) #define FTYPE_CIABUILD_L(tp) (FTYPE_CIABUILD(tp) && (tp&(GAME_TMD))) #define FTYPE_CXIDUMP(tp) (tp&(GAME_TMD)) #define FTYPE_TIKBUILD(tp) (tp&(GAME_TICKET|SYS_TICKDB|BIN_TIKDB)) diff --git a/arm9/source/game/nds.h b/arm9/source/game/nds.h index 3974a0f..69d1516 100644 --- a/arm9/source/game/nds.h +++ b/arm9/source/game/nds.h @@ -90,7 +90,11 @@ typedef struct { u64 secure_area_disable; u32 ntr_rom_size; // in byte u32 header_size; - u8 reserved1[56]; + u32 arm9_param_tbl_offset; + u32 arm7_param_tbl_offset; + u16 ntr_rom_region_end; + u16 twl_rom_region_start; + u8 reserved1[44]; u8 logo[156]; u16 logo_crc; u16 header_crc; diff --git a/arm9/source/game/tmd.c b/arm9/source/game/tmd.c index 4582fb5..d2ddbdc 100644 --- a/arm9/source/game/tmd.c +++ b/arm9/source/game/tmd.c @@ -76,7 +76,7 @@ u32 FixTmdHashes(TitleMetaData* tmd) { return 0; } -u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size) { +u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size, u32 twl_privsave_size) { const u8 sig_type[4] = { TMD_SIG_TYPE }; // safety check: number of contents if (n_contents > TMD_MAX_CONTENTS) return 1; // potential incompatibility here (!) @@ -89,7 +89,8 @@ u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size 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? + for (u32 i = 0; i < 4; i++) tmd->save_size[i] = (save_size >> (i*8)) & 0xFF; // le save size + for (u32 i = 0; i < 4; i++) tmd->twl_privsave_size[i] = (twl_privsave_size >> (i*8)) & 0xFF; // le privsave size tmd->content_count[0] = (u8) ((n_contents >> 8) & 0xFF); tmd->content_count[1] = (u8) (n_contents & 0xFF); memset(tmd->contentinfo_hash, 0xFF, 0x20); // placeholder (hash) diff --git a/arm9/source/game/tmd.h b/arm9/source/game/tmd.h index 04448c9..8d1cc0d 100644 --- a/arm9/source/game/tmd.h +++ b/arm9/source/game/tmd.h @@ -62,5 +62,5 @@ u32 ValidateTmdSignature(TitleMetaData* tmd); u32 VerifyTmd(TitleMetaData* tmd); u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk); u32 FixTmdHashes(TitleMetaData* tmd); -u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size); +u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size, u32 twl_privsave_size); u32 BuildTmdCert(u8* tmdcert); diff --git a/arm9/source/utils/gameutil.c b/arm9/source/utils/gameutil.c index 309aa30..0128116 100644 --- a/arm9/source/utils/gameutil.c +++ b/arm9/source/utils/gameutil.c @@ -1453,7 +1453,7 @@ u32 BuildCiaFromNcchFile(const char* path_ncch, const char* path_cia) { if ((BuildCiaHeader(&(cia->header)) != 0) || (BuildCiaCert(cia->cert) != 0) || (BuildFakeTicket(&(cia->ticket), title_id) != 0) || - (BuildFakeTmd(&(cia->tmd), title_id, 1, save_size)) || + (BuildFakeTmd(&(cia->tmd), title_id, 1, save_size, 0)) || (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || (WriteCiaStub(cia, path_cia) != 0)) { free(cia); @@ -1519,7 +1519,7 @@ u32 BuildCiaFromNcsdFile(const char* path_ncsd, const char* path_cia) { if ((BuildCiaHeader(&(cia->header)) != 0) || (BuildCiaCert(cia->cert) != 0) || (BuildFakeTicket(&(cia->ticket), title_id) != 0) || - (BuildFakeTmd(&(cia->tmd), title_id, content_count, save_size)) || + (BuildFakeTmd(&(cia->tmd), title_id, content_count, save_size, 0)) || (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || (WriteCiaStub(cia, path_cia) != 0)) { free(cia); @@ -1562,6 +1562,69 @@ u32 BuildCiaFromNcsdFile(const char* path_ncsd, const char* path_cia) { return 0; } +u32 BuildCiaFromNdsFile(const char* path_nds, const char* path_cia) { + TwlHeader twl; + u8 title_id[8]; + u32 save_size = 0; + u32 privsave_size = 0; + + // Init progress bar + if (!ShowProgress(0, 0, path_nds)) return 1; + + // load TWL header, get save sizes && title id + if (fvx_qread(path_nds, &twl, 0, sizeof(TwlHeader), NULL) != FR_OK) + return 1; + for (u32 i = 0; i < 8; i++) + title_id[i] = (twl.title_id >> ((7-i)*8)) & 0xFF; + save_size = twl.pubsav_size; + privsave_size = twl.prvsav_size; + + // some basic sanity checks + // see: https://problemkaputt.de/gbatek.htm#dsicartridgeheader + // (gamecart dumps are not allowed) + u8 tidhigh_dsiware[4] = { 0x00, 0x03, 0x00, 0x04 }; + if ((memcmp(title_id, tidhigh_dsiware, 3) != 0) || !title_id[3]) + return 1; + + // convert DSi title ID to 3DS title ID + u8 tidhigh_3ds[4] = { 0x00, 0x04, 0x80, 0x04 }; + memcpy(title_id, tidhigh_3ds, 3); + + // build the CIA stub + CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub)); + if (!cia) return 1; + memset(cia, 0, sizeof(CiaStub)); + if ((BuildCiaHeader(&(cia->header)) != 0) || + (BuildCiaCert(cia->cert) != 0) || + (BuildFakeTicket(&(cia->ticket), title_id) != 0) || + (BuildFakeTmd(&(cia->tmd), title_id, 1, save_size, privsave_size)) || + (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || + (WriteCiaStub(cia, path_cia) != 0)) { + free(cia); + return 1; + } + + // insert NDS content + TmdContentChunk* chunk = cia->content_list; + memset(chunk, 0, sizeof(TmdContentChunk)); // nothing else to do + if (InsertCiaContent(path_cia, path_nds, 0, 0, chunk, NULL, false, false, false) != 0) { + free(cia); + return 1; + } + + // write the CIA stub (take #2) + FindTitleKey((&cia->ticket), title_id); + if ((FixTmdHashes(&(cia->tmd)) != 0) || + (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || + (WriteCiaStub(cia, path_cia) != 0)) { + free(cia); + return 1; + } + + free(cia); + return 0; +} + u32 BuildCiaFromGameFile(const char* path, bool force_legit) { u64 filetype = IdentifyFileType(path); char dest[256]; @@ -1596,6 +1659,8 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) { ret = BuildCiaFromNcchFile(path, dest); else if (filetype & GAME_NCSD) ret = BuildCiaFromNcsdFile(path, dest); + else if ((filetype & GAME_NDS) && (filetype & FLAG_DSIW)) + ret = BuildCiaFromNdsFile(path, dest); else ret = 1; if (ret != 0) // try to get rid of the borked file