Merge pull request #1 from rvtr/TwlArchiveData

TAD support
This commit is contained in:
Lillian Skinner 2024-04-05 01:54:20 -04:00 committed by GitHub
commit 0ebb1eda5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 560 additions and 258 deletions

View File

@ -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

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
*.o
*.d
*.nds
*.tad
*.dsi
*.elf
*.map

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -8,6 +8,7 @@
#include "nand/ticket0.h"
#include "rom.h"
#include "storage.h"
#include "tad.h"
#include <dirent.h>
#include <errno.h>
#include <sys/stat.h>
@ -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);
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;
}

View File

@ -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()

View File

@ -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)
//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;

View File

@ -1,6 +1,7 @@
#include "rom.h"
#include "main.h"
#include "storage.h"
#include "tad.h"
#include <dirent.h>
#include <nds.h>
#include <malloc.h>
@ -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);
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, h->ndshdr.bannerOffset, SEEK_CUR);
fread(b, sizeof(tNDSBanner), 1, f);
}
@ -146,12 +139,6 @@ void printRomInfo(char const* fpath)
tDSiHeader* h = getRomHeader(fpath);
tNDSBanner* b = getRomBanner(fpath);
if (!isDsiHeader(h))
{
iprintf("Could not read dsi header.\n");
}
else
{
if (!b)
{
iprintf("Could not read banner.\n");
@ -246,7 +233,6 @@ void printRomInfo(char const* fpath)
if (access(temp, F_OK) == 0)
printf("\t\x1B[%om%s\n\x1B[47m", (getFileSizePath(temp) == 0x4000) ? 047 : 041, strrchr(temp, '/') + 1);
}
}
free(b);
free(h);
@ -260,37 +246,11 @@ unsigned long long getRomSize(char const* fpath)
FILE* f = fopen(fpath, "rb");
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);
}
}
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;
}

View File

@ -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

View File

@ -440,6 +440,7 @@ int getMenuSlotsFree()
const char* dirs[] = {
"00030004",
"00030005",
"0003000f",
"00030015",
"00030017"
};

372
arm9/src/tad.c Normal file
View File

@ -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 <nds/ndstypes.h>
#include <malloc.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <dirent.h>
/*
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);
}

15
arm9/src/tad.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef TAD_H
#define TAD_H
#include <nds/ndstypes.h>
#include <nds/memory.h>
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

View File

@ -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;
}

BIN
icon.bmp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icon.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB