From 2cc05ae420aeff3ba9dac3147373bf0720fc5732 Mon Sep 17 00:00:00 2001 From: xprism1 Date: Thu, 29 Jun 2023 17:13:18 +0800 Subject: [PATCH] utils: add cia2cdn --- README.md | 8 ++++++++ lib/ctr_cdn.py | 37 ++++++++++++++++++++++++++----------- ntool.py | 16 +++++++++++++++- utils.py | 25 ++++++++++++++++++++++++- 4 files changed, 73 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 0a5a0e4..34d69b6 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,14 @@ python3 ntool.py cci2cia (--out ) (--cci_dev) python3 ntool.py cdn2cia (--out ) (--title-ver ) (--cdn-dev) (--cia-dev) ``` +### Convert CIA to CDN contents +- Pass `--titlekey` to use a custom titlekey to encrypt the content files (this field will be ignored if the ticket in the CIA is signed) +- Pass `--cia-dev` if the CIA is dev-signed +- Note that clean CDN contents are not guaranteed as the CIA may have improper contents (e.g. due to being decrypted) +```py +python3 ntool.py cia2cdn (--out ) (--titlekey ) (--cia-dev) +``` + ### Full extraction and rebuild of NCCH/CIA/CCI: - First, use `ncch_extractall`/`cia_extractall`/`cci_extractall` to extract the NCCH/CIA/CCI to a folder - Pass the `--dev` flag to use dev crypto diff --git a/lib/ctr_cdn.py b/lib/ctr_cdn.py index 21e8302..a7bf4fc 100644 --- a/lib/ctr_cdn.py +++ b/lib/ctr_cdn.py @@ -111,14 +111,15 @@ class CDNReader: ) class CDNBuilder: - def __init__(self, content_files=[], tik='', tmd='', dev=0, out_tik='tik_new'): + def __init__(self, content_files=[], tik='', tmd='', titlekey='', dev=0, out='new'): ''' content_files: list containing filenames of content files, which must each be named '[content index in hex, 4 chars].[contentID in hex, 8 chars].[ncch/nds]' Certificate chain will be appended at the end of the following files: - tik: path to ticket (optional) - tmd: path to tmd + titlekey: decrypted title key in hex, will be used if provided and ticket is not provided (if neither ticket nor titlekey is provided, use titlekey generation algorithm) dev: 0 or 1 (if 1, use dev-crypto for ticket titlekey) - out_tik: path to output ticket with cert chain appended + out: path to output folder ''' content_files.sort(key=lambda h: int(h.split('.')[0], 16)) @@ -131,25 +132,35 @@ class CDNBuilder: if tik != '': # If ticket is present, parse ticket to get titlekey self.tik_read = tikReader(tik, dev) self.titlekey = self.tik_read.titlekey - else: # Use titlekey generation algorithm - self.titlekey = hextobytes(CTR.titlekey_gen(self.tmd_read.titleID, 'mypass')) + else: + if titlekey != '': + self.titlekey = hextobytes(titlekey) + else: # Use titlekey generation algorithm + self.titlekey = hextobytes(CTR.titlekey_gen(self.tmd_read.titleID, 'mypass')) + if not os.path.isdir(out): + os.makedirs(out) + # Encrypt content files for i in self.content_files: info = self.tmd_read.files[i] name = i.split('.')[1] # CDN files are named as contentID + if 'iv' in info: + iv = info['iv'] + else: + iv = int(i.split('.')[0], 16).to_bytes(2, 'big') + (b'\0' * 14) f = open(i, 'rb') - g = open(name, 'wb') - cipher = AES.new(self.titlekey, AES.MODE_CBC, iv=info['iv']) + g = open(os.path.join(out, name), 'wb') + cipher = AES.new(self.titlekey, AES.MODE_CBC, iv=iv) for data in read_chunks(f, info['size']): g.write(cipher.encrypt(data)) f.close() g.close() - print(f'Wrote to {name}') + print(f'Wrote to {os.path.join(out, name)}') # Append certificate chain to end of tmd (and tik) name = f'tmd.{self.tmd_read.hdr.title_ver}' - with open(name, 'wb') as f: + with open(os.path.join(out, name), 'wb') as f: with open(tmd, 'rb') as g: f.write(g.read()) if dev == 0: @@ -162,10 +173,14 @@ class CDNBuilder: f.write(g.read()) with open(os.path.join(resources_dir, 'CA00000004.cert'), 'rb') as g: f.write(g.read()) - print(f'Wrote to {name}') + print(f'Wrote to {os.path.join(out, name)}') if self.tik != '': - with open(f'{out_tik}', 'wb') as f: + if self.tik_read.data.consoleID == 0: + tik_name = 'cetk' + else: + tik_name = 'tik' + with open(os.path.join(out, tik_name), 'wb') as f: with open(tik, 'rb') as g: f.write(g.read()) if dev == 0: @@ -178,4 +193,4 @@ class CDNBuilder: f.write(g.read()) with open(os.path.join(resources_dir, 'CA00000004.cert'), 'rb') as g: f.write(g.read()) - print(f'Wrote to {out_tik}') + print(f'Wrote to {os.path.join(out, tik_name)}') diff --git a/ntool.py b/ntool.py index 740b8c8..d337b27 100755 --- a/ntool.py +++ b/ntool.py @@ -45,4 +45,18 @@ elif sys.argv[1] == 'cdn2cia': cdn_dev = 1 elif sys.argv[i] == '--cia-dev': cia_dev = 1 - cdn2cia(path, out, title_ver, cdn_dev, cia_dev) \ No newline at end of file + cdn2cia(path, out, title_ver, cdn_dev, cia_dev) + +elif sys.argv[1] == 'cia2cdn': + path = sys.argv[2] + out = '' + titlekey = '' + cia_dev = 0 + for i in range(2, len(sys.argv)): + if sys.argv[i] == '--out': + out = sys.argv[i + 1] + elif sys.argv[i] == '--titlekey': + titlekey = sys.argv[i + 1] + elif sys.argv[i] == '--cia-dev': + cia_dev = 1 + cia2cdn(path, out, titlekey, cia_dev) \ No newline at end of file diff --git a/utils.py b/utils.py index 58e26f7..653070c 100644 --- a/utils.py +++ b/utils.py @@ -7,7 +7,7 @@ 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 +from lib.ctr_cdn import CDNReader, CDNBuilder from lib.ctr_cnt import cntReader def cia_dev2retail(path, out=''): @@ -569,6 +569,29 @@ def cdn2cia(path, out='', title_ver='', cdn_dev=0, cia_dev=0): 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/'