from .common import * from .keys import * from .ctr_ncch import NCCHReader, NCCHBuilder from .ctr_cia import CIAReader from .ctr_romfs import RomFSReader media_unit = 0x200 KB = 1024 MB = 1 << 20 GB = 1 << 30 class CCIHdr(Structure): # 0x0 - 0x1FF _pack_ = 1 _fields_ = [ ('sig', c_uint8 * 0x100), ('magic', c_char * 4), ('ncsd_size', c_uint32), ('mediaID', c_uint8 * 8), ('partitions_fs_type', c_uint8 * 8), ('partitions_crypt_type', c_uint8 * 8), ('partitions_offset_size', c_uint8 * 64), ('exh_hash', c_uint8 * 32), ('exh_size', c_uint32), ('sector_0_offset', c_uint32), ('flags', c_uint8 * 8), ('partitionIDs', c_uint8 * 64), ('reserved', c_uint8 * 0x2E), ('crypt_type', c_uint8), ('backup_security_ver', c_uint8) ] def __new__(cls, buf): return cls.from_buffer_copy(buf) def __init__(self, data): pass class CardInfo(Structure): # 0x200 - 0x2FF _pack_ = 1 _fields_ = [ ('writable_addr', c_uint32), ('reserved1', c_uint8 * 3), ('card_flags', c_uint8), ('reserved2', c_uint8 * 0xF8), ] def __new__(cls, buf): return cls.from_buffer_copy(buf) def __init__(self, data): pass class MasteringInfo(Structure): # 0x300 - 0x3FF _pack_ = 1 _fields_ = [ ('media_size_used', c_uint32), ('reserved1', c_uint8 * 0xC), ('title_ver', c_uint16), ('card_rev', c_uint16), ('reserved2', c_uint8 * 0xC), ('cver_titleID', c_uint8 * 8), ('cver_title_ver', c_uint16), ('reserved3', c_uint8 * 0xD6) ] def __new__(cls, buf): return cls.from_buffer_copy(buf) def __init__(self, data): pass class InitialData(Structure): # 0x1000 - 0x11FF _pack_ = 1 _fields_ = [ ('keyY', c_uint8 * 16), ('enc_titlekey', c_uint8 * 16), ('mac', c_uint8 * 16), ('nonce', c_uint8 * 0xC), ('reserved', c_uint8 * 0xC4), ('ncch_hdr_copy', c_uint8 * 0x100) ] def __new__(cls, buf): return cls.from_buffer_copy(buf) def __init__(self, data): pass class CardDeviceInfo(Structure): # 0x1200 - 0x3FFF, retail cards returns 'FF' here when read _pack_ = 1 _fields_ = [ ('card_device_reserved_1', c_uint8 * 0x200), ('titlekey', c_uint8 * 16), ('card_device_reserved_2', c_uint8 * 0x1BF0), ('test_pattern', c_uint8 * 0x1000) ] def __new__(cls, buf): return cls.from_buffer_copy(buf) def __init__(self, data): pass class CCIReader: def __init__(self, file, dev=0): self.file = file self.dev = dev with open(file, 'rb') as f: self.hdr = CCIHdr(f.read(0x200)) self.card_info = CardInfo(f.read(0x100)) self.mastering_info = MasteringInfo(f.read(0x100)) padding = f.read(0xC00) self.initial_data = InitialData(f.read(0x200)) self.card_device_info = CardDeviceInfo(f.read(0x2E00)) # Decrypt InitialData TitleKey if self.card_info.card_flags >> 6 == 3: normal_key = b'\x00' * 16 else: normal_key = CTR.key_scrambler(CTR.KeyX0x3B[0], readbe(bytes(self.initial_data.keyY))) cipher = AES.new(normal_key, AES.MODE_CCM, nonce=bytes(self.initial_data.nonce)) self.title_key = cipher.decrypt(bytes(self.initial_data.enc_titlekey)) # Get component offset and size files = {} files['cci_header.bin'] = { 'offset': 0, 'size': 0x200 } files['card_info.bin'] = { 'offset': 0x200, 'size': 0x100 } files['mastering_info.bin'] = { 'offset': 0x300, 'size': 0x100 } files['initialdata.bin'] = { 'offset': 0x1000, 'size': 0x200 } if bytes(self.card_device_info) != b'\xFF' * 0x2E00: files['card_device_info.bin'] = { 'offset': 0x1200, 'size': 0x2E00 } names = { 0: 'game', 1: 'manual', 2: 'dlp', 3: 'unk3', 4: 'unk4', 5: 'unk5', 6: 'update_n3ds', 7: 'update_o3ds' } for i in range(0, 64, 8): part_off, part_size = readle(self.hdr.partitions_offset_size[i:i + 4]) * media_unit, readle(self.hdr.partitions_offset_size[i + 4:i + 8]) * media_unit if part_off: files[f'content{i // 8}.{names[i // 8]}.ncch'] = { 'offset': part_off, 'size': part_size } self.files = files def extract(self): f = open(self.file, 'rb') for name, info in self.files.items(): f.seek(info['offset']) g = open(name, 'wb') for data in read_chunks(f, info['size']): g.write(data) print(f'Extracted {name}') g.close() f.close() def decrypt(self): # Extract components f = open(self.file, 'rb') for name, info in self.files.items(): f.seek(info['offset']) g = open(name, 'wb') for data in read_chunks(f, info['size']): g.write(data) g.close() f.close() f = open('decrypted.3ds', 'wb') with open('cci_header.bin', 'rb') as g: f.write(g.read()) with open('card_info.bin', 'rb') as g: f.write(g.read()) with open('mastering_info.bin', 'rb') as g: f.write(g.read()) f.write(b'\x00' * 0xC00) with open('initialdata.bin', 'rb') as g: f.write(g.read()) if os.path.isfile('card_device_info.bin'): with open('card_device_info.bin', 'rb') as g: f.write(g.read()) else: f.write(b'\xFF' * 0x2E00) # Use NCCHReader to decrypt NCCHs and write to new file sys.stdout = open(os.devnull, 'w') # Block print statements for name, info in self.files.items(): if name.endswith('ncch'): h = open(name, 'rb') h.seek(0x100) if h.read(4) == b'NCCH': ncch = NCCHReader(name, dev=self.dev) ncch.decrypt() g = open('decrypted.ncch', 'rb') else: g = open(name, 'rb') for data in read_chunks(g, info['size']): f.write(data) g.close() h.close() sys.stdout = sys.__stdout__ curr = f.tell() padding_size = os.path.getsize(self.file) - curr g = open(self.file, 'rb') g.seek(curr) for data in read_chunks(g, padding_size): f.write(data) f.close() g.close() for name, info in self.files.items(): os.remove(name) if os.path.isfile('decrypted.ncch'): os.remove('decrypted.ncch') print(f'Decrypted to decrypted.3ds') def encrypt(self): # Extract components f = open(self.file, 'rb') for name, info in self.files.items(): f.seek(info['offset']) g = open(name, 'wb') for data in read_chunks(f, info['size']): g.write(data) g.close() # Read original partition 0 flags from NCCH header copy f.seek(0x1188) flags = f.read(8) if flags[7] & 0x1: part0_crypto = 'fixed' else: part0_crypto = { 0x00: 'Secure1', 0x01: 'Secure2', 0x0A: 'Secure3', 0x0B: 'Secure4' }[flags[3]] f.close() f = open('encrypted.3ds', 'wb') with open('cci_header.bin', 'rb') as g: f.write(g.read()) with open('card_info.bin', 'rb') as g: f.write(g.read()) with open('mastering_info.bin', 'rb') as g: f.write(g.read()) f.write(b'\x00' * 0xC00) with open('initialdata.bin', 'rb') as g: f.write(g.read()) if os.path.isfile('card_device_info.bin'): with open('card_device_info.bin', 'rb') as g: f.write(g.read()) else: f.write(b'\xFF' * 0x2E00) # Use NCCHReader to extract and NCCHBuilder to re-encrypt NCCHs, then write to new file sys.stdout = open(os.devnull, 'w') # Block print statements for name, info in self.files.items(): if name.endswith('ncch'): h = open(name, 'rb') h.seek(0x100) if h.read(4) == b'NCCH': ncch = NCCHReader(name, dev=self.dev) ncch.extract() ncch_header = 'ncch_header.bin' if os.path.isfile('exheader.bin'): exheader = 'exheader.bin' else: exheader = '' if os.path.isfile('logo.bin'): logo = 'logo.bin' else: logo = '' if os.path.isfile('plain.bin'): plain = 'plain.bin' else: plain = '' if os.path.isfile('exefs.bin'): exefs = 'exefs.bin' else: exefs = '' if os.path.isfile('romfs.bin'): romfs = 'romfs.bin' else: romfs = '' if name.startswith('content0'): NCCHBuilder(ncch_header=ncch_header, exheader=exheader, logo=logo, plain=plain, exefs=exefs, romfs=romfs, crypto=part0_crypto, dev=self.dev) else: # Partitions 1 and up use Secure1, but if partition 0 uses fixed key, then the others will also use fixed key if part0_crypto == 'fixed': NCCHBuilder(ncch_header=ncch_header, exheader=exheader, logo=logo, plain=plain, exefs=exefs, romfs=romfs, crypto='fixed', dev=self.dev) else: NCCHBuilder(ncch_header=ncch_header, exheader=exheader, logo=logo, plain=plain, exefs=exefs, romfs=romfs, crypto='Secure1', dev=self.dev) g = open('new.ncch', 'rb') else: g = open(name, 'rb') for data in read_chunks(g, info['size']): f.write(data) g.close() h.close() for i in os.listdir('.'): if i in ['ncch_header.bin', 'exheader.bin', 'logo.bin', 'plain.bin', 'exefs.bin', 'romfs.bin']: os.remove(i) sys.stdout = sys.__stdout__ curr = f.tell() padding_size = os.path.getsize(self.file) - curr g = open(self.file, 'rb') g.seek(curr) for data in read_chunks(g, padding_size): f.write(data) f.close() g.close() for name, info in self.files.items(): os.remove(name) if os.path.isfile('new.ncch'): os.remove('new.ncch') print(f'Encrypted to encrypted.3ds') def regen_undumpable(self): with open(os.path.join(resources_dir, 'test_pattern.bin'), 'rb') as f: test_pattern = f.read() shutil.copyfile(self.file, 'new.3ds') with open('new.3ds', 'r+b') as f: f.seek(0x1400) f.write(self.title_key) f.seek(0x3000) f.write(test_pattern) print('Wrote to new.3ds') def verify(self): sig_check = [] sig_check.append(('NCSD Header', Crypto.verify_rsa_sha256(CTR.cci_mod[self.dev], bytes(self.hdr)[0x100:], bytes(self.hdr.sig)))) mac_check = [] if self.card_info.card_flags >> 6 == 3: normal_key = b'\x00' * 16 else: normal_key = CTR.key_scrambler(CTR.KeyX0x3B[0], readbe(bytes(self.initial_data.keyY))) cipher = AES.new(normal_key, AES.MODE_CCM, nonce=bytes(self.initial_data.nonce)) try: cipher.decrypt_and_verify(bytes(self.initial_data.enc_titlekey), received_mac_tag=bytes(self.initial_data.mac)) mac_check.append(('TitleKey', True)) except ValueError: mac_check.append(('TitleKey', False)) others = [] if self.hdr.crypt_type & 1: # Bit 0 of hdr.crypt_type is set others.append(('Cardbus crypto', self.hdr.crypt_type >> 1 == self.card_info.card_flags >> 6)) # Check if bits 2-1 of hdr.crypt_type == crypt type in card info section print("Signatures:") for i in sig_check: print(' > {0:15} {1:4}'.format(i[0] + ':', 'GOOD' if i[1] else 'FAIL')) print("MACs:") for i in mac_check: print(' > {0:15} {1:4}'.format(i[0] + ':', 'GOOD' if i[1] else 'FAIL')) if others != []: print("Others:") for i in others: print(' > {0:15} {1:4}'.format(i[0] + ':', 'GOOD' if i[1] else 'FAIL')) def __str__(self): partitions = '' for i in range(0, 64, 8): part_id = hex(readle(self.hdr.partitionIDs[i:i + 8]))[2:].zfill(16) if part_id != '0' * 16: partitions += f'Partition {i // 8}\n' partitions += f' > ID: {part_id}\n' card_device = { 1: 'NOR Flash', 2: 'None', 3: 'BT' } media_platform = { 1: 'CTR' } media_type = { 0: 'Inner device', 1: 'CARD1', 2: 'CARD2', 3: 'Extended device' } card_type = { 0: 'S1', 1: 'S2' } crypt_type = { 0: 'Secure0', 1: 'Secure1', 2: 'Secure2', 3: 'Fixed key' } return ( f'TitleID: {hex(readle(self.hdr.mediaID))[2:].zfill(16)}\n' f'{partitions}' f'Flags:\n' f' > BkupWriteWaitTime: {hex(self.hdr.flags[0])[2:].zfill(2)}\n' f' > BkupSecurityVer: {hex(self.hdr.flags[1] + self.hdr.backup_security_ver)[2:].zfill(2)}\n' f' > Card device: {card_device[self.hdr.flags[3] | self.hdr.flags[7]]}\n' f' > Media platform: {media_platform[self.hdr.flags[4]]}\n' f' > Media type: {media_type[self.hdr.flags[5]]}\n' f'Card info:\n' f' Writable address: 0x{hex(self.card_info.writable_addr)[2:].zfill(8)}\n' f' Card type: {card_type[(self.card_info.card_flags >> 5) & 1]}\n' # Bit 5 f' Cardbus crypto: {crypt_type[self.card_info.card_flags >> 6]}\n' # Bit 7-6 f'Mastering metadata:\n' f' Media size used: 0x{hex(self.mastering_info.media_size_used)[2:].zfill(8)}\n' f' Title version: {self.mastering_info.title_ver}\n' f' Card revision: {self.mastering_info.card_rev}\n' f' CVer TitleID: {hex(readle(self.mastering_info.cver_titleID))[2:].zfill(16)}\n' f' CVer title version: {self.mastering_info.cver_title_ver}\n' f'Initial data:\n' f' KeyY: {hex(readbe(self.initial_data.keyY))[2:].zfill(32)}\n' f' TitleKey: {hex(readbe(self.initial_data.enc_titlekey))[2:].zfill(32)} (decrypted: {hex(readbe(self.title_key))[2:].zfill(32)})\n' f' MAC: {hex(readbe(self.initial_data.mac))[2:].zfill(32)}\n' f'Card device info:\n' f' TitleKey: {hex(readbe(self.card_device_info.titlekey))[2:].zfill(32)}' ) class CCIBuilder: def __init__(self, cci_header='', card_info='', mastering_info='', initialdata='', card_device_info='', ncchs=[], size='', backup_write_wait_time=-1, save_crypto='', card_device='', media_type='', writable_addr='', card_type='', cardbus_crypto='', title_ver=-1, card_rev=-1, regen_sig='', dev=0, gen_card_device_info=0, out='new.3ds'): ''' cci_header, card_info, mastering_info, initialdata, card_device_info: path to respective component (if available) ncchs: list containing filenames of NCCHs, which must each be named 'content[content index]*' (* is wildcard) Following parameters are required if no cci_header, card_info and mastering_info are provided; if files and parameter is supplied, the parameter overrides the file(s) - size: total ROM size; '128MB' or '256MB' or '512MB' or '1GB' or '2GB' or '4GB' or '8GB' (leave blank for auto) - backup_write_wait_time (leave blank for auto) - save_crypto: 'fw1' or 'fw2' or 'fw3' or 'fw6' (leave blank for auto) - card_device: 'NorFlash' or 'None' or 'BT' (leave blank for auto) - media_type: 'InnerDevice' or 'CARD1' or 'CARD2' or 'ExtendedDevice' (leave blank for auto) - writable_addr: in hex (leave blank for auto) - card_type: 'S1' or 'S2' (leave blank for auto) - cardbus_crypto: 'Secure0' or 'Secure1' or 'Secure2' or 'fixed' (leave blank for auto) - title_ver - card_rev regen_sig: '' or 'retail' (test keys) or 'dev' dev: 0 or 1 gen_card_device_info: 0 or 1 (whether to fill in 0x1400-0x140F and 0x3000-0x3FFF) out: path to output file ''' # Get savedata size ncchs.sort() # Sort NCCHs by content index used_size = 0x4000 + sum([os.path.getsize(i) for i in ncchs]) ncch = NCCHReader(ncchs[0], dev=dev) info = ncch.files['exheader.bin'] with open(ncchs[0], 'rb') as f: f.seek(0x100) ncch_hdr = f.read(0x100) f.seek(info['offset']) if ncch.is_decrypted: exheader = f.read(info['size']) else: counter = Counter.new(128, initial_value=readbe(info['counter'])) cipher = AES.new(info['key'], AES.MODE_CTR, counter=counter) exheader = cipher.decrypt(f.read(info['size'])) save_data_size = readle(exheader[0x1C0:0x1C8]) if save_data_size > 0 and save_data_size < 128 * KB: save_data_size = 128 * KB elif save_data_size > 128 * KB and save_data_size < 512 * KB: save_data_size = 512 * KB elif save_data_size > 512 * KB: save_data_size += align(save_data_size, MB) # Checks if backup_write_wait_time != -1: if not (backup_write_wait_time >= 0 and backup_write_wait_time <= 255): raise Exception('Invalid backup write wait time') if card_device == 'NorFlash': if media_type == 'CARD2': raise Exception('NorFlash is invalid for CARD2') elif media_type == 'CARD1' and save_data_size != 128 * KB and save_data_size != 512 * KB: raise Exception('NorFlash can only be used with save-data sizes 128K and 512K') if writable_addr != '': if not all([i in string.hexdigits for i in writable_addr]): raise Exception('Invalid writable address') # Defaults if cci_header == '': if regen_sig == '': regen_sig = 'retail' if size == '': if save_data_size >= MB: data_size = used_size + save_data_size else: data_size = used_size if data_size < 128 * MB: size = '128MB' elif data_size < 256 * MB: size = '256MB' elif data_size < 512 * MB: size = '512MB' elif data_size < 1 * GB: size = '1GB' elif data_size < 2 * GB: size = '2GB' elif data_size < 4 * GB: size = '4GB' elif data_size < 8 * GB: size = '8GB' else: raise Exception('NCCH partitions are too large') if backup_write_wait_time == -1: backup_write_wait_time = 0 if save_crypto == '': save_crypto = 'fw3' if card_device == '': if save_data_size == 0 or save_data_size >= MB: card_device = 'None' else: card_device = 'NorFlash' if media_type == '': if save_data_size >= MB: media_type = 'CARD2' else: media_type = 'CARD1' if card_type == '': card_type = 'S1' if cardbus_crypto == '': if regen_sig == 'dev': cardbus_crypto = 'fixed' else: cardbus_crypto = 'Secure0' # Create (or modify) CCI header if cci_header == '': hdr = CCIHdr(b'\x00' * 0x200) hdr.magic = b'NCSD' else: with open(cci_header, 'rb') as f: hdr = CCIHdr(f.read()) if size != '': hdr.ncsd_size = { '128MB': 128 * MB, '256MB': 256 * MB, '512MB': 512 * MB, '1GB': 1 * GB , '2GB': 2 * GB , '4GB': 4 * GB , '8GB': 8 * GB }[size] // media_unit titleID = bytes(ncch.hdr.titleID) hdr.mediaID = (c_uint8 * sizeof(hdr.mediaID))(*titleID) curr = 0x4000 for i in range(0, 64, 8): for file in ncchs: if file.startswith(f'content{i // 8}'): hdr.partitions_offset_size[i:i + 4] = int32tobytes(curr // media_unit) file_size = os.path.getsize(file) hdr.partitions_offset_size[i + 4:i + 8] = int32tobytes(file_size // media_unit) curr += file_size for i in range(0, 64, 8): for file in ncchs: if file.startswith(f'content{i // 8}'): tmp = NCCHReader(file, dev) hdr.partitionIDs[i:i + 8] = bytes(tmp.hdr.titleID) if backup_write_wait_time != -1: hdr.flags[0] = backup_write_wait_time if save_crypto != '': if save_crypto == 'fw6': hdr.flags[1] = 1 if card_device != '': card_device = { 'NorFlash': 1, 'None': 2, 'BT': 3 }[card_device] if save_crypto == 'fw2': hdr.flags[7] = card_device elif save_crypto == 'fw3' or 'fw6': hdr.flags[3] = card_device hdr.flags[4] = 1 if media_type != '': hdr.flags[5] = { 'InnerDevice': 0, 'CARD1': 1, 'CARD2': 2, 'ExtendedDevice': 3 }[media_type] if regen_sig == 'retail': sig = Crypto.sign_rsa_sha256(CTR.test_mod, CTR.test_priv, bytes(hdr)[0x100:]) hdr.sig = (c_uint8 * sizeof(hdr.sig))(*sig) elif regen_sig == 'dev': sig = Crypto.sign_rsa_sha256(CTR.cci_mod[1], CTR.cci_priv[1], bytes(hdr)[0x100:]) hdr.sig = (c_uint8 * sizeof(hdr.sig))(*sig) # Create (or modify) card info if card_info == '': cinfo = CardInfo(b'\x00' * 0x100) else: with open(card_info, 'rb') as f: cinfo = CardInfo(f.read()) if (hdr.ncsd_size * media_unit / 2 < save_data_size) or (save_data_size > 2047 * MB): raise Exception('Too large savedata size') if card_info == '' and writable_addr == '': # Defaults if media_type == 'CARD1': writable_addr = hex(0xFFFFFFFF * media_unit)[2:] else: # unused_size: values related to the physical implementation of gamecards if media_type == 'CARD1': unused_size = { '128MB': 0x00280000, '256MB': 0x00500000, '512MB': 0x00a00000, '1GB': 0x04680000, '2GB': 0x08c80000, '4GB': 0x11900000, '8GB': 0x23000000 }[size] elif media_type == 'CARD2': unused_size = { '512MB': 0x02380000, '1GB': 0x04680000, '2GB': 0x08c80000, '4GB': 0x11900000, '8GB': 0x23000000 }[size] if unused_size > 0: writable_addr = hdr.ncsd_size * media_unit - unused_size - save_data_size # Nintendo's method of calculating writable region offset else: warnings.warn('Nintendo does not support CARD2 for the current ROM size, aligning save offset after last NCCH') writable_addr = used_size + align(used_size, media_unit) writable_addr = hex(writable_addr)[2:] if writable_addr != '': writable_addr = int(writable_addr, 16) cinfo.writable_addr = writable_addr // media_unit if card_type != '': cinfo.card_flags &= 0b11011111 # Clear flag cinfo.card_flags |= { 'S1': 0, 'S2': 1 }[card_type] << 5 if cardbus_crypto != '': cinfo.card_flags &= 0b00111111 # Clear flag cinfo.card_flags |= { 'Secure0': 0, 'Secure1': 1, 'Secure2': 2, 'fixed': 3 }[cardbus_crypto] << 6 # Create (or modify) mastering info if mastering_info == '': minfo = MasteringInfo(b'\x00' * 0x100) else: with open(mastering_info, 'rb') as f: minfo = MasteringInfo(f.read()) minfo.media_size_used = used_size if title_ver != -1: minfo.title_ver = title_ver if card_rev != -1: minfo.card_rev = card_rev cver_tids = ['000400db00017102', '000400db00017202', '000400db00017302', '000400db00017402', '000400db00017502', '000400db00017602' ] for i in ncchs: if i.startswith('content7'): sys.stdout = open(os.devnull, 'w') # Block print statements upd = NCCHReader(i, dev) upd.extract() upd_romfs = RomFSReader('romfs.bin') for path, info in upd_romfs.files.items(): tid = path.replace('.cia', '') if tid in cver_tids: f = open('romfs.bin', 'rb') f.seek(info['offset']) g = open(path, 'wb') for data in read_chunks(f, info['size']): g.write(data) g.close() f.close() cia = CIAReader(path) titleID_bytes = int64tobytes(int(tid, 16)) minfo.cver_titleID = (c_uint8 * sizeof(minfo.cver_titleID))(*titleID_bytes) minfo.cver_title_ver = cia.tmd.hdr.title_ver os.remove(path) break sys.stdout = sys.__stdout__ for i in os.listdir('.'): if i in ['ncch_header.bin', 'exheader.bin', 'logo.bin', 'plain.bin', 'exefs.bin', 'romfs.bin']: os.remove(i) # Create initialdata if initialdata == '': idata = InitialData(b'\x00' * 0x200) idata.keyY = (c_uint8 * sizeof(idata.keyY))(*titleID) if cinfo.card_flags >> 6 == 3: normal_key = b'\x00' * 16 else: normal_key = CTR.key_scrambler(CTR.KeyX0x3B[0], readbe(bytes(idata.keyY))) title_key = secrets.token_bytes(16) # Random nonce = secrets.token_bytes(0xC) # Random cipher = AES.new(normal_key, AES.MODE_CCM, nonce=nonce) enc_titlekey, mac = cipher.encrypt_and_digest(title_key) idata.enc_titlekey = (c_uint8 * sizeof(idata.enc_titlekey))(*enc_titlekey) idata.mac = (c_uint8 * sizeof(idata.mac))(*mac) idata.nonce = (c_uint8 * sizeof(idata.nonce))(*nonce) idata.ncch_hdr_copy = (c_uint8 * sizeof(idata.ncch_hdr_copy))(*ncch_hdr) else: with open(initialdata, 'rb') as f: idata = InitialData(f.read()) if cinfo.card_flags >> 6 == 3: normal_key = b'\x00' * 16 else: normal_key = CTR.key_scrambler(CTR.KeyX0x3B[0], readbe(bytes(idata.keyY))) cipher = AES.new(normal_key, AES.MODE_CCM, nonce=bytes(idata.nonce)) title_key = cipher.decrypt(bytes(idata.enc_titlekey)) # Create card device info (if necessary) if card_device_info == '': cdinfo = CardDeviceInfo(b'\xFF' * 0x2E00) else: with open(card_device_info, 'rb') as f: cdinfo = CardDeviceInfo(f.read()) if gen_card_device_info: cdinfo.titlekey = (c_uint8 * sizeof(cdinfo.titlekey))(*title_key) with open(os.path.join(resources_dir, 'test_pattern.bin'), 'rb') as f: test_pattern = f.read() cdinfo.test_pattern = (c_uint8 * sizeof(cdinfo.test_pattern))(*test_pattern) # Write CCI with open(out, 'wb') as f: f.write(bytes(hdr)) f.write(bytes(cinfo)) f.write(bytes(minfo)) f.write(b'\x00' * 0xC00) f.write(bytes(idata)) f.write(bytes(cdinfo)) for i in ncchs: g = open(i, 'rb') for data in read_chunks(g, os.path.getsize(i)): f.write(data) g.close() f.write(b'\xFF' * (hdr.ncsd_size * media_unit - used_size)) print(f'Wrote to {out}')