mirror of
https://gist.github.com/f1069530129b7a57967e3fc4b30866b4.git
synced 2025-10-31 13:31:11 -04:00
125 lines
5.2 KiB
Python
125 lines
5.2 KiB
Python
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") |