Compare commits

...

21 Commits

Author SHA1 Message Date
Lillian Skinner
adfc7d6c45
Merge pull request #3 from NinjaCheetah/master
feat: Add workflow to automatically build devsigned SRL and TAD
2024-08-16 03:20:28 -04:00
NinjaCheetah
e34911cf05
Update build.yml 2024-08-16 03:07:43 -04:00
NinjaCheetah
95261ba5b4
Update build.yml 2024-08-16 03:01:27 -04:00
NinjaCheetah
bc01aa1880
Update build.yml 2024-08-16 02:58:58 -04:00
NinjaCheetah
9238711b3e
Update build.yml 2024-08-16 02:55:06 -04:00
NinjaCheetah
37c7af3136
Update build.yml 2024-08-16 02:51:54 -04:00
NinjaCheetah
5d981e4317
Update build.yml 2024-08-16 02:48:19 -04:00
NinjaCheetah
4eafed393d
Update build.yml 2024-08-16 02:43:33 -04:00
NinjaCheetah
2ae316443e
Update build.yml 2024-08-16 02:30:50 -04:00
NinjaCheetah
9794ebc784
Update build.yml 2024-08-16 02:24:28 -04:00
NinjaCheetah
58a662b23e
Update build.yml 2024-08-16 02:17:51 -04:00
NinjaCheetah
f802636230
Update build.yml 2024-08-16 02:15:05 -04:00
NinjaCheetah
492cdddc04
Update build.yml 2024-08-16 02:12:16 -04:00
NinjaCheetah
0fa509e5fd
Update build.yml 2024-08-16 02:09:11 -04:00
NinjaCheetah
c95212e38c
Update build.yml 2024-08-16 02:00:27 -04:00
Lillian Skinner
4fc1756ab7 Use legit tickets instead of forging 2024-07-02 04:24:52 -04:00
Lillian Skinner
b5bcd7123c Add support for data titles
Still need to do ticket verification for sysmenuVersion to work. Settings can't boot otherwise.
2024-07-02 03:00:03 -04:00
Lillian Skinner
adad8088bd
Fixed version display 2024-04-14 14:17:12 -04:00
Lillian Skinner
1f88bb7674
Merge pull request #2 from rolfiee/master
fix typo
2024-04-05 10:33:07 -04:00
Rolfie
afd6364d09
fix typo 2024-04-05 16:30:20 +02:00
Lillian Skinner
b5892b7905
Credits 2024-04-05 10:27:38 -04:00
8 changed files with 249 additions and 133 deletions

View File

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

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
#include "main.h"
#include "rom.h"
#include "tad.h"
#include "install.h"
#include "menu.h"
#include "storage.h"

View File

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

View File

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

View File

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