diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 61f3fff..01cdc66 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build NAND Title Manager +name: Build TAD Delivery Tool on: push: @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v1 - name: Setup environment run: git config --global safe.directory '*' - - name: Build NAND Title Manager + - name: Build TAD Delivery Tool run: make - name: Publish build to GH Actions uses: actions/upload-artifact@v2 diff --git a/.gitignore b/.gitignore index 05b2a6d..7be604a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.o *.d *.nds +*.tad *.dsi *.elf *.map diff --git a/Makefile b/Makefile index 14231f6..673154d 100644 --- a/Makefile +++ b/Makefile @@ -13,11 +13,11 @@ export TOPDIR := $(CURDIR) NITRO_FILES := # These set the information text in the nds file -GAME_TITLE := NAND Title Manager -GAME_SUBTITLE1 := JeffRuLz, Pk11 +GAME_TITLE := TAD Delivery Tool +GAME_SUBTITLE1 := JeffRuLz, Pk11, rmc -GAME_CODE := HTMA -GAME_LABEL := NANDTM +GAME_CODE := 4TDA +GAME_LABEL := TAD_DELIVERY include $(DEVKITARM)/ds_rules @@ -49,7 +49,7 @@ checkarm9: #--------------------------------------------------------------------------------- $(TARGET).dsi : $(NITRO_FILES) arm7/$(TARGET).elf arm9/$(TARGET).elf ndstool -c $(TARGET).dsi -7 arm7/$(TARGET).elf -9 arm9/$(TARGET).elf \ - -u "00030004" \ + -u "00030015" \ -g "$(GAME_CODE)" "00" "$(GAME_LABEL)" \ -b $(GAME_ICON) "$(GAME_TITLE);$(GAME_SUBTITLE1)" \ $(_ADDFILES) diff --git a/README.md b/README.md index a3e30b7..1518168 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # NAND Title Manager A basic title manager for the Nintendo DSi supporting both hiyaCFW's SDNAND and SysNAND, modified from JeffRuLz's Title Manager for HiyaCFW. +This fork has been modified to allow installing TADs, the DSi's version of a WAD. These are typically signed and encrypted for development units. This makes them impossible to install on a retail unit. My NTM fork should let dev TADs install on retail, retail TADs install on dev, etc. ## WARNING This can modify your internal system NAND! There is *always* a risk of **bricking**, albeit small, when you modify NAND. Please proceed with caution. Having Unlaunch installed is also strongly recommended as it will likely protect you if something manages to go wrong. @@ -30,4 +31,4 @@ This can modify your internal system NAND! There is *always* a risk of **brickin - [Martin Korth (nocash)](https://problemkaputt.de): [GBATEK](https://problemkaputt.de/gbatek.htm) - [JeffRuLz](https://github.com/JeffRuLz): [TMFH](https://github.com/JeffRuLz/TMFH) (what this is a fork of) - [DesperateProgrammer](https://github.com/DesperateProgrammer): [DSi Language Patcher](https://github.com/DesperateProgrammer/DSiLanguagePacher) (working NAND writing code) -- [rvtr](https://github.com/rvtr): Adding support for installing dev titles +- [rvtr](https://github.com/rvtr): Adding support for installing TADs and dev titles diff --git a/arm9/src/backupmenu.c b/arm9/src/backupmenu.c index d968f5a..6ff6603 100644 --- a/arm9/src/backupmenu.c +++ b/arm9/src/backupmenu.c @@ -157,12 +157,7 @@ static void generateList(Menu* m) } else { - if (strcasecmp(strrchr(ent->d_name, '.'), ".nds") == 0 || - strcasecmp(strrchr(ent->d_name, '.'), ".app") == 0 || - strcasecmp(strrchr(ent->d_name, '.'), ".dsi") == 0 || - strcasecmp(strrchr(ent->d_name, '.'), ".ids") == 0 || - strcasecmp(strrchr(ent->d_name, '.'), ".srl") == 0 || - strcasecmp(strrchr(ent->d_name, '.'), ".cia") == 0) + if (strcasecmp(strrchr(ent->d_name, '.'), ".tad") == 0) { if (count < m->page * ITEMS_PER_PAGE) count += 1; diff --git a/arm9/src/install.c b/arm9/src/install.c index e9a9dfd..1835f06 100644 --- a/arm9/src/install.c +++ b/arm9/src/install.c @@ -8,6 +8,7 @@ #include "nand/ticket0.h" #include "rom.h" #include "storage.h" +#include "tad.h" #include #include #include @@ -351,7 +352,7 @@ static void _createTicket(tDSiHeader *h, char* ticketPath) } } -bool install(char* fpath, bool systemTitle) +bool install(char* tadPath, bool systemTitle) { bool result = false; @@ -364,6 +365,7 @@ bool install(char* fpath, bool systemTitle) //start installation clearScreen(&bottomScreen); + char* fpath = openTad(tadPath); tDSiHeader* h = getRomHeader(fpath); @@ -372,7 +374,7 @@ bool install(char* fpath, bool systemTitle) iprintf("\x1B[31m"); //red iprintf("Error: "); iprintf("\x1B[33m"); //yellow - iprintf("Could not open file.\n"); + iprintf("Could not decrypt TAD.\n"); iprintf("\x1B[47m"); //white goto error; } @@ -385,35 +387,25 @@ bool install(char* fpath, bool systemTitle) //title id must be one of these if (h->tid_high == 0x00030004 || // DSiWare - h->tid_high == 0x00030005 || // "unimportant" system titles - h->tid_high == 0x00030011 || // SRLs in the TWL SDK - h->tid_high == 0x00030015 || // system titles + h->tid_high == 0x00030005 || // "Unimportant" system titles + h->tid_high == 0x0003000f || // Data titles + h->tid_high == 0x00030015 || // System titles h->tid_high == 0x00030017) // Launcher {} else { iprintf("\x1B[31m"); //red - iprintf("Error: "); + iprintf("TID Error: "); iprintf("\x1B[33m"); //yellow - iprintf("This is not a DSi rom.\n"); + iprintf("Could not decrypt TAD.\n%s", fpath); iprintf("\x1B[47m"); //white goto error; } - - //patch dev titles to system titles on SysNAND. - // - //software released through the TWL SDK usually comes as a TAD and an SRL - //things like NandFiler have a TAD with a TID of 0x00030015 and an SRL with 0x00030011 - //the TAD is the installable version, so I'm assuming that 0x00030015 is what the console wants to see on NAND - //this changes the SRL TID accordingly - //not entirely sure why there's even any difference. I think the installed TAD and SRL the same as each other (minus the TID) - if(!sdnandMode && h->tid_high == 0x00030011) - { - h->tid_high = 0x00030015; - fixHeader = true; - } + // I am going to remove patching because it results in bad TMDs that the launcher auto deletes. + // Comment these back in if you really want, but know that 99% of dev apps will not install right with patching. //offer to patch system titles to normal DSiWare on SysNAND + /* if(!sdnandMode && h->tid_high != 0x00030004 && h->tid_high != 0x00030017) //do not allow patching home menus to be normal DSiWare! This will trigger "ERROR! - 0x0000000000000008 HWINFO_SECURE" on prototype launchers. May also cause issues on the prod versions. { if(choiceBox("This is set as a system/dev\ntitle, would you like to patch\nit to be a normal DSiWare?\n\nThis is safer, but invalidates\nRSA checks and may not work.\n\nIf the title is homebrew this isstrongly recommended.") == YES) @@ -422,8 +414,10 @@ bool install(char* fpath, bool systemTitle) fixHeader = true; } } + */ //offer to patch home menus to be system titles on SysNAND + /* if(!sdnandMode && h->tid_high == 0x00030017) { if(choiceBox("This title is a home menu.\nWould you like to patch it to bea system title?\n\nThis is safer and prevents your\nhome menu from being hidden.") == YES) @@ -432,6 +426,7 @@ bool install(char* fpath, bool systemTitle) fixHeader = true; } } + */ //no system titles without Unlaunch if (!unlaunchFound && h->tid_high != 0x00030004) @@ -450,28 +445,17 @@ bool install(char* fpath, bool systemTitle) u32 tidLow = (h->tid_low & 0xFFFFFF00); if (!sdnandMode && ( (h->tid_high == 0x00030005 && ( - tidLow == 0x484e4400 || // DS Download Play - tidLow == 0x484e4500 || // PictoChat tidLow == 0x484e4900 || // Nintendo DSi Camera tidLow == 0x484e4a00 || // Nintendo Zone tidLow == 0x484e4b00 // Nintendo DSi Sound - )) || (h->tid_high == 0x00030011 && ( - tidLow == 0x30535500 || // Twl SystemUpdater - tidLow == 0x34544e00 || // TwlNmenu - tidLow == 0x54574c00 // TWL EVA )) || (h->tid_high == 0x00030015 && ( tidLow == 0x484e4200 || // System Settings - tidLow == 0x484e4600 || // Nintendo DSi Shop - tidLow == 0x34544e00 // TwlNmenu + tidLow == 0x484e4600 // Nintendo DSi Shop )) || (h->tid_high == 0x00030017 && ( tidLow == 0x484e4100 // Launcher ))) && ( (h->tid_low & 0xFF) == region || // Only blacklist console region, or the following programs that have all-region codes: - h->tid_low == 0x484e4541 || // PictoChat - h->tid_low == 0x484e4441 || // Download Play - h->tid_low == 0x30535541 || // Twl SystemUpdater (iirc one version fits in NAND) h->tid_low == 0x34544e41 || // TwlNmenu (blocking due to potential to uninstall system titles) - h->tid_low == 0x54574c41 || // TWL EVA region == 0 //if the region check failed somehow, blacklist everything )) { @@ -495,6 +479,9 @@ bool install(char* fpath, bool systemTitle) { 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"; + 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); @@ -556,6 +543,7 @@ bool install(char* fpath, bool systemTitle) goto error; //system title patch + /* if (systemTitle) { iprintf("System Title Patch..."); @@ -567,8 +555,10 @@ bool install(char* fpath, bool systemTitle) fixHeader = true; } + */ //check that there's space on nand + /* if (!_checkDsiSpace(installSize, (h->tid_high != 0x00030004))) { if (sdnandMode && choicePrint("Install as system title?")) @@ -581,6 +571,7 @@ bool install(char* fpath, bool systemTitle) goto error; } } + */ //check for saves char pubPath[PATH_MAX]; @@ -682,20 +673,29 @@ bool install(char* fpath, bool systemTitle) //create 000000##.app { - iprintf("Creating 000000%02x.app...", appVersion); + // We must get the app name from the TMD (0x1E4-1E8). + // NTM/TMFH did it weirdly before by using a single byte at 0x1E7 called "appVersion"? + // Not sure how that even worked at all. The home menu deleted the incorrectly named apps + // and TwlNmenu showed them as being broken. + // + // This new code should always create valid titles. + FILE *tmd = fopen(tmdPath, "rb"); + unsigned char appName[4]; + fseek(tmd, 484, SEEK_SET); + fread(appName, 1, 4, tmd); + fclose(tmd); + + iprintf("Creating %02x%02x%02x%02x.app...", appName[0], appName[1], appName[2], appName[3]); swiWaitForVBlank(); char appPath[80]; - sprintf(appPath, "%s/000000%02x.app", contentPath, appVersion); + sprintf(appPath, "%s/%02x%02x%02x%02x.app", contentPath, appName[0], appName[1], appName[2], appName[3]); //copy nds file to app { int result = 0; - if (!romIsCia(fpath)) - result = copyFile(fpath, appPath); - else - result = copyFilePart(fpath, 0x3900, fileSize, appPath); + result = copyFile(fpath, appPath); if (result != 0) { @@ -867,5 +867,11 @@ complete: if (!sdnandMode) nandio_lock_writing(); + 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"); + rmdir("sd:/_nds/TADDeliveryTool/tmp"); + return result; } \ No newline at end of file diff --git a/arm9/src/installmenu.c b/arm9/src/installmenu.c index 380a12f..059d776 100644 --- a/arm9/src/installmenu.c +++ b/arm9/src/installmenu.c @@ -186,12 +186,7 @@ static void generateList(Menu* m) } else { - if (strcasecmp(strrchr(ent->d_name, '.'), ".nds") == 0 || - strcasecmp(strrchr(ent->d_name, '.'), ".app") == 0 || - strcasecmp(strrchr(ent->d_name, '.'), ".dsi") == 0 || - strcasecmp(strrchr(ent->d_name, '.'), ".ids") == 0 || - strcasecmp(strrchr(ent->d_name, '.'), ".srl") == 0 || - strcasecmp(strrchr(ent->d_name, '.'), ".cia") == 0) + if (strcasecmp(strrchr(ent->d_name, '.'), ".tad") == 0) { if (count < m->page * ITEMS_PER_PAGE) count += 1; @@ -237,7 +232,7 @@ static void printItem(Menu* m) if (m->items[m->cursor].directory) clearScreen(&topScreen); else - printRomInfo(m->items[m->cursor].value); + printTadInfo(m->items[m->cursor].value); } static int subMenu() diff --git a/arm9/src/main.c b/arm9/src/main.c index 7858c2f..d7eb077 100644 --- a/arm9/src/main.c +++ b/arm9/src/main.c @@ -11,8 +11,6 @@ bool programEnd = false; bool sdnandMode = true; bool unlaunchFound = false; bool unlaunchPatches = false; -bool devkpFound = false; -bool launcherDSiFound = false; bool arm7Exiting = false; bool charging = false; u8 batteryLevel = 0; @@ -25,11 +23,11 @@ enum { MAIN_MENU_MODE, MAIN_MENU_INSTALL, MAIN_MENU_TITLES, - MAIN_MENU_BACKUP, + //MAIN_MENU_BACKUP, MAIN_MENU_TEST, MAIN_MENU_FIX, - MAIN_MENU_DATA_MANAGEMENT, - MAIN_MENU_LANGUAGE_PATCHER, + //MAIN_MENU_DATA_MANAGEMENT, + //MAIN_MENU_LANGUAGE_PATCHER, MAIN_MENU_EXIT }; @@ -57,32 +55,30 @@ static int _mainMenu(int cursor) //top screen clearScreen(&topScreen); - iprintf("\t\tNAND Title Manager\n"); + iprintf("\t\tTAD Delivery Tool\n"); iprintf("\t\t\tmodified from\n"); iprintf("\tTitle Manager for HiyaCFW\n"); + iprintf("\tand Nand Title Manager\n"); iprintf("\nversion %s\n", VERSION); iprintf("\n\n\x1B[41mWARNING:\x1B[47m This tool can write to\nyour internal NAND!\n\nThis always has a risk, albeit\nlow, of \x1B[41mbricking\x1B[47m your system\nand should be done with caution!\n"); iprintf("\n\t \x1B[46mhttps://dsi.cfw.guide\x1B[47m\n"); - iprintf("\n\n \x1B[46mgithub.com/Epicpkmn11/NTM/wiki\x1B[47m\n"); - iprintf("\x1b[22;0HJeff - 2018-2019"); - iprintf("\x1b[23;0HPk11 - 2022-2023"); + iprintf("\n\x1B[46mgithub.com/rvtr/TDT\x1B[47m\n"); + iprintf("\x1b[21;0HJeff - 2018-2019"); + iprintf("\x1b[22;0HPk11 - 2022-2023"); + iprintf("\x1b[23;0Hrmc - 2024-2024"); //menu Menu* m = newMenu(); setMenuHeader(m, "MAIN MENU"); - char modeStr[32], datamanStr[32], launcherStr[32]; + char modeStr[32]; sprintf(modeStr, "Mode: %s", sdnandMode ? "SDNAND" : "\x1B[41mSysNAND\x1B[47m"); - sprintf(datamanStr, "\x1B[%02omEnable Data Management", devkpFound ? 037 : 047); - sprintf(launcherStr, "\x1B[%02omUninstall region mod", launcherDSiFound ? 047 : 037); addMenuItem(m, modeStr, NULL, 0); addMenuItem(m, "Install", NULL, 0); addMenuItem(m, "Titles", NULL, 0); - addMenuItem(m, "Restore", NULL, 0); + //addMenuItem(m, "Restore", NULL, 0); addMenuItem(m, "Test", NULL, 0); addMenuItem(m, "Fix FAT copy mismatch", NULL, 0); - addMenuItem(m, datamanStr, NULL, 0); - addMenuItem(m, launcherStr, NULL, 0); addMenuItem(m, "\x1B[47mExit", NULL, 0); m->cursor = cursor; @@ -168,8 +164,10 @@ int main(int argc, char **argv) char path[64]; sprintf(path, "nand:/title/00030017/%08lx/content/title.tmd", launcherTid); unsigned long long tmdSize = getFileSizePath(path); - if (tmdSize > 520) - unlaunchFound = true; + //if (tmdSize > 520) + // unlaunchFound = true; + unlaunchFound = true; + // unlaunch is always true just for testing. //check if launcher patches are enabled const static u32 tidValues[][2] = { @@ -205,19 +203,13 @@ int main(int argc, char **argv) } else if (!unlaunchPatches) { - messageBox("Unlaunch's Launcher Patches are\nnot enabled. You will need to\nprovide TMD files or reinstall.\n\n\x1B[46mhttps://dsi.cfw.guide/\x1B[47m"); + messageBox("Unlaunch's Launcher Patches are\nnot enabled. You will need these\nto boot any TADs.\n\n\x1B[46mhttps://dsi.cfw.guide/\x1B[47m"); } } - //check for dev.kp (Data Management visible) - devkpFound = (access("sd:/sys/dev.kp", F_OK) == 0); - - //check for launcher.dsi (Language patcher) - launcherDSiFound = (access("nand:/launcher.dsi", F_OK) == 0); - messageBox("\x1B[41mWARNING:\x1B[47m This tool can write to\nyour internal NAND!\n\nThis always has a risk, albeit\nlow, of \x1B[41mbricking\x1B[47m your system\nand should be done with caution!\n\nIf you have not yet done so,\nyou should make a NAND backup."); - messageBox("If you are following a video\nguide, please stop.\n\nVideo guides for console moddingare often outdated or straight\nup incorrect to begin with.\n\nThe recommended guide for\nmodding your DSi is:\n\n\x1B[46mhttps://dsi.cfw.guide/\x1B[47m\n\nFor more information on using\nNTM, see the official wiki:\n\n\x1B[46mhttps://github.com/Epicpkmn11/\n\t\t\t\t\t\t\t\tNTM/wiki\x1B[47m"); + messageBox("If you are following a video\nguide, please stop.\n\nVideo guides for console moddingare often outdated or straight\nup incorrect to begin with.\n\nThe recommended guide for\nmodding your DSi is:\n\n\x1B[46mhttps://dsi.cfw.guide/\x1B[47m\n\nFor more information on using\nTDT, see the official repo:\n\n\x1B[46mhttps://github.com/rvtr/TDT\x1B[47m"); //main menu int cursor = 0; @@ -229,7 +221,6 @@ int main(int argc, char **argv) { case MAIN_MENU_MODE: sdnandMode = !sdnandMode; - devkpFound = (access(sdnandMode ? "sd:/sys/dev.kp" : "nand:/sys/dev.kp", F_OK) == 0); break; case MAIN_MENU_INSTALL: @@ -239,11 +230,11 @@ int main(int argc, char **argv) case MAIN_MENU_TITLES: titleMenu(); break; - + /* case MAIN_MENU_BACKUP: backupMenu(); break; - + */ case MAIN_MENU_TEST: testMenu(); break; @@ -257,35 +248,6 @@ int main(int argc, char **argv) } break; - case MAIN_MENU_DATA_MANAGEMENT: - if (!devkpFound && (choiceBox("Make Data Management visible\nin System Settings?") == YES) && (sdnandMode || nandio_unlock_writing())) - { - //ensure sys folder exists - if(access(sdnandMode ? "sd:/sys" : "nand:/sys", F_OK) != 0) - mkdir(sdnandMode ? "sd:/sys" : "nand:/sys", 0777); - - //create empty file - FILE *file = fopen(sdnandMode ? "sd:/sys/dev.kp" : "nand:/sys/dev.kp", "wb"); - fclose(file); - - if(!sdnandMode) - nandio_lock_writing(); - devkpFound = (access(sdnandMode ? "sd:/sys/dev.kp" : "nand:/sys/dev.kp", F_OK) == 0); - messageBox("Data Management is now visible\nin System Settings.\n"); - } - break; - case MAIN_MENU_LANGUAGE_PATCHER: - if (launcherDSiFound && (choiceBox("Uninstall the language patched\nDSi Menu? (launcher.dsi)") == YES) && nandio_unlock_writing()) - { - //delete launcher.dsi - remove("nand:/launcher.dsi"); - - nandio_lock_writing(); - launcherDSiFound = (access("nand:/launcher.dsi", F_OK) == 0); - messageBox("The language patched DSi Menu\nhas been removed.\n"); - } - break; - case MAIN_MENU_EXIT: programEnd = true; break; @@ -310,4 +272,4 @@ void clearScreen(PrintConsole* screen) { consoleSelect(screen); consoleClear(); -} \ No newline at end of file +} diff --git a/arm9/src/rom.c b/arm9/src/rom.c index fab9ddc..5fb99e3 100644 --- a/arm9/src/rom.c +++ b/arm9/src/rom.c @@ -1,6 +1,7 @@ #include "rom.h" #include "main.h" #include "storage.h" +#include "tad.h" #include #include #include @@ -19,11 +20,7 @@ tDSiHeader* getRomHeader(char const* fpath) if (h) { - if (romIsCia(fpath)) - fseek(f, 0x3900, SEEK_SET); - else - fseek(f, 0, SEEK_SET); - + fseek(f, 0, SEEK_SET); fread(h, sizeof(tDSiHeader), 1, f); } @@ -50,11 +47,7 @@ tNDSBanner* getRomBanner(char const* fpath) if (b) { - if (romIsCia(fpath)) - fseek(f, 0x3900, SEEK_SET); - else - fseek(f, 0, SEEK_SET); - + fseek(f, 0, SEEK_SET); fseek(f, h->ndshdr.bannerOffset, SEEK_CUR); fread(b, sizeof(tNDSBanner), 1, f); } @@ -146,106 +139,99 @@ void printRomInfo(char const* fpath) tDSiHeader* h = getRomHeader(fpath); tNDSBanner* b = getRomBanner(fpath); - if (!isDsiHeader(h)) + if (!b) { - iprintf("Could not read dsi header.\n"); + iprintf("Could not read banner.\n"); } else { - if (!b) + //proper title { - iprintf("Could not read banner.\n"); + char gameTitle[128+1]; + getGameTitle(b, gameTitle, true); + + iprintf("%s\n\n", gameTitle); } - else + + //file size { - //proper title - { - char gameTitle[128+1]; - getGameTitle(b, gameTitle, true); - - iprintf("%s\n\n", gameTitle); - } - - //file size - { - iprintf("Size: "); - unsigned long long romSize = getRomSize(fpath); - printBytes(romSize); - //size in blocks, rounded up - iprintf(" (%lld blocks)\n", ((romSize / BYTES_PER_BLOCK) * BYTES_PER_BLOCK + BYTES_PER_BLOCK) / BYTES_PER_BLOCK); - } - - iprintf("Label: %.12s\n", h->ndshdr.gameTitle); - iprintf("Game Code: %.4s\n", h->ndshdr.gameCode); - - //system type - { - iprintf("Unit Code: "); - - switch (h->ndshdr.unitCode) - { - case 0: iprintf("NDS"); break; - case 2: iprintf("NDS+DSi"); break; - case 3: iprintf("DSi"); break; - default: iprintf("unknown"); - } - - iprintf("\n"); - } - - //application type - { - iprintf("Program Type: "); - - switch (h->ndshdr.reserved1[7]) - { - case 0x3: iprintf("Normal"); break; - case 0xB: iprintf("Sys"); break; - case 0xF: iprintf("Debug/Sys"); break; - default: iprintf("unknown"); - } - - iprintf("\n"); - } - - //DSi title ids - { - if (h->tid_high == 0x00030004 || - h->tid_high == 0x00030005 || - h->tid_high == 0x00030011 || // TID for software in TWL SDK - h->tid_high == 0x00030015 || - h->tid_high == 0x00030017 || - h->tid_high == 0x00030000) - { - iprintf("Title ID: %08x %08x\n", (unsigned int)h->tid_high, (unsigned int)h->tid_low); - } - } - - //print full file path - iprintf("\n%s\n", fpath); - - //print extra files - int extensionPos = strrchr(fpath, '.') - fpath; - char temp[PATH_MAX]; - strcpy(temp, fpath); - strcpy(temp + extensionPos, ".tmd"); - //DSi TMDs are 520, TMDs from NUS are 2,312. If 2,312 we can simply trim it to 520 - int tmdSize = getFileSizePath(temp); - if (access(temp, F_OK) == 0) - printf("\t\x1B[%om%s\n\x1B[47m", (tmdSize == 520 || tmdSize == 2312) ? 047 : 041, strrchr(temp, '/') + 1); - - strcpy(temp + extensionPos, ".pub"); - if (access(temp, F_OK) == 0) - printf("\t\x1B[%om%s\n\x1B[47m", (getFileSizePath(temp) == h->public_sav_size) ? 047 : 041, strrchr(temp, '/') + 1); - - strcpy(temp + extensionPos, ".prv"); - if (access(temp, F_OK) == 0) - printf("\t\x1B[%om%s\n\x1B[47m", (getFileSizePath(temp) == h->private_sav_size) ? 047 : 041, strrchr(temp, '/') + 1); - - strcpy(temp + extensionPos, ".bnr"); - if (access(temp, F_OK) == 0) - printf("\t\x1B[%om%s\n\x1B[47m", (getFileSizePath(temp) == 0x4000) ? 047 : 041, strrchr(temp, '/') + 1); + iprintf("Size: "); + unsigned long long romSize = getRomSize(fpath); + printBytes(romSize); + //size in blocks, rounded up + iprintf(" (%lld blocks)\n", ((romSize / BYTES_PER_BLOCK) * BYTES_PER_BLOCK + BYTES_PER_BLOCK) / BYTES_PER_BLOCK); } + + iprintf("Label: %.12s\n", h->ndshdr.gameTitle); + iprintf("Game Code: %.4s\n", h->ndshdr.gameCode); + + //system type + { + iprintf("Unit Code: "); + + switch (h->ndshdr.unitCode) + { + case 0: iprintf("NDS"); break; + case 2: iprintf("NDS+DSi"); break; + case 3: iprintf("DSi"); break; + default: iprintf("unknown"); + } + + iprintf("\n"); + } + + //application type + { + iprintf("Program Type: "); + + switch (h->ndshdr.reserved1[7]) + { + case 0x3: iprintf("Normal"); break; + case 0xB: iprintf("Sys"); break; + case 0xF: iprintf("Debug/Sys"); break; + default: iprintf("unknown"); + } + + iprintf("\n"); + } + + //DSi title ids + { + if (h->tid_high == 0x00030004 || + h->tid_high == 0x00030005 || + h->tid_high == 0x00030011 || // TID for software in TWL SDK + h->tid_high == 0x00030015 || + h->tid_high == 0x00030017 || + h->tid_high == 0x00030000) + { + iprintf("Title ID: %08x %08x\n", (unsigned int)h->tid_high, (unsigned int)h->tid_low); + } + } + + //print full file path + iprintf("\n%s\n", fpath); + + //print extra files + int extensionPos = strrchr(fpath, '.') - fpath; + char temp[PATH_MAX]; + strcpy(temp, fpath); + strcpy(temp + extensionPos, ".tmd"); + //DSi TMDs are 520, TMDs from NUS are 2,312. If 2,312 we can simply trim it to 520 + int tmdSize = getFileSizePath(temp); + if (access(temp, F_OK) == 0) + printf("\t\x1B[%om%s\n\x1B[47m", (tmdSize == 520 || tmdSize == 2312) ? 047 : 041, strrchr(temp, '/') + 1); + + strcpy(temp + extensionPos, ".pub"); + if (access(temp, F_OK) == 0) + printf("\t\x1B[%om%s\n\x1B[47m", (getFileSizePath(temp) == h->public_sav_size) ? 047 : 041, strrchr(temp, '/') + 1); + + strcpy(temp + extensionPos, ".prv"); + if (access(temp, F_OK) == 0) + printf("\t\x1B[%om%s\n\x1B[47m", (getFileSizePath(temp) == h->private_sav_size) ? 047 : 041, strrchr(temp, '/') + 1); + + strcpy(temp + extensionPos, ".bnr"); + if (access(temp, F_OK) == 0) + printf("\t\x1B[%om%s\n\x1B[47m", (getFileSizePath(temp) == 0x4000) ? 047 : 041, strrchr(temp, '/') + 1); } free(b); @@ -261,36 +247,10 @@ unsigned long long getRomSize(char const* fpath) if (f) { - //cia - if (romIsCia(fpath)) - { - unsigned char bytes[4] = { 0 }; - fseek(f, 0x38D0, SEEK_SET); - fread(bytes, 4, 1, f); - size = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; - } - else - { - fseek(f, 0, SEEK_END); - size = ftell(f); - } + fseek(f, 0, SEEK_END); + size = ftell(f); } fclose(f); return size; -} - -bool romIsCia(char const* fpath) -{ - if (!fpath) return false; - return (strstr(fpath, ".cia") != NULL || strstr(fpath, ".CIA") != NULL); -} - -bool isDsiHeader(tDSiHeader* h) -{ - if (!h) return false; - - u16 crc16 = swiCRC16(0xFFFF, h, 0x15E); - - return h->ndshdr.headerCRC16 == crc16; } \ No newline at end of file diff --git a/arm9/src/rom.h b/arm9/src/rom.h index 9fc07d5..122cdf7 100644 --- a/arm9/src/rom.h +++ b/arm9/src/rom.h @@ -17,7 +17,6 @@ void printRomInfo(char const* fpath); unsigned long long getRomSize(char const* fpath); -bool romIsCia(char const* fpath); -bool isDsiHeader(tDSiHeader* h); +bool romIsSrl(char const* fpath); #endif \ No newline at end of file diff --git a/arm9/src/storage.c b/arm9/src/storage.c index 827c286..7234a1a 100644 --- a/arm9/src/storage.c +++ b/arm9/src/storage.c @@ -440,6 +440,7 @@ int getMenuSlotsFree() const char* dirs[] = { "00030004", "00030005", + "0003000f", "00030015", "00030017" }; diff --git a/arm9/src/tad.c b/arm9/src/tad.c new file mode 100644 index 0000000..4fea52e --- /dev/null +++ b/arm9/src/tad.c @@ -0,0 +1,372 @@ +/* + A lot of this is heavily "inspired" by remaketad.pl in TwlIPL (/tools/bin/remaketad.pl) + That script was made to decrypt + unpack a dev TAD and and rebuild for SystemUpdaters. + + https://github.com/rvtr/TwlIPL/blob/trunk/tools/bin/remaketad.pl +*/ + +#include "tad.h" +#include "storage.h" +#include "rom.h" +#include "main.h" +#include "nand/twltool/dsi.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/* + 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. Really uncommon. I only know of 24 prod TADs + to have ever been found, and 19 of those haven't ever been released (pleeeeeaaaaase release IMPORT soon). + All retail signed and can't be created with any leaked maketads. + DEBUGGER: Used in TwlSystemUpdater TADs. Created with maketad_updater. + + If for whatever reason you want to make TADs, see here: + https://randommeaninglesscharacters.com/dsidev/man/maketad.html +*/ +const unsigned char devKey[] = { + 0xA1, 0x60, 0x4A, 0x6A, 0x71, 0x23, 0xB5, 0x29, + 0xAE, 0x8B, 0xEC, 0x32, 0xC8, 0x16, 0xFC, 0xAA +}; +const unsigned char prodKey[] = { + 0xAF, 0x1B, 0xF5, 0x16, 0xA8, 0x07, 0xD2, 0x1A, + 0xEA, 0x45, 0x98, 0x4F, 0x04, 0x74, 0x28, 0x61 +}; +const unsigned char debuggerKey[] = { + 0xA2, 0xFD, 0xDD, 0xF2 ,0xE4, 0x23, 0x57, 0x4A, + 0xE7, 0xED, 0x86, 0x57, 0xB5, 0xAB, 0x19, 0xD3 +}; +// Content IV be fine as a hardcoded string. Content IV is based off of the content index. (index # with zerobyte padding) +// All TADs I've seen only ever had a single content. It might be a good idea to add something down the line in case a +// weird TAD pops up, but until then this should do. +unsigned char content_iv[] = { + 0x00, 0x00, 0x00, 0x00 ,0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +typedef struct { + uint32_t hdrSize; + uint16_t tadType; + uint16_t tadVersion; + uint32_t certSize; + uint32_t crlSize; + uint32_t ticketSize; + uint32_t tmdSize; + uint32_t srlSize; + uint32_t metaSize; +} Header; +typedef struct { + uint32_t hdrOffset; + uint32_t certOffset; + uint32_t crlOffset; + uint32_t ticketOffset; + uint32_t tmdOffset; + uint32_t srlOffset; + uint32_t metaOffset; +} Tad; +unsigned char srlCompany[2]; +unsigned char srlTidLow[4]; +unsigned char srlTidHigh[4]; +unsigned char srlVerLow[1]; +unsigned char srlVerHigh[1]; + +uint32_t swap_endian_u32(uint32_t x) { + return (x >> 24) | ((x >> 8) & 0xff00) | ((x << 8) & 0xff0000) | (x << 24); +} + +uint16_t swap_endian_u16(uint16_t x) { + return (x >> 8) | (x << 8); +} + +uint32_t round_up( const u32 v, const u32 align ) { + u32 r = ((v + align - 1) / align) * align; + return r; +} + +void decrypt_cbc(const unsigned char* key, const unsigned char* iv, const unsigned char* encryptedData, size_t dataSize, size_t keySize, unsigned char* decryptedData) { + aes_context ctx; + aes_setkey_dec(&ctx, key, 128); + aes_crypt_cbc(&ctx, AES_DECRYPT, dataSize, iv, encryptedData, decryptedData); +} + +int openTad(char const* src) { + if (!src) return 1; + + FILE *file = fopen(src, "rb"); + if (file == NULL) { + printf("ERROR: fopen()"); + return 1; + } + + // idk how to create folders recursively + mkdir("sd:/_nds", 0777); + mkdir("sd:/_nds/TADDeliveryTool", 0777); + mkdir("sd:/_nds/TADDeliveryTool/tmp", 0777); + + /* + Please excuse my terrible copy paste coding. I do not know C and I'm translating from other languages + that I don't know (python, perl). + + Anyways, the code below is determining the file offsets and sizes within the TAD. + This is done using the 32 byte header. + + Example header from "KART_K04.tad" + + 00000020 49730000 00000E80 00000000 + 000002A4 00000208 000DFC00 00000000 + + Breaking it down... + + Hex | Dec | Meaning + -----------+--------+------------ + 0x00000020 | 32 | Header size + 0x4973 | Is | TAD type + 0x0000 | 0 | TAD version + 0x00000E80 | 3712 | Cert size + 0x00000000 | 0 | Crl size + 0x000002A4 | 676 | Ticket size + 0x00000208 | 520 | TMD size + 0x000DFC00 | 916480 | SRL size + 0x00000000 | 0 | Meta size + + Gee, looks awfully like a WAD header, doesn't it? Turns out TADs are just renamed WADs. Not even changed one bit. + There's literally a commit replacing every instance of WAD with TAD in TwlIPL... + https://github.com/rvtr/TwlIPL/commit/baca65d35d5d62d815c88e6374b895d5b0755277 + */ + + Header header; + fread(&header, sizeof(Header), 1, file); + iprintf("Parsing TAD header...\n"); + Tad tad; + tad.hdrOffset = 0; + // All offsets in the TAD are aligned to 64 bytes. + 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 + //iprintf(" hdrSize: %lu\n", swap_endian_u32(header.hdrSize)); + //iprintf(" hdrOffset: %lu\n", tad.hdrOffset); + + // Commenting this block out makes the TAD decrypt improperly. Truly a programming moment. + // 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); + + /* + Copy the contents of the TAD to the SD card. + + For installing we only need the TMD, ticket, and SRL (obviously lol). + + 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. + */ + + iprintf("Copying output files...\n"); + // Sorry for copy pasting, I'll make this a routine later + iprintf(" Copying TMD...\n"); + copyFilePart(src, tad.tmdOffset, swap_endian_u32(header.tmdSize), "sd:/_nds/TADDeliveryTool/tmp/temp.tmd"); + + iprintf(" Copying ticket...\n"); + 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"); + + /* + Get the title key + IV from the ticket. + */ + + iprintf("Decrypting SRL...\n"); + //iprintf(" Finding title key...\n"); + FILE *ticket = fopen("sd:/_nds/TADDeliveryTool/tmp/temp.tik", "rb"); + unsigned char title_key_enc[16]; + fseek(ticket, 447, SEEK_SET); + fread(title_key_enc, 1, 16, ticket); + //iprintf(" Title key found!\n"); + /* for (int i = 0; i < 16; i++) {iprintf("%02X", title_key_enc[i]);} */ + iprintf("\n"); + //iprintf(" Finding title key IV...\n"); + unsigned char title_key_iv[16]; + fseek(ticket, 476, SEEK_SET); + fread(title_key_iv, 1, 8, ticket); + memset(title_key_iv + 8, 0, 8); + //iprintf(" Title key IV found!\n"); + /* for (int i = 0; i < 16; i++) {iprintf("%02X", title_key_iv[i]);} */ + iprintf("\n"); + fclose(ticket); + + /* + This is SRL decryption (AES-CBC). + + 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 + + We check for only zerobytes at 0x15-1B to see if the SRL is decrypted properly. (should always be zerobytes) + + https://problemkaputt.de/gbatek.htm#dscartridgeheader + https://gist.github.com/rvtr/f1069530129b7a57967e3fc4b30866b4#file-decrypt_tad-py-L84 + */ + 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); + + 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); + } + if (keyFail == TRUE) { + remove("sd:/_nds/TADDeliveryTool/tmp/temp.srl"); + iprintf("All keys failed!\n"); + return "ERROR"; + } + return "sd:/_nds/TADDeliveryTool/tmp/temp.srl"; + +} + +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 title_key_dec[16]; + unsigned char title_key_iv_bak[16]; + unsigned char content_iv_bak[16]; + unsigned char srl_buffer_enc[16]; + unsigned char srl_buffer_dec[16]; + + // Backup IVs because PolarSSL will overwrite it + memcpy( title_key_iv_bak, title_key_iv, 16 ); + memcpy( content_iv_bak, content_iv, 16 ); + + FILE *srlFile_enc = fopen("sd:/_nds/TADDeliveryTool/tmp/temp.srl.enc", "rb"); + fseek(srlFile_enc, 0, SEEK_SET); + FILE *srlFile_dec = fopen("sd:/_nds/TADDeliveryTool/tmp/temp.srl", "wb"); + fseek(srlFile_dec, 0, SEEK_SET); + + 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) ); + 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); + // Restore IVs + memcpy( title_key_iv, title_key_iv_bak, 16 ); + memcpy( content_iv, content_iv_bak, 16 ); + return keyFail; +} + +void printTadInfo(char const* fpath) +{ + clearScreen(&topScreen); + if (!fpath) return; + + FILE *file = fopen(fpath, "rb"); + Header header; + fread(&header, sizeof(Header), 1, file); + Tad tad; + tad.hdrOffset = 0; + // All offsets in the TAD are aligned to 64 bytes. + 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); + // Get info from TMD. + fseek(file, tad.tmdOffset+396, SEEK_SET); + fread(srlTidHigh, 1, 4, file); + fread(srlTidLow, 1, 4, file); + fseek(file, tad.tmdOffset+408, SEEK_SET); + fread(srlCompany, 1, 2, file); + fseek(file, tad.tmdOffset+476, SEEK_SET); + fread(srlVerHigh, 1, 1, file); + fread(srlVerLow, 1, 1, file); + + // I am so sorry for this mess. + iprintf("\nSize:\n "); + fseek(file, 0, SEEK_END); + unsigned long long romSize = ftell(file); + iprintf("\x1B[42m"); //green + printBytes(romSize); + iprintf("\x1B[47m"); //white + iprintf(" (\x1B[42m%ld blocks\x1B[47m)\n", ((swap_endian_u32(header.srlSize) / BYTES_PER_BLOCK) * BYTES_PER_BLOCK + BYTES_PER_BLOCK) / BYTES_PER_BLOCK); + + iprintf("Game Code:\n "); + iprintf("\x1B[42m"); //green + for (int i = 0; i < 4; i++) {printf("%c", srlTidLow[i]);} + iprintf("\x1B[47m"); //white + + iprintf("\nGame Version:\n \x1B[42m%d.%d\x1B[47m (NUS: \x1B[42mv%d\x1B[47m)\n", (int)srlVerHigh[0] * 256, (int)srlVerLow[0], ((int)srlVerHigh[0] * 256) + (int)srlVerLow[0]); + + iprintf("Company Code:\n \x1B[42m%c%c\x1B[47m(\x1B[42m%02x%02x\x1B[47m)\n", srlCompany[0], srlCompany[1], srlCompany[0], srlCompany[1]); + + // Print program type based on TID high? + iprintf("Title ID: \n "); + iprintf("\x1B[42m"); //green + for (int i = 0; i < 4; i++) {printf("%02x", srlTidHigh[i]);} + iprintf(" "); + for (int i = 0; i < 4; i++) {printf("%02x", srlTidLow[i]);} + iprintf("\x1B[47m"); //white + + //print full file path + iprintf("\n\n%s\n", fpath); + fclose(file); +} \ No newline at end of file diff --git a/arm9/src/tad.h b/arm9/src/tad.h new file mode 100644 index 0000000..5592a51 --- /dev/null +++ b/arm9/src/tad.h @@ -0,0 +1,15 @@ +#ifndef TAD_H +#define TAD_H + +#include +#include + +int 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); + +#endif \ No newline at end of file diff --git a/arm9/src/titlemenu.c b/arm9/src/titlemenu.c index cb8d18d..1795bde 100644 --- a/arm9/src/titlemenu.c +++ b/arm9/src/titlemenu.c @@ -92,6 +92,7 @@ static void generateList(Menu* m) const char* dirs[] = { "00030004", "00030005", + //"0003000f", "00030015", "00030017" }; @@ -100,17 +101,12 @@ static void generateList(Menu* m) { // 00030004 NULL //nothing blacklisted }, - { // 00030005 - "484e44", // DS Download Play - "484e45", // PictoChat - "484e49", // Nintendo DSi Camera - "484e4a", // Nintendo Zone - "484e4b", // Nintendo DSi Sound + { // 0003000f + "484e43", // WiFi Firmware NULL }, { // 00030015 "484e42", // System Settings - "484e46", // Nintendo DSi Shop NULL }, { // 00030017 @@ -159,8 +155,7 @@ static void generateList(Menu* m) blacklisted = true; // also blacklist specific all-region titles - if ((strcmp("484e4441", ent->d_name) == 0) || // Download Play - (strcmp("484e4541", ent->d_name) == 0) || // PictoChat + if ((strcmp("484e4341", ent->d_name) == 0) || // WiFi Firmware (strcmp("34544e41", ent->d_name) == 0)) // TwlNmenu blacklisted = true; } diff --git a/icon.bmp b/icon.bmp index eba498d..fbdaba4 100644 Binary files a/icon.bmp and b/icon.bmp differ diff --git a/icon.gif b/icon.gif new file mode 100644 index 0000000..548f48e Binary files /dev/null and b/icon.gif differ