from Crypto.Cipher import AES
from Crypto.Util import Counter
import struct

rol = lambda val, r_bits, max_bits: \
    (val << r_bits%max_bits) & (2**max_bits-1) | \
    ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))

def to_bytes(num):
    numstr = ''
    tmp = num
    while len(numstr) < 16:
        numstr += chr(tmp & 0xFF)
        tmp >>= 8
    return numstr[::-1]

with open("ncchinfo.bin", 'rb') as n:
    ncch_info = struct.unpack('<LLLL', n.read(0x10))
    if ncch_info[0] == 0xFFFFFFFF:
        for entry in xrange(1, ncch_info[2]):
            entry_counter = struct.unpack('>QQ', n.read(0x10))
            entry_keyy = struct.unpack('>QQ', n.read(0x10))
            entry_sizemb = struct.unpack('<I', n.read(0x4))
            entry_sizebytes = struct.unpack('<I', n.read(0x4))
            entry_9xcrypto = struct.unpack('<I', n.read(0x4))
            entry_7xcrypto = struct.unpack('<I', n.read(0x4))
            entry_titleid = struct.unpack('<Q', n.read(0x8))
            entry_filename = n.read(0x70)
            filename = str(entry_filename).strip('\x00\\/*?:"<>|]')
            if entry_7xcrypto[0] == 0x00: KeyX = 0xB98E95CECA3E4D171F76A94DE934C053 # Key 0x2C - 0x59D0-0x59DF in protected bootrom
            if entry_7xcrypto[0] == 0x01: KeyX = 0xCEE7D8AB30C00DAE850EF5E382AC5AF3 # Key 0x25 - 7.x Key
            if entry_7xcrypto[0] == 0x0A: KeyX = 0x82E9C9BEBFB8BDB875ECC0A07D474374 # Key 0x18 - New3DS Key
            if KeyX > 0:
                CounterIV = long(str("%016X%016X") % (entry_counter[::]), 16)
                KeyY = long(str("%016X%016X") % (entry_keyy[::]), 16)
                Const = 0x1FF9E9AAC5FE0408024591DC5D52768A # 3DS AES Constant
                NormalKey = rol((rol(KeyX, 2, 128) ^ KeyY) + Const, 87, 128)
                ctr = Counter.new(128, initial_value=CounterIV)
                ctrmode = AES.new(to_bytes(NormalKey), AES.MODE_CTR, counter = ctr)
                with open("./xorpad/" + filename, 'w+b') as x:
                        SizeM = (entry_sizebytes[0] / (1024*1024))
                        SizeB = (entry_sizebytes[0] % (1024*1024))
                        if (SizeM > 0):
                            for i in xrange(SizeM):
                                x.write(ctrmode.decrypt(b'\x00' * (1024*1024)))
                                print ("Entry: %02d - Creating - %4d / %4d mb - '%s'\n") % (entry, i, SizeM + 1, filename),
                        if (SizeB > 0):
                            x.write(ctrmode.decrypt(b'\x00' * (SizeB)))
                        print ("Entry: %02d - Creating - %4d / %4d mb - '%s'\n") % (entry, SizeM + 1, SizeM + 1, filename),
            else:
                    print "Entry: %02d - Unsupported Crypto Method - '%s'" % (entry, filename)
    else:
        print "Invalid NCCHINFO.BIN?"