commit 99495f22f022415961b2e374f7b9bd9629640432 Author: NWPlayer123 Date: Thu Sep 10 19:55:27 2020 -0600 diff --git a/decrypt_tad.py b/decrypt_tad.py new file mode 100644 index 0000000..c200c2d --- /dev/null +++ b/decrypt_tad.py @@ -0,0 +1,113 @@ +from io import BytesIO +from struct import unpack +from binascii import hexlify, unhexlify +from Crypto.Cipher import AES #pip install pycryptodome +import sys + +def generate_key(string): + key_hex = int(string, 16) + key_add = int(b"1" * 32, 16) + combine = key_hex + key_add + return unhexlify(b"%032X" % combine) + +dsi_common_key = generate_key(b"9E0AE40596F6C109D934873DF3631750") +wii_debug_key = generate_key(b"904F39596012A4189D7ADB21B705EB99") + +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(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(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")