mirror of
https://gist.github.com/f1069530129b7a57967e3fc4b30866b4.git
synced 2025-06-18 14:45:31 -04:00
This commit is contained in:
commit
99495f22f0
113
decrypt_tad.py
Normal file
113
decrypt_tad.py
Normal file
@ -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")
|
Loading…
Reference in New Issue
Block a user