-
-
Save rvtr/f1069530129b7a57967e3fc4b30866b4 to your computer and use it in GitHub Desktop.
Tool for decrypting DSi TADs (not the ones listed on dsibrew, that's wrong, the installable ones that are closer to WADs), py2/3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from io import BytesIO | |
from struct import unpack | |
from binascii import hexlify, unhexlify | |
from Crypto.Cipher import AES #pip install pycryptodome | |
import sys | |
dsi_common_key = unhexlify(b"%032X" % 0xAF1BF516A807D21AEA45984F04742861) # DSi common key | |
debugger_common_key = unhexlify(b"%032X" % 0xA2FDDDF2E423574AE7ED8657B5AB19D3) # DSi debugger common key (used for `maketad.updater` TADs) | |
wii_debug_key = unhexlify(b"%032X" % 0xA1604A6A7123B529AE8BEC32C816FCAA) # Wii debug key | |
def align(val): #Tads have 64-byte alignment between sections | |
return val + (64 - (val % 64)) | |
with open(sys.argv[1], "rb") as f: | |
header = unpack(">I2sH6I", f.read(0x20)) | |
if header[1] != b"Is": #Installable? same-ish as Wii WADs | |
raise BaseException("Invalid TAD file") | |
if header[0] != 0x20: #header size | |
raise BaseException("unknown header size %d" % header[0]) | |
if header[2] != 0: #WAD version according to wiibrew | |
raise BaseException("unknown TAD version %d" % header[2]) | |
print("Input Tad file: %s" % sys.argv[1]) | |
print("Header: %08X cert %08X ticket %08X tmd %08X content %08X footer" %\ | |
(header[3], header[5], header[6], header[7], header[8])) | |
with open("title.cert", "wb") as o: | |
o.write(f.read(header[3])) | |
f.seek(align(f.tell())) | |
ticket = BytesIO(f.read(header[5])) #store to parse a bit | |
f.seek(align(f.tell())) | |
with open("title.tik", "wb") as o: | |
o.write(ticket.read()) | |
ticket.seek(0) | |
tmd = BytesIO(f.read(header[6])) #store to parse a bit | |
f.seek(align(f.tell())) | |
with open("title.tmd", "wb") as o: | |
o.write(tmd.read()) | |
tmd.seek(0) | |
tmd.seek(0x18C) | |
title_id = tmd.read(8) | |
tmd.seek(0x1DE) | |
content_count, boot_index = unpack(">2H", tmd.read(4)) | |
print("Title ID: %016X %s" % (unpack(">Q", title_id)[0], title_id[4:].decode("UTF-8"))) | |
ticket.seek(0x1BF) | |
enc_title_key = ticket.read(0x10) | |
print("Encrypted Title Key: %032X" % (int(hexlify(enc_title_key), 16))) | |
if content_count > 1: | |
raise BaseException("Multiple contents not supported, very easy to add") | |
#if you encounter multi-content files, handle tmd table here, and then assume | |
#content is in a row in the content blob in the TAD | |
content = BytesIO(f.read(header[7])) | |
f.seek(align(f.tell())) | |
with open("%08X" % boot_index, "wb") as o: #store the encrypted content | |
o.write(content.read()) | |
content.seek(0) | |
if header[8] != 0: #there is a footer | |
with open("footer.bin", "wb") as o: | |
o.write(f.read(header[8])) | |
f.seek(align(f.tell())) | |
title_id += b"\x00" * 8 #pad to 16 bytes for IV | |
obj = AES.new(dsi_common_key, AES.MODE_CBC, title_id) | |
dsi_dec_title_key = obj.decrypt(enc_title_key) | |
obj = AES.new(debugger_common_key, AES.MODE_CBC, title_id) | |
debugger_dec_title_key = obj.decrypt(enc_title_key) | |
obj = AES.new(wii_debug_key, AES.MODE_CBC, title_id) | |
wii_dec_title_key = obj.decrypt(enc_title_key) | |
#try both keys and check the srl's "reserved" bytes, we decrypt the entire file | |
#because otherwise the CBC blocks get off and the start of the srl gets garbled | |
obj = AES.new(dsi_dec_title_key, AES.MODE_CBC, b"\x00" * 16) | |
decrypted_content = BytesIO(obj.decrypt(content.read())) | |
content.seek(0) | |
decrypted_content.seek(0x15) #reserved bytes | |
if decrypted_content.read(7) == b"\x00" * 7: #this is an srl | |
decrypted_content.seek(0xC) | |
print("DSi Common Key Used") | |
game_code = decrypted_content.read(6).decode("UTF-8") #for export filename | |
print("Game Code: %s" % game_code) | |
decrypted_content.seek(0) | |
print("Output file name: %s.srl" % game_code) | |
with open("%s.srl" % game_code, "wb") as o: | |
o.write(decrypted_content.read()) | |
sys.exit(1) | |
obj = AES.new(debugger_dec_title_key, AES.MODE_CBC, b"\x00" * 16) | |
decrypted_content = BytesIO(obj.decrypt(content.read())) | |
content.seek(0) | |
decrypted_content.seek(0x15) #reserved bytes | |
if decrypted_content.read(7) == b"\x00" * 7: #this is an srl | |
decrypted_content.seek(0xC) | |
print("Debugger Common Key Used") | |
game_code = decrypted_content.read(6).decode("UTF-8") #for export filename | |
print("Game Code: %s" % game_code) | |
decrypted_content.seek(0) | |
print("Output file name: %s.srl" % game_code) | |
with open("%s.srl" % game_code, "wb") as o: | |
o.write(decrypted_content.read()) | |
sys.exit(1) | |
obj = AES.new(wii_dec_title_key, AES.MODE_CBC, b"\x00" * 16) | |
decrypted_content = BytesIO(obj.decrypt(content.read())) | |
content.seek(0) | |
decrypted_content.seek(0x15) #reserved bytes | |
if decrypted_content.read(7) == b"\x00" * 7: #this is an srl | |
decrypted_content.seek(0xC) | |
print("Wii Debug Key Used") | |
game_code = decrypted_content.read(6).decode("UTF-8") #for export filename | |
print("Game Code: %s" % game_code) | |
decrypted_content.seek(0) | |
print("Output file name: %s.srl" % game_code) | |
with open("%s.srl" % game_code, "wb") as o: | |
o.write(decrypted_content.read()) | |
sys.exit(1) | |
raise BaseException("Was not able to decrypt the content, oops") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment