from lib.common import * from lib.keys import NTR, TWL from lib.ctr_cia import CIAReader, CIABuilder from lib.ctr_cci import CCIReader, CCIBuilder from lib.ctr_ncch import NCCHReader, NCCHBuilder from lib.ctr_exefs import ExeFSReader, ExeFSBuilder from lib.ctr_romfs import RomFSReader, RomFSBuilder from lib.ctr_crr import crrReader from lib.ctr_tmd import TMDReader, TMDBuilder from lib.ctr_tik import tikReader, tikBuilder from lib.ctr_cdn import CDNReader, CDNBuilder from lib.ctr_cnt import cntReader from lib.ntr_twl_srl import SRLReader, get_rsa_key_idx def srl_retail2dev(path, out=''): name = os.path.splitext(os.path.basename(path))[0] if out == '': out = f'{name}_dev.srl' srl = SRLReader(path, dev=0) shutil.copyfile(path, 'tmp.nds') if srl.media == 'Game card' and srl.secure_area_status == 'decrypted': # Encrypt NTR secure area for decrypted game card SRLs with open(path, 'rb') as f: f.seek(0x4000) secure_area = f.read(2048) secure_area_enc = srl.encrypt_secure_area(secure_area, NTR.blowfish_key) with open('tmp.nds', 'r+b') as f: f.seek(0x4000) f.write(secure_area_enc) if srl.modcrypted: # Decrypt modcrypt regions and re-encrypt with dev key srl.decrypt_modcrypt() f = open('decrypted.nds', 'rb') key = bytes(srl.hdr)[:16][::-1] for i in srl.modcrypt: g = open('tmp.nds', 'r+b') f.seek(i['offset']) g.seek(i['offset']) counter = Counter.new(128, initial_value=readbe(i['counter'])) cipher = AES.new(key, AES.MODE_CTR, counter=counter) for data in read_chunks(f, i['size']): g.write(TWL.aes_ctr(cipher, data)) g.close() f.close() os.remove('decrypted.nds') if srl.hdr.unit_code == 2 or srl.hdr.unit_code == 3 or (srl.hdr.unit_code == 0 and srl.hdr_ext.flags != 0): # Set DeveloperApp flag srl.hdr_ext.flags |= (1 << 7) with open('tmp.nds', 'r+b') as f: f.seek(0x1BF) f.write(int8tobytes(srl.hdr_ext.flags)) if not (srl.hdr.unit_code == 0 and readbe(srl.hdr_ext.sig) == 0): # Re-generate header signature idx = get_rsa_key_idx(srl.hdr, srl.hdr_ext) n = TWL.rsa_key_mod[idx] d = TWL.rsa_key_priv[idx] f = open('tmp.nds', 'rb') sha1_calculated = Crypto.sha1(f, 0xE00) f.close() sha1_padded = b'\x00\x01' + b'\xff' * 105 + b'\x00' + sha1_calculated enc = pow(readbe(sha1_padded), readbe(d[1]), readbe(n[1])).to_bytes(0x80, 'big') with open('tmp.nds', 'r+b') as f: f.seek(0xF80) f.write(enc) if srl.media == 'Game card': # Re-generate undumpable area i.e. KeyTables for game card SRLs srl = SRLReader('tmp.nds', dev=1) srl.regen_undumpable() os.remove('tmp.nds') shutil.move('new.nds', out) else: shutil.move('tmp.nds', out) def cia_dev2retail(path, out=''): name = os.path.splitext(os.path.basename(path))[0] if out == '': out = f'{name}_retail.cia' cia = CIAReader(path, dev=1) cia.extract() cf = list(cia.files.keys()) cf.remove('cia_header.bin') cf.remove('cert.bin') cf.remove('tik') cf.remove('tmd') if 'meta.bin' in cf: meta = 1 cf.remove('meta.bin') else: meta = 0 for i in cf: if i.endswith('.ncch'): ncch = NCCHReader(i, dev=1) ncch.extract() # NOTE: no need to resign CRR since CRR body sig will pass (all that matters) 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 = '' os.remove(i) NCCHBuilder(ncch_header=ncch_header, exheader=exheader, logo=logo, plain=plain, exefs=exefs, romfs=romfs, crypto='Secure1', regen_sig='retail', dev=0, out=i) for j in [ncch_header, exheader, logo, plain, exefs, romfs]: if j != '': os.remove(j) tmd = TMDReader('tmd', dev=1) TMDBuilder(content_files=cf, content_files_dev=0, titleID=tmd.titleID, title_ver=tmd.hdr.title_ver, save_data_size=tmd.hdr.save_data_size, priv_save_data_size=tmd.hdr.priv_save_data_size, twl_flag=tmd.hdr.twl_flag, crypt=0, regen_sig='retail') os.remove('tmd') tik = tikReader('tik', dev=1) tikBuilder(tik='tik', titlekey=hex(readbe(tik.titlekey))[2:].zfill(32), regen_sig='retail') # Use original (decrypted) titlekey os.remove('tik') CIABuilder(content_files=cf, tik='tik_new', tmd='tmd_new', meta=meta, dev=0, out=out) for i in cf + ['tmd_new', 'tik_new', 'cia_header.bin', 'cert.bin', 'meta.bin']: if os.path.isfile(i): os.remove(i) def cia_retail2dev(path, out=''): name = os.path.splitext(os.path.basename(path))[0] if out == '': out = f'{name}_dev.cia' cia = CIAReader(path, dev=0) cia.extract() cf = list(cia.files.keys()) cf.remove('cia_header.bin') cf.remove('cert.bin') cf.remove('tik') cf.remove('tmd') if 'meta.bin' in cf: meta = 1 cf.remove('meta.bin') else: meta = 0 for i in cf: if i.endswith('.ncch'): ncch = NCCHReader(i, dev=0) 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' romfs_rdr = RomFSReader('romfs.bin') if '.crr/static.crr' in romfs_rdr.files.keys() or '.crr\\static.crr' in romfs_rdr.files.keys(): romfs_rdr.extract() crr = crrReader('romfs/.crr/static.crr') crr.regen_sig(dev=1) os.remove('romfs.bin') RomFSBuilder(romfs_dir='romfs/', out='romfs.bin') shutil.rmtree('romfs/') else: romfs = '' os.remove(i) NCCHBuilder(ncch_header=ncch_header, exheader=exheader, logo=logo, plain=plain, exefs=exefs, romfs=romfs, crypto='Secure1', regen_sig='dev', dev=1, out=i) for j in [ncch_header, exheader, logo, plain, exefs, romfs]: if j != '': os.remove(j) tmd = TMDReader('tmd', dev=0) TMDBuilder(content_files=cf, content_files_dev=1, titleID=tmd.titleID, title_ver=tmd.hdr.title_ver, save_data_size=tmd.hdr.save_data_size, priv_save_data_size=tmd.hdr.priv_save_data_size, twl_flag=tmd.hdr.twl_flag, crypt=1, regen_sig='dev') os.remove('tmd') tik = tikReader('tik', dev=0) tikBuilder(tik='tik', titlekey=hex(readbe(tik.titlekey))[2:].zfill(32), regen_sig='dev') # Use original (decrypted) titlekey os.remove('tik') CIABuilder(content_files=cf, tik='tik_new', tmd='tmd_new', meta=meta, dev=1, out=out) for i in cf + ['tmd_new', 'tik_new', 'cia_header.bin', 'cert.bin', 'meta.bin']: if os.path.isfile(i): os.remove(i) def cci_dev2retail(path, out=''): name = os.path.splitext(os.path.basename(path))[0] if out == '': out = f'{name}_retail.3ds' cci = CCIReader(path, dev=1) cci.extract() parts = list(cci.files.keys()) parts.remove('cci_header.bin') parts.remove('card_info.bin') parts.remove('mastering_info.bin') parts.remove('initialdata.bin') if 'card_device_info.bin' in parts: parts.remove('card_device_info.bin') for i in parts: if i.endswith('.ncch'): ncch = NCCHReader(i, dev=1) ncch.extract() # NOTE: no need to resign CRR since CRR body sig will pass (all that matters) 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 = '' os.remove(i) NCCHBuilder(ncch_header=ncch_header, exheader=exheader, logo=logo, plain=plain, exefs=exefs, romfs=romfs, crypto='Secure1', regen_sig='retail', dev=0, out=i) for j in [ncch_header, exheader, logo, plain, exefs, romfs]: if j != '': os.remove(j) CCIBuilder(cci_header='cci_header.bin', card_info='card_info.bin', mastering_info='mastering_info.bin', initialdata='', card_device_info='', ncchs=parts, cardbus_crypto='Secure0', regen_sig='retail', dev=0, gen_card_device_info=0, out=out) for i in parts + ['cci_header.bin', 'card_info.bin', 'mastering_info.bin', 'initialdata.bin', 'card_device_info.bin']: if os.path.isfile(i): os.remove(i) def cci_retail2dev(path, out=''): name = os.path.splitext(os.path.basename(path))[0] if out == '': out = f'{name}_dev.3ds' cci = CCIReader(path, dev=0) cci.extract() parts = list(cci.files.keys()) parts.remove('cci_header.bin') parts.remove('card_info.bin') parts.remove('mastering_info.bin') parts.remove('initialdata.bin') if 'card_device_info.bin' in parts: parts.remove('card_device_info.bin') for i in parts: if i.endswith('.ncch'): ncch = NCCHReader(i, dev=0) 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' romfs_rdr = RomFSReader('romfs.bin') if '.crr/static.crr' in romfs_rdr.files.keys() or '.crr\\static.crr' in romfs_rdr.files.keys(): romfs_rdr.extract() crr = crrReader('romfs/.crr/static.crr') crr.regen_sig(dev=1) os.remove('romfs.bin') RomFSBuilder(romfs_dir='romfs/', out='romfs.bin') shutil.rmtree('romfs/') else: romfs = '' os.remove(i) NCCHBuilder(ncch_header=ncch_header, exheader=exheader, logo=logo, plain=plain, exefs=exefs, romfs=romfs, crypto='Secure1', regen_sig='dev', dev=1, out=i) for j in [ncch_header, exheader, logo, plain, exefs, romfs]: if j != '': os.remove(j) CCIBuilder(cci_header='cci_header.bin', card_info='card_info.bin', mastering_info='mastering_info.bin', initialdata='', card_device_info='', ncchs=parts, cardbus_crypto='fixed', regen_sig='dev', dev=1, gen_card_device_info=1, out=out) for i in parts + ['cci_header.bin', 'card_info.bin', 'mastering_info.bin', 'initialdata.bin', 'card_device_info.bin']: if os.path.isfile(i): os.remove(i) def ncch_extractall(path, dev=0): name = os.path.splitext(os.path.basename(path))[0] os.mkdir(name) ncch = NCCHReader(path, dev) ncch.extract() exefs_code_compress = 0 for i in ['ncch_header.bin', 'exheader.bin', 'logo.bin', 'plain.bin', 'exefs.bin', 'romfs.bin']: if os.path.isfile(i): if i == 'exheader.bin': with open(i, 'rb') as f: f.seek(0xD) flag = readle(f.read(1)) if flag & 1: exefs_code_compress = 1 shutil.move(i, os.path.join(name, i)) os.chdir(name) # Extract ExeFS if os.path.isfile('exefs.bin'): exefs = ExeFSReader('exefs.bin') exefs.extract(code_compressed=exefs_code_compress) os.mkdir('exefs') for i in exefs.files.keys(): shutil.move(i, os.path.join('exefs', i)) if exefs_code_compress: os.remove(os.path.join('exefs', '.code.bin')) shutil.move('code-decompressed.bin', os.path.join('exefs', '.code.bin')) # Extract RomFS if os.path.isfile('romfs.bin'): romfs = RomFSReader('romfs.bin') romfs.extract() os.chdir('..') def macos_clean(path): proc = subprocess.call(['dot_clean', path], stdout=None, stderr=None) proc = subprocess.call(['find', path, '-type', 'f', '-name', '.DS_Store', '-exec', 'rm', '{}', ';'], stdout=None, stderr=None) def ncch_rebuildall(path, dev=0): os.chdir(path) name = os.path.basename(os.getcwd()) out = f'{name}.ncch' if os.path.isdir('exefs/'): if platform.system() == 'Darwin': macos_clean('exefs/') if os.path.isfile('exefs.bin'): os.remove('exefs.bin') exefs_code_compress = 0 if os.path.isfile('exheader.bin'): with open('exheader.bin', 'rb') as f: f.seek(0xD) flag = readle(f.read(1)) if flag & 1: exefs_code_compress = 1 ExeFSBuilder(exefs_dir='exefs/', code_compress=exefs_code_compress) if os.path.isdir('romfs/'): if platform.system() == 'Darwin': macos_clean('romfs/') if os.path.isfile('romfs.bin'): os.remove('romfs.bin') RomFSBuilder(romfs_dir='romfs/') 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 = '' NCCHBuilder(ncch_header=ncch_header, exheader=exheader, logo=logo, plain=plain, exefs=exefs, romfs=romfs, dev=dev, out=out) if not os.path.isfile(f'../{out}'): shutil.move(out, f'../{out}') else: shutil.move(out, f'../{name} (new).ncch') os.chdir('..') def cci_extractall(path, dev=0): name = os.path.splitext(os.path.basename(path))[0] os.mkdir(name) cci = CCIReader(path, dev) cci.extract() for i in cci.files.keys(): shutil.move(i, os.path.join(name, i)) if i.endswith('.ncch'): os.chdir(name) ncch_extractall(i) os.chdir('..') def cci_rebuildall(path, dev=0): os.chdir(path) name = os.path.basename(os.getcwd()) out = f'{name}.3ds' ncchs = [] card_device_info = '' if os.path.isfile('card_device_info.bin'): card_device_info = 'card_device_info.bin' for i in os.listdir('.'): if os.path.isdir(i): ncchs.append(f'{i}.ncch') if os.path.isfile(f'{i}.ncch'): os.remove(f'{i}.ncch') ncch_rebuildall(i, dev) CCIBuilder(cci_header='cci_header.bin', card_info='card_info.bin', mastering_info='mastering_info.bin', initialdata='initialdata.bin', card_device_info=card_device_info, ncchs=ncchs, dev=dev, out=out) if not os.path.isfile(f'../{out}'): shutil.move(out, f'../{out}') else: shutil.move(out, f'../{name} (new).3ds') os.chdir('..') def cia_extractall(path, dev=0): name = os.path.splitext(os.path.basename(path))[0] os.mkdir(name) cia = CIAReader(path, dev) cia.extract() for i in cia.files.keys(): shutil.move(i, os.path.join(name, i)) if i.endswith('.ncch'): os.chdir(name) ncch_extractall(i) os.chdir('..') def cia_rebuildall(path, dev=0): os.chdir(path) name = os.path.basename(os.getcwd()) out = f'{name}.cia' cf = [] meta = 0 if os.path.isfile('meta.bin'): meta = 1 for i in os.listdir('.'): if os.path.isdir(i) or (os.path.isfile(i) and i.endswith('.nds')): if os.path.isdir(i): cf.append(f'{i}.ncch') if os.path.isfile(f'{i}.ncch'): os.remove(f'{i}.ncch') ncch_rebuildall(i, dev) else: cf.append(i) CIABuilder(certs='cert.bin', content_files=cf, tik='tik', tmd='tmd', meta=meta, dev=dev, out=out) if not os.path.isfile(f'../{out}'): shutil.move(out, f'../{out}') else: shutil.move(out, f'../{name} (new).cia') os.chdir('..') def cci2cia(path, out='', cci_dev=0, cia_dev=0): name = os.path.splitext(os.path.basename(path))[0] if out == '': out = f'{name}_conv.cia' cci = CCIReader(path, cci_dev) cci.extract() ncchs = [i for i in cci.files.keys() if i.endswith('.ncch')] for i in ['content6.update_n3ds.ncch', 'content7.update_o3ds.ncch']: if i in ncchs: ncchs.remove(i) os.remove(i) if cia_dev == 0: regen_sig = 'retail' else: regen_sig = 'dev' for i in ncchs: n = NCCHReader(i, dev=cci_dev) n.extract() os.remove(i) if i.startswith('content0'): with open('exheader.bin', 'r+b') as f: f.seek(0xD) flag = readle(f.read(1)) f.seek(0xD) f.write(int8tobytes(flag | 2)) # Set SDApplication bit 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 = '' NCCHBuilder(ncch_header=ncch_header, exheader=exheader, logo=logo, plain=plain, exefs=exefs, romfs=romfs, regen_sig=regen_sig, dev=cia_dev, out=i) for j in [ncch_header, exheader, logo, plain, exefs, romfs]: if j != '': os.remove(j) cf = [] d = { 'content0.game.ncch': '0000.00000000.ncch', 'content1.manual.ncch': '0001.00000001.ncch', 'content2.dlp.ncch': '0002.00000002.ncch' } for i in ncchs: cf.append(d[i]) shutil.move(i, d[i]) TMDBuilder(content_files=cf, content_files_dev=cia_dev, titleID=hex(readle(cci.hdr.mediaID))[2:].zfill(16), title_ver=0, crypt=0, regen_sig=regen_sig, out='tmd') tikBuilder(titleID=hex(readle(cci.hdr.mediaID))[2:].zfill(16), title_ver=0, regen_sig=regen_sig, out='tik') CIABuilder(content_files=cf, tik='tik', tmd='tmd', meta=1, dev=cia_dev, out=out) for i in ['cci_header.bin', 'card_info.bin', 'mastering_info.bin', 'initialdata.bin', 'card_device_info.bin', 'tmd', 'tik'] + cf: if os.path.exists(i): os.remove(i) def cdn2cia(path, out='', title_ver='', cdn_dev=0, cia_dev=0): os.chdir(path) name = os.path.basename(os.getcwd()) content_files = [] tmds = [] tmd = '' tik = '' for i in os.listdir('.'): if i.startswith('tmd.'): tmds.append(i) elif i == 'cetk': tik = i elif i.startswith('0'): content_files.append(i) if len(tmds) == 1: # If only one tmd in CDN dir, use it tmd = tmds[0] else: tmds.sort(key=lambda h: int(h.split('.')[1])) if title_ver == '': # If title version not provided, use latest one tmd = tmds[-1] else: tmd = f'tmd.{title_ver}' if cia_dev == 0: regen_sig = 'retail' else: regen_sig = 'dev' t = TMDReader(tmd) if out == '': out = f'{name}.{t.hdr.title_ver}.cia' cdn = CDNReader(content_files=content_files, tmd=tmd, tik=tik, dev=cdn_dev) cdn.extract() cf = [i for i in os.listdir('.') if i.endswith('.ncch') or i.endswith('.nds')] tmd += '.extracted' if tik == '': tikBuilder(titleID=t.titleID, title_ver=t.hdr.title_ver, titlekey=hex(readbe(cdn.titlekey))[2:].zfill(32), regen_sig=regen_sig, out='tik') tik = 'tik' else: tik += '.extracted' meta = 1 if t.titleID[3:5] == '48': meta = 0 CIABuilder(content_files=cf, tik=tik, tmd=tmd, meta=meta, dev=cia_dev, out='tmp.cia') for i in cf + [tmd, tik]: os.remove(i) shutil.move('tmp.cia', '../tmp.cia') os.chdir('..') shutil.move('tmp.cia', out) def cia2cdn(path, out='', titlekey='', cia_dev=0): name = os.path.splitext(os.path.basename(path))[0] if out == '': out = name cia = CIAReader(path, cia_dev) cia.extract() for i in ['cia_header.bin', 'cert.bin', 'meta.bin']: if os.path.isfile(i): os.remove(i) tik = 'tik' tik_read = tikReader(tik) if not tik_read.verify()[0][1]: # Ticket has invalid sig tik = '' cf = [i for i in os.listdir('.') if i.endswith('.ncch') or i.endswith('.nds')] CDNBuilder(content_files=cf, tik=tik, tmd='tmd', titlekey=titlekey, out=out) for i in ['tik', 'tmd'] + cf: if os.path.isfile(i): os.remove(i) def csu2retailcias(path, out=''): if out == '': out = 'updates_retail/' cci = CCIReader(path, dev=1) cci.extract() n = NCCHReader('content0.game.ncch', dev=1) n.extract() romfs = RomFSReader('romfs.bin') romfs.extract() cnt = cntReader('romfs/contents/Contents.cnt', 'romfs/contents/CupList') cnt.extract() for i in ['cci_header.bin', 'card_info.bin', 'mastering_info.bin', 'initialdata.bin', 'card_device_info.bin', 'content0.game.ncch', 'ncch_header.bin', 'exheader.bin', 'logo.bin', 'plain.bin', 'exefs.bin', 'romfs.bin']: if os.path.exists(i): os.remove(i) shutil.rmtree('romfs/') if not os.path.isdir(out): os.mkdir(out) for i in os.listdir('updates/'): cia_dev2retail(path=os.path.join('updates/', i), out=os.path.join(out, i)) shutil.rmtree('updates/')