diff --git a/.gitignore b/.gitignore index 7be604a..764b93d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,9 @@ *.d *.nds *.tad +*.wad *.dsi +*.srl *.elf *.map *build/ @@ -10,4 +12,28 @@ *.DS_Store *._* +# For the emulator SD +*_nds/ +*private/ +*gm9i/ + +# Test TADs: +# 00030004 +*BrainAgeExpressArtsAndLetters/ +*BrainAgeExpressMath/ +# 00030005 +*DownloadPlay/ +*PictoChat/ +# 0003000f +*DSHashTable/ +*sysmenuVersion/ +*wlanfirm/ +# 00030015 +*Settings/ +# 00030017 +*Launcher/ + +# TWL EVA title check +*tad_check/ + arm9/include/version.h diff --git a/arm9/src/install.c b/arm9/src/install.c index 1835f06..07a7951 100644 --- a/arm9/src/install.c +++ b/arm9/src/install.c @@ -62,7 +62,7 @@ static bool _iqueHack(tDSiHeader* h) { if (!h) return false; - if (h->ndshdr.reserved1[8] == 0x80) + if (h->ndshdr.reserved1[8] == 0x80 && dataTitle == FALSE) { iprintf("iQue Hack..."); @@ -81,7 +81,7 @@ static unsigned long long _getSaveDataSize(tDSiHeader* h) { unsigned long long size = 0; - if (h) + if (h && dataTitle == FALSE) { size += h->public_sav_size; size += h->private_sav_size; @@ -154,7 +154,7 @@ static bool _openMenuSlot() static void _createPublicSav(tDSiHeader* h, char* dataPath) { - if (!h) return; + if (!h || dataTitle == TRUE) return; if (h->public_sav_size > 0) { @@ -199,7 +199,7 @@ static void _createPublicSav(tDSiHeader* h, char* dataPath) static void _createPrivateSav(tDSiHeader* h, char* dataPath) { - if (!h) return; + if (!h || dataTitle == TRUE) return; if (h->private_sav_size > 0) { @@ -244,7 +244,7 @@ static void _createPrivateSav(tDSiHeader* h, char* dataPath) static void _createBannerSav(tDSiHeader* h, char* dataPath) { - if (!h) return; + if (!h || dataTitle == TRUE) return; if (h->appflags & 0x4) { @@ -391,7 +391,9 @@ bool install(char* tadPath, bool systemTitle) h->tid_high == 0x0003000f || // Data titles h->tid_high == 0x00030015 || // System titles h->tid_high == 0x00030017) // Launcher - {} + {} else if (dataTitle == TRUE) { + iprintf("TAD is a data title. "); + } else { iprintf("\x1B[31m"); //red @@ -439,8 +441,13 @@ bool install(char* tadPath, bool systemTitle) goto error; } - //blacklisted titles + /* + // Blacklisted titles + // + // I'm disabling this because if you can reinstall wlanfirm then it's silly to block the camera app... + // The app has shown itself to be safe enough, and soon hopefully will get legit installs for some TADs. { + //tid without region u32 tidLow = (h->tid_low & 0xFFFFFF00); if (!sdnandMode && ( @@ -474,19 +481,24 @@ bool install(char* tadPath, bool systemTitle) } } } + */ //confirmation message { const char system[] = "\x1B[41mWARNING:\x1B[47m This is a system app,\ninstalling it is potentially\nmore risky than regular DSiWare.\n\x1B[33m"; - const char areYouSure[] = "Are you sure you want to install\n"; + const char systemData[] = "\x1B[41mWARNING:\x1B[47m This is a data title,\ninstalling it is extremely\nrisky. You will have a very\n\x1B[31mhigh chance of bricking!\n\x1B[33m"; + const char areYouSure[] = "Are you sure you want to install?\n"; clearScreen(&topScreen); // Top screen breaks after this for some reason. printTadInfo(tadPath); clearScreen(&bottomScreen); char* msg = (char*)malloc(strlen(system) + strlen(areYouSure) + strlen(fpath) + 2); - if (sdnandMode || h->tid_high == 0x00030004) - sprintf(msg, "%s%s?\n", areYouSure, fpath); - else - sprintf(msg, "%s%s%s?\n", system, areYouSure, fpath); + if (sdnandMode || h->tid_high == 0x00030004) { + sprintf(msg, "%s\n", areYouSure); + } else if (dataTitle == TRUE) { + sprintf(msg, "%s%s\n", systemData, areYouSure); + } else { + sprintf(msg, "%s%s\n", system, areYouSure); + } bool choice = choiceBox(msg); free(msg); @@ -518,7 +530,7 @@ bool install(char* tadPath, bool systemTitle) } else if(!sdnandMode && !unlaunchPatches && access(tmdPath, F_OK) != 0) { - if (choicePrint("TMD not found, game cannot be\nplayed without Unlaunch's\nlauncher patches.\nSee wiki for how to get a TMD.\n\nInstall anyway?") == YES) + if (choicePrint("TMD not found, game cannot be\nplayed without Unlaunch's\nlauncher patches.\n\nInstall anyway?") == YES) tmdFound = false; else goto error; @@ -558,20 +570,10 @@ bool install(char* tadPath, bool systemTitle) */ //check that there's space on nand - /* if (!_checkDsiSpace(installSize, (h->tid_high != 0x00030004))) { - if (sdnandMode && choicePrint("Install as system title?")) - { - h->tid_high = 0x00030015; - fixHeader = true; - } - else - { - goto error; - } + goto error; } - */ //check for saves char pubPath[PATH_MAX]; @@ -625,10 +627,15 @@ bool install(char* tadPath, bool systemTitle) char dirPath[32]; mkdir(sdnandMode ? "sd:/title" : "nand:/title", 0777); - sprintf(dirPath, "%s:/title/%08x", sdnandMode ? "sd" : "nand", (unsigned int)h->tid_high); - mkdir(dirPath, 0777); - - sprintf(dirPath, "%s:/title/%08x/%08x", sdnandMode ? "sd" : "nand", (unsigned int)h->tid_high, (unsigned int)h->tid_low); + if (dataTitle == TRUE) { + sprintf(dirPath, "%s:/title/%02x%02x%02x%02x", sdnandMode ? "sd" : "nand", srlTidHigh[0], srlTidHigh[1], srlTidHigh[2], srlTidHigh[3]); + mkdir(dirPath, 0777); + sprintf(dirPath, "%s:/title/%02x%02x%02x%02x/%02x%02x%02x%02x", sdnandMode ? "sd" : "nand", srlTidHigh[0], srlTidHigh[1], srlTidHigh[2], srlTidHigh[3], srlTidLow[0], srlTidLow[1], srlTidLow[2], srlTidLow[3]); + } else { + sprintf(dirPath, "%s:/title/%08x", sdnandMode ? "sd" : "nand", (unsigned int)h->tid_high); + mkdir(dirPath, 0777); + sprintf(dirPath, "%s:/title/%08x/%08x", sdnandMode ? "sd" : "nand", (unsigned int)h->tid_high, (unsigned int)h->tid_low); + } //check if title is free if (_titleIsUsed(h)) @@ -716,7 +723,7 @@ bool install(char* tadPath, bool systemTitle) //pad out banner if it is the last part of the file { - if (h->ndshdr.bannerOffset > (fileSize - 0x23C0)) + if (h->ndshdr.bannerOffset > (fileSize - 0x23C0) && dataTitle == FALSE) { iprintf("Padding banner..."); swiWaitForVBlank(); @@ -831,7 +838,7 @@ bool install(char* tadPath, bool systemTitle) } //ticket folder /ticket/XXXXXXXX - if (tmdFound) + if (tmdFound && dataTitle == FALSE) { //ensure folders exist char ticketPath[32]; @@ -870,7 +877,7 @@ complete: remove("sd:/_nds/TADDeliveryTool/tmp/temp.tmd"); remove("sd:/_nds/TADDeliveryTool/tmp/temp.tik"); remove("sd:/_nds/TADDeliveryTool/tmp/temp.srl.enc"); - remove("sd:/_nds/TADDeliveryTool/tmp/temp.srl"); + //remove("sd:/_nds/TADDeliveryTool/tmp/temp.srl"); rmdir("sd:/_nds/TADDeliveryTool/tmp"); return result; diff --git a/arm9/src/installmenu.c b/arm9/src/installmenu.c index 059d776..c454812 100644 --- a/arm9/src/installmenu.c +++ b/arm9/src/installmenu.c @@ -1,5 +1,6 @@ #include "main.h" #include "rom.h" +#include "tad.h" #include "install.h" #include "menu.h" #include "storage.h" diff --git a/arm9/src/tad.c b/arm9/src/tad.c index 4ebff96..3d6c1c2 100644 --- a/arm9/src/tad.c +++ b/arm9/src/tad.c @@ -22,9 +22,9 @@ /* The common keys for decrypting TADs. - DEV: Used in most TADs. Anything created with the standard maketad will be dev. - PROD: Used in some TADs for factory tools like PRE_IMPORT and IMPORT. Far less common. There are only ~200 publicly released, and they are impossible to make from scratch. - DEBUGGER: Used in TwlSystemUpdater TADs. Created with maketad_updater. + DEV: Used for most TADs. Anything created with the standard maketad will be dev. + PROD: Used for some TADs in factory tools like PRE_IMPORT and IMPORT. They can be created from the NUS or manually from NAND. + DEBUGGER: Used for TwlSystemUpdater TADs. Created with maketad_updater, not really common to see. If for whatever reason you want to make TADs, see here: https://randommeaninglesscharacters.com/dsidev/man/maketad.html @@ -69,10 +69,14 @@ typedef struct { uint32_t metaOffset; } Tad; unsigned char srlCompany[2]; -unsigned char srlTidLow[4]; -unsigned char srlTidHigh[4]; unsigned char srlVerLow[1]; unsigned char srlVerHigh[1]; +unsigned char contentHash[20]; +unsigned char srlTidLow[4]; +unsigned char srlTidHigh[4]; +uint32_t srlTrueSize; +bool dataTitle; + uint32_t swap_endian_u32(uint32_t x) { return (x >> 24) | ((x >> 8) & 0xff00) | ((x << 8) & 0xff0000) | (x << 24); @@ -93,13 +97,13 @@ void decrypt_cbc(const unsigned char* key, const unsigned char* iv, const unsign aes_crypt_cbc(&ctx, AES_DECRYPT, dataSize, iv, encryptedData, decryptedData); } -int openTad(char const* src) { - if (!src) return 1; +char* openTad(char const* src) { + if (!src) return "ERROR"; FILE *file = fopen(src, "rb"); if (file == NULL) { printf("ERROR: fopen()"); - return 1; + return "ERROR"; } // idk how to create folders recursively @@ -140,39 +144,40 @@ int openTad(char const* src) { iprintf("Parsing TAD header...\n"); Tad tad; tad.hdrOffset = 0; + + // 18803 = "Is". This is the standard TAD type. + // Others exist, but they are for Wii boot2 (ib) and netcard (NULL) + if (swap_endian_u16(header.tadType) == 18803) { + //iprintf(" tadType: 'Is'\n"); + } else { + iprintf(" tadType: UNKNOWN\nERROR: unexpected TAD type\n"); + return "ERROR"; + } + // All offsets in the TAD are aligned to 64 bytes. + // TODO: Make sure offset calculation and alignment is correct by comparing that to total size tad.certOffset = round_up(swap_endian_u32(header.hdrSize), 64); tad.crlOffset = round_up(tad.certOffset + swap_endian_u32(header.certSize), 64); tad.ticketOffset = round_up(tad.crlOffset + swap_endian_u32(header.crlSize), 64); tad.tmdOffset = round_up(tad.ticketOffset + swap_endian_u32(header.ticketSize), 64); tad.srlOffset = round_up(tad.tmdOffset + swap_endian_u32(header.tmdSize), 64); tad.metaOffset = round_up(tad.srlOffset + swap_endian_u32(header.srlSize), 64); - // TODO: Make sure offset calculation and alignment is correct by comparing that to total size + /* + Okay sooo this is stupid. Content size defined in header != true content size + + sysmenuVersion has the header content size aligned to 64 bytes + The TMD content size + hash is for an unpadded content + + As such I think that the TMD size should always be the default. + */ + fseek(file, tad.tmdOffset+496, SEEK_SET); + fread(&srlTrueSize, 1, 4, file); + fread(contentHash, 1, 20, file); - // 18803 = "Is". This is the standard TAD type. - if (swap_endian_u16(header.tadType) == 18803) { - //iprintf(" tadType: 'Is'\n"); - } else { - iprintf(" tadType: UNKNOWN\nERROR: unexpected TAD type\n"); - return 1; - } - //iprintf(" tadVersion: %u\n", swap_endian_u16(header.tadVersion)); - //iprintf(" certSize: %lu\n", swap_endian_u32(header.certSize)); - //iprintf(" certOffset: %lu\n", tad.certOffset); - //iprintf(" crlSize: %lu\n", swap_endian_u32(header.crlSize)); - //iprintf(" crlOffset: %lu\n", tad.crlOffset); - //iprintf(" ticketSize: %lu\n", swap_endian_u32(header.ticketSize)); - //iprintf(" ticketOffset: %lu\n", tad.ticketOffset); - //iprintf(" tmdSize: %lu\n", swap_endian_u32(header.tmdSize)); - //iprintf(" tmdOffset: %lu\n", tad.tmdOffset); - //iprintf(" srlSize: %lu\n", swap_endian_u32(header.srlSize)); - //iprintf(" srlOffset: %lu\n", tad.srlOffset); - //iprintf(" metaSize: %lu\n", swap_endian_u32(header.metaSize)); - //iprintf(" metaOffset: %lu\n", tad.metaOffset); - fseek(file, tad.tmdOffset+396, SEEK_SET); fread(srlTidHigh, 1, 4, file); fread(srlTidLow, 1, 4, file); + fclose(file); /* @@ -180,8 +185,7 @@ int openTad(char const* src) { For installing we only need the TMD, ticket, and SRL. - We can skip the cert since that already exists in NAND, and using the TAD's cert could introduce problems like - trying to sign files for dev on a prod console. + We can skip the cert since that already exists in NAND, and the TADs cert might not match the signing on the DSi. */ iprintf("Copying output files...\n"); @@ -193,7 +197,7 @@ int openTad(char const* src) { copyFilePart(src, tad.ticketOffset, swap_endian_u32(header.ticketSize), "sd:/_nds/TADDeliveryTool/tmp/temp.tik"); iprintf(" Copying SRL...\n"); - copyFilePart(src, tad.srlOffset, swap_endian_u32(header.srlSize), "sd:/_nds/TADDeliveryTool/tmp/temp.srl.enc"); + copyFilePart(src, tad.srlOffset, swap_endian_u32(srlTrueSize), "sd:/_nds/TADDeliveryTool/tmp/temp.srl.enc"); /* Get the title key + IV from the ticket. @@ -219,27 +223,34 @@ int openTad(char const* src) { /* This is SRL decryption (AES-CBC). - Common key + title key IV to decrypt title key, title key + content IV to decrypt content + Common key + title key IV to decrypt title key + Title key + content IV to decrypt content We have to try each possible common key until we find one that works. I don't know a better way to do this (nothing in the TAD would specify the key needed) so we'll try keys in the order of which ones are more common: - DEV --> DEBUGGER --> PROD - + DEV --> PROD --> DEBUGGER */ + + if (srlTidHigh[3] == 0x0f) { + dataTitle = TRUE; + } else { + dataTitle = FALSE; + } + bool keyFail; iprintf("Trying dev common key...\n"); - keyFail = decryptTad(devKey, title_key_iv, title_key_enc, content_iv, swap_endian_u32(header.srlSize), srlTidLow); + keyFail = decryptTad(devKey, title_key_iv, title_key_enc, content_iv, swap_endian_u32(srlTrueSize), srlTidLow, dataTitle, contentHash); - if (keyFail == TRUE) { - remove("sd:/_nds/TADDeliveryTool/tmp/temp.srl"); - iprintf("Key fail!\n\nTrying debugger common key...\n"); - keyFail = decryptTad(debuggerKey, title_key_iv, title_key_enc, content_iv, swap_endian_u32(header.srlSize), srlTidLow); - } if (keyFail == TRUE) { remove("sd:/_nds/TADDeliveryTool/tmp/temp.srl"); iprintf("Key fail!\n\nTrying prod common key...\n"); - keyFail = decryptTad(prodKey, title_key_iv, title_key_enc, content_iv, swap_endian_u32(header.srlSize), srlTidLow); + keyFail = decryptTad(prodKey, title_key_iv, title_key_enc, content_iv, swap_endian_u32(srlTrueSize), srlTidLow, dataTitle, contentHash); + } + if (keyFail == TRUE) { + remove("sd:/_nds/TADDeliveryTool/tmp/temp.srl"); + iprintf("Key fail!\n\nTrying debugger common key...\n"); + keyFail = decryptTad(debuggerKey, title_key_iv, title_key_enc, content_iv, swap_endian_u32(srlTrueSize), srlTidLow, dataTitle, contentHash); } if (keyFail == TRUE) { remove("sd:/_nds/TADDeliveryTool/tmp/temp.srl"); @@ -255,7 +266,9 @@ bool decryptTad(unsigned char* commonKey, unsigned char* title_key_enc, unsigned char* content_iv, int srlSize, - unsigned char* srlTidLow) { + unsigned char* srlTidLow, + bool dataTitle, + unsigned char* contentHash) { unsigned char title_key_dec[16]; unsigned char title_key_iv_bak[16]; unsigned char content_iv_bak[16]; @@ -274,23 +287,63 @@ bool decryptTad(unsigned char* commonKey, iprintf(" Decrypting SRL in chunks..\n"); decrypt_cbc(commonKey, title_key_iv, title_key_enc, 16, 16, title_key_dec); int i=0; - bool keyFail = FALSE; - while (i < srlSize && keyFail == FALSE) { - fread(srl_buffer_enc, 1, 16, srlFile_enc); - decrypt_cbc(title_key_dec, content_iv, srl_buffer_enc, 16, 16, srl_buffer_dec); - fwrite(srl_buffer_dec, 1, 16, srlFile_dec); - printProgressBar( ((float)i / (float)srlSize) ); - // Executable SRLs will always have a reverse order TID low at 0x230. - // Use this to check if the current common key works. - if (i == 560) { - if (srl_buffer_dec[3] != srlTidLow[0] || - srl_buffer_dec[2] != srlTidLow[1] || - srl_buffer_dec[1] != srlTidLow[2] || - srl_buffer_dec[0] != srlTidLow[3] ) { + bool keyFail = FALSE; + + /* + Why have two methods of decrypting for data and normal titles? + + Normal titles can be massive (16mb)! Decrpyting and calculating SHA1 multiple times to test keys + is painfully slow. We can quickly test the SRL header for the TID to make sure the key works. + + Data titles can't be tested the same way. Since they're just data, they don't have a header to read. + Luckily they tend to be small (10-300kb) so completely decrypting and checking a SHA1 hash is fast. + */ + + if (dataTitle == TRUE) { + // Copied SHA1 stuff from here. + // https://github.com/DS-Homebrew/SafeNANDManager/blob/master/arm9/source/arm9.c#L96-L152 + swiSHA1context_t ctx; + ctx.sha_block=0; + u8 sha1[20]={0}; + swiSHA1Init(&ctx); + + while (i < srlSize) { + fread(srl_buffer_enc, 1, 16, srlFile_enc); + decrypt_cbc(title_key_dec, content_iv, srl_buffer_enc, 16, 16, srl_buffer_dec); + fwrite(srl_buffer_dec, 1, 16, srlFile_dec); + printProgressBar( ((float)i / (float)srlSize) ); + swiSHA1Update(&ctx, srl_buffer_dec, 16); + i=i+16; + + } + swiSHA1Final(sha1, &ctx); + + // Compare SHA1 hash of file to TMD + for (int i = 0; i < 20; i++) { + if (contentHash[i] != sha1[i]) { keyFail = TRUE; } } - i=i+16; + iprintf("\n"); + + } else { + while (i < srlSize && keyFail == FALSE) { + fread(srl_buffer_enc, 1, 16, srlFile_enc); + decrypt_cbc(title_key_dec, content_iv, srl_buffer_enc, 16, 16, srl_buffer_dec); + fwrite(srl_buffer_dec, 1, 16, srlFile_dec); + printProgressBar( ((float)i / (float)srlSize) ); + // Executable SRLs will always have a reverse order TID low at 0x230. + // Use this to check if the current common key works. + if (i == 560) { + if (srl_buffer_dec[3] != srlTidLow[0] || + srl_buffer_dec[2] != srlTidLow[1] || + srl_buffer_dec[1] != srlTidLow[2] || + srl_buffer_dec[0] != srlTidLow[3] ) { + keyFail = TRUE; + } + } + i=i+16; + } } fclose(srlFile_dec); fclose(srlFile_enc); diff --git a/arm9/src/tad.h b/arm9/src/tad.h index 5592a51..9cbd219 100644 --- a/arm9/src/tad.h +++ b/arm9/src/tad.h @@ -4,12 +4,18 @@ #include #include -int openTad(char const* src); +char* openTad(char const* src); bool decryptTad(unsigned char* commonKey, unsigned char* title_key_iv, unsigned char* title_key_enc, unsigned char* content_iv, int srlSize, - unsigned char* srlTidLow); + unsigned char* srlTidLow, + bool dataTitle, + unsigned char* contentHash); +void printTadInfo(char const* fpath); +extern bool dataTitle; +extern unsigned char srlTidLow[4]; +extern unsigned char srlTidHigh[4]; #endif \ No newline at end of file