mirror of
https://github.com/rvtr/TDT.git
synced 2025-06-19 03:05:43 -04:00
Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
adfc7d6c45 | ||
![]() |
e34911cf05 | ||
![]() |
95261ba5b4 | ||
![]() |
bc01aa1880 | ||
![]() |
9238711b3e | ||
![]() |
37c7af3136 | ||
![]() |
5d981e4317 | ||
![]() |
4eafed393d | ||
![]() |
2ae316443e | ||
![]() |
9794ebc784 | ||
![]() |
58a662b23e | ||
![]() |
f802636230 | ||
![]() |
492cdddc04 | ||
![]() |
0fa509e5fd | ||
![]() |
c95212e38c | ||
![]() |
4fc1756ab7 | ||
![]() |
b5bcd7123c | ||
![]() |
adad8088bd | ||
![]() |
1f88bb7674 | ||
![]() |
afd6364d09 | ||
![]() |
b5892b7905 |
54
.github/workflows/build.yml
vendored
54
.github/workflows/build.yml
vendored
@ -19,16 +19,59 @@ jobs:
|
||||
name: Build with Docker using devkitARM
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup environment
|
||||
run: git config --global safe.directory '*'
|
||||
- name: Build TAD Delivery Tool
|
||||
run: make
|
||||
- name: Publish build to GH Actions
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
path: "*.dsi"
|
||||
name: build
|
||||
path: "TDT.dsi"
|
||||
name: TDT-Nightly-Unsigned
|
||||
|
||||
devsign:
|
||||
runs-on: windows-latest
|
||||
needs: [build]
|
||||
name: Devsign TDT and build a TAD
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: TDT-Nightly-Unsigned
|
||||
path: D:\a\TDT\TDT\TDT-Build
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Clone ntool
|
||||
uses: GuillaumeFalourd/clone-github-repo-action@v2.1
|
||||
with:
|
||||
depth: 1
|
||||
owner: 'xprism1'
|
||||
repository: 'ntool'
|
||||
- name: Devsign TAD Delivery Tool
|
||||
run: |
|
||||
cp TDT-Build\TDT.dsi ntool
|
||||
cd ntool
|
||||
pip install pycryptodome
|
||||
python ntool.py srl_retail2dev TDT.dsi
|
||||
- name: Publish devsigned build to GH Actions
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
path: "ntool/TDT_dev.srl"
|
||||
name: TDT-Nightly-Devsigned
|
||||
- name: Make a devsigned TAD
|
||||
run: |
|
||||
curl https://cdn.randommeaninglesscharacters.com/tools/maketad/maketad.zip -o maketad.zip
|
||||
7z e maketad.zip
|
||||
cp ntool/TDT_dev.srl .
|
||||
.\maketad-20090604.exe TDT_dev.srl -s -o TDT-Nightly.tad
|
||||
- name: Publish devsigned TAD to GH Actions
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
path: "TDT-Nightly.tad"
|
||||
name: TDT-Nightly-TAD
|
||||
|
||||
# Only run this for non-PR jobs.
|
||||
publish_build:
|
||||
@ -42,8 +85,7 @@ jobs:
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
- name:
|
||||
if:
|
||||
- name: Publish Build
|
||||
run: |
|
||||
ID=$(jq --raw-output '.release.id' $GITHUB_EVENT_PATH)
|
||||
|
||||
|
26
.gitignore
vendored
26
.gitignore
vendored
@ -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
|
||||
|
@ -15,6 +15,8 @@ This can modify your internal system NAND! There is *always* a risk of **brickin
|
||||
- [DevkitPro](https://devkitpro.org/): devkitARM and libnds
|
||||
- [Tuxality](https://github.com/Tuxality): [maketmd](https://github.com/Tuxality/maketmd)
|
||||
- [Martin Korth (nocash)](https://problemkaputt.de): [GBATEK](https://problemkaputt.de/gbatek.htm)
|
||||
- [Epicpkmn11](https://github.com/Epicpkmn11): [TMFH](https://github.com/Epicpkmn11/NTM) (what this is a fork of)
|
||||
- [Epicpkmn11](https://github.com/Epicpkmn11): [NTM](https://github.com/Epicpkmn11/NTM) (what this is a fork of)
|
||||
- [JeffRuLz](https://github.com/JeffRuLz): [TMFH](https://github.com/JeffRuLz/TMFH) (what NTM is a fork of)
|
||||
- [DesperateProgrammer](https://github.com/DesperateProgrammer): [DSi Language Patcher](https://github.com/DesperateProgrammer/DSiLanguagePacher) (working NAND writing code)
|
||||
- [NinjaCheetah](https://github.com/NinjaCheetah): Helping fix some TAD decryption issues
|
||||
- [DamiDoop](https://github.com/DamiDoop): Making the very nice icon
|
||||
|
@ -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)
|
||||
{
|
||||
@ -290,7 +290,7 @@ static void _createTicket(tDSiHeader *h, char* ticketPath)
|
||||
{
|
||||
if (!h) return;
|
||||
|
||||
iprintf("Forging ticket...");
|
||||
iprintf("Signing ticket...");
|
||||
swiWaitForVBlank();
|
||||
|
||||
if (!ticketPath)
|
||||
@ -304,15 +304,11 @@ static void _createTicket(tDSiHeader *h, char* ticketPath)
|
||||
const u32 encryptedSize = sizeof(ticket_v0_t) + 0x20;
|
||||
u8 *buffer = (u8*)memalign(4, encryptedSize); //memalign might be needed for encryption, but not sure
|
||||
memset(buffer, 0, encryptedSize);
|
||||
ticket_v0_t *ticket = (ticket_v0_t*)buffer;
|
||||
ticket->sig_type[0] = 0x00;
|
||||
ticket->sig_type[1] = 0x01;
|
||||
ticket->sig_type[2] = 0x00;
|
||||
ticket->sig_type[3] = 0x01;
|
||||
strcpy(ticket->issuer, "Root-CA00000001-XS00000006");
|
||||
PUT_UINT32_BE(h->tid_high, ticket->title_id, 0);
|
||||
PUT_UINT32_BE(h->tid_low, ticket->title_id, 4);
|
||||
memset(ticket->content_access_permissions, 0xFF, 0x20);
|
||||
|
||||
FILE *ticket = fopen("sd:/_nds/TADDeliveryTool/tmp/temp.tik", "rb");
|
||||
fseek(ticket, 0, SEEK_SET);
|
||||
fread(buffer, sizeof(u8), sizeof(ticket_v0_t), ticket);
|
||||
fclose(ticket);
|
||||
|
||||
// Encrypt
|
||||
if (dsi_es_block_crypt(buffer, encryptedSize, ENCRYPT) != 0)
|
||||
@ -391,7 +387,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 +437,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 +477,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 +526,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 +566,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 +623,9 @@ 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);
|
||||
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/%08x/%08x", sdnandMode ? "sd" : "nand", (unsigned int)h->tid_high, (unsigned int)h->tid_low);
|
||||
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]);
|
||||
|
||||
//check if title is free
|
||||
if (_titleIsUsed(h))
|
||||
@ -716,7 +713,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();
|
||||
@ -833,15 +830,16 @@ bool install(char* tadPath, bool systemTitle)
|
||||
//ticket folder /ticket/XXXXXXXX
|
||||
if (tmdFound)
|
||||
{
|
||||
|
||||
//ensure folders exist
|
||||
char ticketPath[32];
|
||||
siprintf(ticketPath, "%s:/ticket", sdnandMode ? "sd" : "nand");
|
||||
mkdir(ticketPath, 0777);
|
||||
siprintf(ticketPath, "%s/%08lx", ticketPath, h->tid_high);
|
||||
siprintf(ticketPath, "%s/%02x%02x%02x%02x", ticketPath, srlTidHigh[0], srlTidHigh[1], srlTidHigh[2], srlTidHigh[3]);
|
||||
mkdir(ticketPath, 0777);
|
||||
|
||||
//actual tik path
|
||||
siprintf(ticketPath, "%s/%08lx.tik", ticketPath, h->tid_low);
|
||||
siprintf(ticketPath, "%s/%02x%02x%02x%02x.tik", ticketPath, srlTidLow[0], srlTidLow[1], srlTidLow[2], srlTidLow[3]);
|
||||
|
||||
if (access(ticketPath, F_OK) != 0 || (choicePrint("Ticket already exists.\nKeep it? (recommended)") == NO && choicePrint("Are you sure?") == YES))
|
||||
_createTicket(h, ticketPath);
|
||||
@ -872,6 +870,7 @@ complete:
|
||||
remove("sd:/_nds/TADDeliveryTool/tmp/temp.srl.enc");
|
||||
remove("sd:/_nds/TADDeliveryTool/tmp/temp.srl");
|
||||
rmdir("sd:/_nds/TADDeliveryTool/tmp");
|
||||
rmdir("sd:/_nds/TADDeliveryTool");
|
||||
|
||||
return result;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
#include "main.h"
|
||||
#include "rom.h"
|
||||
#include "tad.h"
|
||||
#include "install.h"
|
||||
#include "menu.h"
|
||||
#include "storage.h"
|
||||
|
@ -203,7 +203,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
else if (!unlaunchPatches)
|
||||
{
|
||||
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");
|
||||
messageBox("Unlaunch's Launcher Patches are\nnot enabled. You will need theseto boot some TADs.\n\n\x1B[46mhttps://dsi.cfw.guide/\x1B[47m");
|
||||
}
|
||||
}
|
||||
|
||||
|
192
arm9/src/tad.c
192
arm9/src/tad.c
@ -22,11 +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. 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.
|
||||
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
|
||||
@ -71,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);
|
||||
@ -95,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
|
||||
@ -110,10 +112,7 @@ int openTad(char const* src) {
|
||||
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.
|
||||
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"
|
||||
@ -126,7 +125,7 @@ int openTad(char const* src) {
|
||||
Hex | Dec | Meaning
|
||||
-----------+--------+------------
|
||||
0x00000020 | 32 | Header size
|
||||
0x4973 | Is | TAD type
|
||||
0x4973 | 18803 | TAD type (always "Is" in ASCII)
|
||||
0x0000 | 0 | TAD version
|
||||
0x00000E80 | 3712 | Cert size
|
||||
0x00000000 | 0 | Crl size
|
||||
@ -145,56 +144,52 @@ 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
|
||||
//iprintf(" hdrSize: %lu\n", swap_endian_u32(header.hdrSize));
|
||||
//iprintf(" hdrOffset: %lu\n", tad.hdrOffset);
|
||||
/*
|
||||
Okay sooo this is stupid. Content size defined in header != true content size
|
||||
|
||||
// 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;
|
||||
}
|
||||
sysmenuVersion has the header content size aligned to 64 bytes
|
||||
The TMD content size + hash is for an unpadded content
|
||||
|
||||
//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);
|
||||
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);
|
||||
|
||||
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).
|
||||
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");
|
||||
// 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");
|
||||
|
||||
@ -202,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.
|
||||
@ -215,46 +210,47 @@ int openTad(char const* src) {
|
||||
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
|
||||
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
|
||||
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");
|
||||
@ -270,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];
|
||||
@ -290,20 +288,62 @@ bool decryptTad(unsigned char* commonKey,
|
||||
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] ) {
|
||||
|
||||
/*
|
||||
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);
|
||||
@ -354,9 +394,9 @@ void printTadInfo(char const* fpath)
|
||||
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("\nGame Version:\n \x1B[42m%d.%d\x1B[47m (NUS: \x1B[42mv%d\x1B[47m)\n", (int)srlVerHigh[0], (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]);
|
||||
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 ");
|
||||
|
@ -4,12 +4,18 @@
|
||||
#include <nds/ndstypes.h>
|
||||
#include <nds/memory.h>
|
||||
|
||||
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
|
Loading…
Reference in New Issue
Block a user