def save(confFile='conf/nut.conf'): Print.debug("saving config") os.makedirs(os.path.dirname(confFile), exist_ok=True) j = {} try: with open(confFile, encoding='utf8') as f: j = json.load(f) except BaseException: # pylint: disable=broad-except pass jset(j, ['paths'], paths.__dict__) jset(j, ['compression'], compression.__dict__) jset(j, ['pullUrls'], pullUrls) jset(j, ['threads'], threads) jset(j, ['download'], download.__dict__) jset(j, ['server', 'hostname'], server.hostname) jset(j, ['server', 'port'], server.port) jset(j, ['autolaunchBrowser'], autolaunchBrowser) jset(j, ['autoUpdateTitleDb'], autoUpdateTitleDb) jset(j, ['allowNoMetadata'], allowNoMetadata) with open(confFile, 'w', encoding='utf-8') as f: Print.debug("writing config to filesystem") json.dump(j, f, indent=4)
def decrypt_NCA(fPath, outDir=''): fName = os.path.basename(fPath).split()[0] if outDir == '': outDir = os.path.splitext(fPath)[0] os.makedirs(outDir, exist_ok=True) commandLine = hactoolPath + ' "' + fPath + '"' + keysArg \ + ' --exefsdir="' + outDir + os.sep + 'exefs"' \ + ' --romfsdir="' + outDir + os.sep + 'romfs"' \ + ' --section0dir="' + outDir + os.sep + 'section0"' \ + ' --section1dir="' + outDir + os.sep + 'section1"' \ + ' --section2dir="' + outDir + os.sep + 'section2"' \ + ' --section3dir="' + outDir + os.sep + 'section3"' \ + ' --header="' + outDir + os.sep + 'Header.bin"' try: Print.debug(commandLine) subprocess.check_output(commandLine, shell=True) if os.listdir(outDir) == []: raise subprocess.CalledProcessError( 'Decryption failed, output folder %s is empty!' % outDir, cmd=commandLine) except subprocess.CalledProcessError: Print.error('Decryption failed!') raise return outDir
def downloadFile(url, fPath): fName = os.path.basename(fPath).split()[0] r = makeRequest('GET', url) with open(fPath, 'wb') as f: f.write(r.content) Print.debug('\r\nSaved to %s!' % f.name) return fPath
def makeRequest(method, url, hdArgs={}, start=None, end=None, accept='*/*'): if start is None: reqHd = { 'Accept': accept, 'Connection': None, 'Accept-Encoding': None, } else: reqHd = { 'Accept': accept, 'Connection': None, 'Accept-Encoding': None, 'Range': 'bytes=%d-%d' % (start, end - 1), } reqHd.update(hdArgs) r = requests.request(method, url, headers=reqHd, verify=False, stream=True, timeout=15) Print.debug('%s %s %s' % (method, str(r.status_code), url)) Print.debug(r.request.headers) if r.status_code == 403: raise IOError('Forbidden ' + r.text) return r
def download_file(url, fPath, titleId=None): fName = os.path.basename(fPath).split()[0] if os.path.exists(fPath): dlded = os.path.getsize(fPath) r = make_request('GET', url, hdArgs={'Range': 'bytes=%s-' % dlded}) if r.headers.get('Server') != 'openresty/1.9.7.4': Print.info('Download is already complete, skipping!') return fPath elif r.headers.get( 'Content-Range' ) == None: # CDN doesn't return a range if request >= filesize fSize = int(r.headers.get('Content-Length')) else: fSize = dlded + int(r.headers.get('Content-Length')) if dlded == fSize: Print.info('Download is already complete, skipping!') return fPath elif dlded < fSize: Print.info('Resuming download...') f = open(fPath, 'ab') else: Print.error( 'Existing file is bigger than expected (%s/%s), restarting download...' % (dlded, fSize)) dlded = 0 f = open(fPath, "wb") else: dlded = 0 r = make_request('GET', url) fSize = int(r.headers.get('Content-Length')) f = open(fPath, 'wb') chunkSize = 0x100000 if fSize >= 10000: s = Status.create(fSize, desc=fName, unit='B') s.id = titleId.upper() s.add(dlded) for chunk in r.iter_content(chunkSize): f.write(chunk) s.add(len(chunk)) dlded += len(chunk) if not Config.isRunning: break s.close() else: f.write(r.content) dlded += len(r.content) if fSize != 0 and dlded != fSize: raise ValueError('Downloaded data is not as big as expected (%s/%s)!' % (dlded, fSize)) f.close() Print.debug('\r\nSaved to %s!' % f.name) return fPath
def gen_xml(self, ncaPath, outf): data = self.parse() ContentMeta = ET.Element('ContentMeta') ET.SubElement(ContentMeta, 'Type').text = self.type ET.SubElement(ContentMeta, 'Id').text = '0x' + self.id ET.SubElement(ContentMeta, 'Version').text = self.ver ET.SubElement(ContentMeta, 'RequiredDownloadSystemVersion').text = self.dlsysver n = 1 for titleId in data: locals()["Content" + str(n)] = ET.SubElement( ContentMeta, 'Content') ET.SubElement(locals()["Content" + str(n)], 'Type').text = data[titleId][0] ET.SubElement(locals()["Content" + str(n)], 'Id').text = titleId ET.SubElement(locals()["Content" + str(n)], 'Size').text = str(data[titleId][1]) ET.SubElement(locals()["Content" + str(n)], 'Hash').text = data[titleId][2] ET.SubElement(locals()["Content" + str(n)], 'KeyGeneration').text = self.mkeyrev n += 1 # cnmt.nca itself cnmt = ET.SubElement(ContentMeta, 'Content') ET.SubElement(cnmt, 'Type').text = 'Meta' ET.SubElement(cnmt, 'Id').text = os.path.basename(ncaPath).split('.')[0] ET.SubElement(cnmt, 'Size').text = str(os.path.getsize(ncaPath)) ET.SubElement(cnmt, 'Hash').text = sha256_file(ncaPath) ET.SubElement(cnmt, 'KeyGeneration').text = self.mkeyrev ET.SubElement(ContentMeta, 'Digest').text = self.digest ET.SubElement(ContentMeta, 'KeyGenerationMin').text = self.mkeyrev ET.SubElement(ContentMeta, 'RequiredSystemVersion').text = self.sysver if self.type == 'Application': ET.SubElement( ContentMeta, 'PatchId').text = '0x%016x' % (int(self.id, 16) + 0x800) elif self.type == 'Patch': ET.SubElement( ContentMeta, 'OriginalId').text = '0x%016x' % (int(self.id, 16) & 0xFFFFFFFFFFFFF000) elif self.type == 'AddOnContent': ET.SubElement( ContentMeta, 'ApplicationId').text = '0x%016x' % (int(self.id, 16) - 0x1000 & 0xFFFFFFFFFFFFF000) string = ET.tostring(ContentMeta, encoding='utf-8') reparsed = minidom.parseString(string) with open(outf, 'wb') as f: f.write(reparsed.toprettyxml(encoding='utf-8', indent=' ')[:-1]) Print.debug('\t\tGenerated %s!' % os.path.basename(outf)) return outf
def data(self, index, role=Qt.DisplayRole): Print.debug('TableModel data') if role == Qt.DisplayRole: i = index.row() j = index.column() row = self.datatable[i] if Column(j) == Column.FILE_SIZE: return _format_size(row[Column(j)]) return f"{row[Column(j)]}" else: return QtCore.QVariant()
def poll_commands(in_ep, out_ep): p = Packet(in_ep, out_ep) while True: if p.recv(0): if p.command == 1: Print.debug('Recv command! %d' % p.command) req = UsbRequest(p.payload.decode('utf-8')) with UsbResponse(p) as resp: Server.route(req, resp) else: Print.error('Unknown command! %d' % p.command) else: Print.error('failed to read!')
def repack(self): Print.debug('\tRepacking to NSP...') hd = self.gen_header() totSize = len(hd) + sum(os.path.getsize(file) for file in self.files) if os.path.exists(self.path) and os.path.getsize(self.path) == totSize: Print.info('\t\tRepack %s is already complete!' % self.path) return t = Status.create(totSize, unit='B', desc=os.path.basename(self.path)) Print.debug('\t\tWriting header...') outf = open(self.path, 'wb') outf.write(hd) t.update(len(hd)) done = 0 for file in self.files: Print.debug('\t\tAppending %s...' % os.path.basename(file)) with open(file, 'rb') as inf: while True: buf = inf.read(4096) if not buf: break outf.write(buf) t.update(len(buf)) t.close() Print.debug('\t\tRepacked to %s!' % outf.name) outf.close()
def update(self, dataIn): Print.debug('TableModel update start') self.datatable = [] for value in dataIn.values(): new_item = {} new_item[Column.FILENAME] = os.path.basename(value.path) new_item[Column.TITLE_ID] = str(value.titleId) titleType = "UPD" if value.isUpdate() else "DLC" if value.isDLC() \ else "BASE" new_item[Column.TITLE_TYPE] = titleType new_item[Column.FILE_SIZE] = value.fileSize self.datatable.append(new_item) self._sort() Print.debug('TableModel update finished')
def makeRequest(method, url, hdArgs={}, start=None, end=None, accept='*/*'): if start is None: reqHd = { 'Accept': accept, 'Connection': None, 'Accept-Encoding': None, 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36' } else: reqHd = { 'Accept': accept, 'Connection': None, 'Accept-Encoding': None, 'Range': 'bytes=%d-%d' % (start, end - 1), 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36' } reqHd.update(hdArgs) r = requests.request(method, url, headers=reqHd, verify=False, stream=True, timeout=15) Print.debug('%s %s %s' % (method, str(r.status_code), url)) Print.debug(r.request.headers) #data = dump.dump_all(r) #print(data.decode('utf-8')) if r.status_code == 403: raise IOError('Forbidden ' + r.text) return r
def verify_NCA(ncaFile, titleKey): if not titleKey: return False commandLine = hactoolPath + ' "' + ncaFile + '"' + keysArg + ' --titlekey="' + titleKey + '"' try: output = str( subprocess.check_output(commandLine, stderr=subprocess.STDOUT, shell=True)) except subprocess.CalledProcessError as exc: Print.error("Status : FAIL" + str(exc.returncode) + ', ' + str(exc.output)) return False else: if "Error: section 0 is corrupted!" in output or "Error: section 1 is corrupted!" in output: Print.error("\nNCA Verification failed. Probably a bad titlekey.") return False Print.debug("\nTitlekey verification successful.") return True
def reload(file_name=DEFAULT_TRANSLATION_FILE): global _initialized global _lang_db global _en_db global _lang global _locale global _file_name _file_name = file_name _lang = ENGLISH_LANG_ID if Config.language is None or Config.language == "en" else Config.language Print.debug(f"translation file is '{_file_name}'") Print.debug(f"_lang is '{_lang}'") try: with open(_file_name, encoding='utf-8') as json_file: data = json.load(json_file) _en_db = data[ENGLISH_LANG_ID] if _lang != ENGLISH_LANG_ID: _lang_db = data[_lang] _initialized = True except (FileNotFoundError, ValueError): Print.warning( f"missing translation file '{_file_name}' or it's not a JSON-file") _initialized = False Print.debug(f"_initialized is '{_initialized}'") return _initialized
def download_game(titleId, ver, tkey=None, nspRepack=False, name='', verify=False): name = get_name(titleId) gameType = '' if name == 'Unknown Title': temp = "[" + titleId + "]" else: temp = name + " [" + titleId + "]" outputDir = os.path.join(os.path.dirname(__file__), nspout) basetitleId = '' if titleId.endswith('000'): # Base game gameDir = os.path.join(outputDir, temp) gameType = 'BASE' elif titleId.endswith('800'): # Update basetitleId = '%s000' % titleId[:-3] gameDir = os.path.join(outputDir, temp) gameType = 'UPD' else: # DLC basetitleId = '%s%s000' % (titleId[:-4], str(int(titleId[-4], 16) - 1)) gameDir = os.path.join(outputDir, temp) gameType = 'DLC' os.makedirs(gameDir, exist_ok=True) if not os.path.exists(outputDir): os.makedirs(outputDir, exist_ok=True) if name != "": if gameType == 'BASE': outf = os.path.join(outputDir, '%s [%s][v%s]' % (name, titleId, ver)) else: outf = os.path.join( outputDir, '%s [%s][%s][v%s]' % (name, gameType, titleId, ver)) else: if gameType == 'BASE': outf = os.path.join(outputDir, '%s [v%s]' % (titleId, ver)) else: outf = os.path.join(outputDir, '%s [%s][v%s]' % (titleId, gameType, ver)) if truncateName: name = name.replace(' ', '')[0:20] outf = os.path.join(outputDir, '%s%sv%s' % (name, titleId, ver)) if tkey: outf = outf + '.nsp' else: outf = outf + '.nsx' for item in os.listdir(outputDir): if item.find('%s' % titleId) != -1: if item.find('v%s' % ver) != -1: Print.info('%s already exists, skipping download' % outf) shutil.rmtree(gameDir) return files = download_title(gameDir, titleId, ver, tkey, nspRepack, verify=verify) if gameType != 'UPD': if tkey: verified = verify_NCA(get_biggest_file(gameDir), tkey) if not verified: shutil.rmtree(gameDir) Print.debug('cleaned up downloaded content') return if nspRepack == True: if files == None: return NSP = nsp(outf, files) Print.debug('starting repack, This may take a while') NSP.repack() shutil.rmtree(gameDir) Print.debug('cleaned up downloaded content') if enxhop: enxhopDir = os.path.join(outputDir, 'switch') os.makedirs(enxhopDir, exist_ok=True) with open(os.path.join(enxhopDir, 'eNXhop.txt'), 'a+') as f: f.write(titleId + '\n') return outf return gameDir
def download_title(gameDir, titleId, ver, tkey=None, nspRepack=False, n='', verify=False, retry=0): try: Print.info('Downloading %s [%s] v%s:' % (get_name(titleId), titleId, ver)) titleId = titleId.lower() isNsx = False if tkey: tkey = tkey.lower() if len(titleId) != 16: titleId = (16 - len(titleId)) * '0' + titleId url = 'https://atum%s.hac.%s.d4c.nintendo.net/t/a/%s/%s?device_id=%s' % ( n, env, titleId, ver, deviceId) Print.debug(url) try: r = make_request('HEAD', url) except Exception as e: Print.error( "Error downloading title. Check for incorrect titleid or version: " + str(e)) return CNMTid = r.headers.get('X-Nintendo-Content-ID') if CNMTid == None: Print.info('title not available on CDN') return Print.debug('Downloading CNMT (%s.cnmt.nca)...' % CNMTid) url = 'https://atum%s.hac.%s.d4c.nintendo.net/c/a/%s?device_id=%s' % ( n, env, CNMTid, deviceId) fPath = os.path.join(gameDir, CNMTid + '.cnmt.nca') cnmtNCA = download_file(url, fPath, titleId) cnmtDir = decrypt_NCA(cnmtNCA) CNMT = cnmt( os.path.join(cnmtDir, 'section0', os.listdir(os.path.join(cnmtDir, 'section0'))[0]), os.path.join(cnmtDir, 'Header.bin')) if nspRepack == True: outf = os.path.join( gameDir, '%s.xml' % os.path.basename(cnmtNCA.strip('.nca'))) cnmtXML = CNMT.gen_xml(cnmtNCA, outf) rightsID = '%s%s%s' % (titleId, (16 - len(CNMT.mkeyrev)) * '0', CNMT.mkeyrev) tikPath = os.path.join(gameDir, '%s.tik' % rightsID) certPath = os.path.join(gameDir, '%s.cert' % rightsID) if CNMT.type == 'Application' or CNMT.type == 'AddOnContent': shutil.copy( os.path.join(os.path.dirname(__file__), 'Certificate.cert'), certPath) if tkey: with open( os.path.join(os.path.dirname(__file__), 'Ticket.tik'), 'rb') as intik: data = bytearray(intik.read()) data[0x180:0x190] = uhx(tkey) data[0x285] = int(CNMT.mkeyrev) data[0x2A0:0x2B0] = uhx(rightsID) with open(tikPath, 'wb') as outtik: outtik.write(data) else: isNsx = True with open( os.path.join(os.path.dirname(__file__), 'Ticket.tik'), 'rb') as intik: data = bytearray(intik.read()) data[0x180:0x190] = uhx( '00000000000000000000000000000000') data[0x285] = int(CNMT.mkeyrev) data[0x2A0:0x2B0] = uhx(rightsID) with open(tikPath, 'wb') as outtik: outtik.write(data) Print.debug( 'Generated %s and %s!' % (os.path.basename(certPath), os.path.basename(tikPath))) elif CNMT.type == 'Patch': Print.debug('Downloading cetk...') with open( download_cetk( rightsID, os.path.join(gameDir, '%s.cetk' % rightsID)), 'rb') as cetk: cetk.seek(0x180) tkey = hx(cetk.read(0x10)).decode() Print.info('Titlekey: %s' % tkey) with open(tikPath, 'wb') as tik: cetk.seek(0x0) tik.write(cetk.read(0x2C0)) with open(certPath, 'wb') as cert: cetk.seek(0x2C0) cert.write(cetk.read(0x700)) Print.debug( 'Extracted %s and %s from cetk!' % (os.path.basename(certPath), os.path.basename(tikPath))) NCAs = { 0: [], 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], } for type in [0, 3, 4, 5, 1, 2, 6]: # Download smaller files first for ncaID in CNMT.parse(CNMT.ncaTypes[type]): Print.debug('Downloading %s entry (%s.nca)...' % (CNMT.ncaTypes[type], ncaID)) url = 'https://atum%s.hac.%s.d4c.nintendo.net/c/c/%s?device_id=%s' % ( n, env, ncaID, deviceId) fPath = os.path.join(gameDir, ncaID + '.nca') NCAs[type].append(download_file(url, fPath, titleId)) if verify: if calc_sha256(fPath) != CNMT.parse( CNMT.ncaTypes[type])[ncaID][2]: os.remove(fPath) raise BaseException( '%s is corrupted, hashes don\'t match!' % os.path.basename(fPath)) else: Print.info('Verified %s...' % os.path.basename(fPath)) if nspRepack == True: files = [] files.append(certPath) files.append(tikPath) for key in [1, 5, 2, 4, 6]: files.extend(NCAs[key]) files.append(cnmtNCA) files.append(cnmtXML) files.extend(NCAs[3]) return files except KeyboardInterrupt: raise except BaseException as e: retry += 1 if retry < 5: Print.error( 'An error occured while downloading, retry attempt %d: %s' % (retry, str(e))) download_title(gameDir, titleId, ver, tkey, nspRepack, n, verify, retry) else: raise
def flags(self, index): Print.debug('TableModel flags') return Qt.ItemIsEnabled
def columnCount(self, parent=QtCore.QModelIndex()): Print.debug('TableModel columnCount') return self.column_count
def rowCount(self, parent=QtCore.QModelIndex()): Print.debug('TableModel rowCount') return len(self.datatable)