def blockCompressXci(filePath, compressionLevel, blockSizeExponent, outputDir, threads): filePath = filePath.resolve() container = factory(filePath) container.open(str(filePath), 'rb') secureIn = container.hfs0['secure'] xczPath = outputDir.joinpath(filePath.stem + '.xcz') Print.info('Block compressing (level {0}) {1} -> {2}'.format( compressionLevel, filePath, xczPath)) try: with Xci.XciStream( str(xczPath), originalXciPath=filePath ) as xci: # need filepath to copy XCI container settings with Hfs0.Hfs0Stream(xci.hfs0.add('secure', 0), xci.f.tell()) as secureOut: blockCompressContainer(secureIn, secureOut, compressionLevel, blockSizeExponent, threads) xci.hfs0.resize('secure', secureOut.actualSize) except BaseException as ex: if not ex is KeyboardInterrupt: Print.error(format_exc()) if xczPath.is_file(): xczPath.unlink() container.close() return xczPath
def solidCompressTask(in_queue, statusReport, readyForWork, pleaseNoPrint, pleaseKillYourself, id): while True: readyForWork.increment() item = in_queue.get() readyForWork.decrement() if pleaseKillYourself.value() > 0: break try: filePath, compressionLevel, outputDir, threadsToUse, verifyArg = item outFile = solidCompress(filePath, compressionLevel, outputDir, threadsToUse, statusReport, id, pleaseNoPrint) if verifyArg: Print.info("[VERIFY NSZ] {0}".format(outFile)) try: verify(outFile, True, [statusReport, id], pleaseNoPrint) except VerificationException: Print.error("[BAD VERIFY] {0}".format(outFile)) Print.error("[DELETE NSZ] {0}".format(outFile)) remove(outFile) except KeyboardInterrupt: Print.info('Keyboard exception') except BaseException as e: Print.info('nut exception: {0}'.format(str(e))) raise
def AllowedToWriteOutfile(filePath, targetFileExtension, targetDict, removeOld, overwrite, parseCnmt): (filesAtTarget, alreadyExists) = targetDict extractedIdVersion = ExtractTitleIDAndVersion(filePath, parseCnmt) if extractedIdVersion == None: if parseCnmt: Print.error( 'Failed to extract TitleID/Version from booth filename "{0}" and Cnmt - Outdated keys.txt?' .format(Path(filePath).name)) else: Print.error( 'Failed to extract TitleID/Version from filename "{0}". Use -p to extract from Cnmt.' .format(Path(filePath).name)) return fileNameCheck(filePath, targetFileExtension, filesAtTarget, removeOld, overwrite) (titleIDExtracted, versionExtracted) = extractedIdVersion titleIDEntry = alreadyExists.get(titleIDExtracted) if titleIDEntry != None: DuplicateEntriesToDelete = [] OutdatedEntriesToDelete = [] exitFlag = False for versionEntry in titleIDEntry.keys(): if versionEntry == versionExtracted: if overwrite: DuplicateEntriesToDelete.append(versionEntry) else: Print.info('{0} with the same ID and version already exists in the output directory.\n'\ 'If you want to overwrite it use the -w parameter!'.format(titleIDEntry[versionEntry])) return False elif versionEntry < versionExtracted: if removeOld: if versionEntry == 0: raise ValueError( "rm-old-version: A titleID containing updates should never have any version v0 with the same titleID!" ) OutdatedEntriesToDelete.append(versionEntry) else: #versionEntry > versionExtracted if removeOld: exitFlag = True if exitFlag: Print.info('{0} with a the same ID and newer version already exists in the output directory.\n'\ 'If you want to process it do not use --rm-old-version!'.format(titleIDEntry[versionEntry])) return False for versionEntry in DuplicateEntriesToDelete: for delFilePath in titleIDEntry[versionEntry]: Print.info('Delete duplicate: {0}'.format(delFilePath)) remove(delFilePath) del filesAtTarget[Path(delFilePath).name.lower()] del titleIDEntry[versionEntry] for versionEntry in OutdatedEntriesToDelete: for delFilePath in titleIDEntry[versionEntry]: Print.info('Delete outdated version: {0}'.format(delFilePath)) remove(delFilePath) del filesAtTarget[Path(delFilePath).name.lower()] del titleIDEntry[versionEntry] return fileNameCheck(filePath, targetFileExtension, filesAtTarget, removeOld, overwrite)
def decompress(filePath, outputDir, statusReportInfo, pleaseNoPrint = None): if isNspNsz(filePath): __decompressNsz(filePath, outputDir, True, False, statusReportInfo, pleaseNoPrint) elif isXciXcz(filePath): __decompressXcz(filePath, outputDir, True, False, statusReportInfo, pleaseNoPrint) elif isCompressedGameFile(filePath): filename = changeExtension(filePath, '.nca') outPath = filename if outputDir == None else str(Path(outputDir).joinpath(filename)) Print.info('Decompressing %s -> %s' % (filePath, outPath), pleaseNoPrint) try: inFile = factory(filePath) inFile.open(str(filePath), 'rb') with open(outPath, 'wb') as outFile: written, hexHash = __decompressNcz(inFile, outFile, statusReportInfo, pleaseNoPrint) fileNameHash = Path(filePath).stem.lower() if hexHash[:32] == fileNameHash: Print.info('[VERIFIED] {0}'.format(filename), pleaseNoPrint) else: Print.info('[MISMATCH] Filename startes with {0} but {1} was expected - hash verified failed!'.format(fileNameHash, hexHash[:32]), pleaseNoPrint) except BaseException as ex: if not ex is KeyboardInterrupt: Print.error(format_exc()) if Path(outPath).is_file(): Path(outPath).unlink() finally: inFile.close() else: raise NotImplementedError("Can't decompress {0} as that file format isn't implemented!".format(filePath))
def verify(self): success = True for f in self: if not isinstance(f, Nca): continue hash = str(f.sha256()) if hash[0:16] != str(f._path)[0:16]: Print.error('BAD HASH %s = %s' % (str(f._path), str(f.sha256()))) success = False return success
def load(fileName): try: global keyAreaKeys global titleKeks global loadedKeysFile loadedKeysFile = fileName with open(fileName, encoding="utf8") as f: for line in f.readlines(): r = re.match('\s*([a-z0-9_]+)\s*=\s*([A-F0-9]+)\s*', line, re.I) if r: keys[r.group(1)] = r.group(2) aes_kek_generation_source = getKey('aes_kek_generation_source') aes_key_generation_source = getKey('aes_key_generation_source') titlekek_source = getKey('titlekek_source') key_area_key_application_source = getKey( 'key_area_key_application_source') key_area_key_ocean_source = getKey('key_area_key_ocean_source') key_area_key_system_source = getKey('key_area_key_system_source') keyAreaKeys = [] for i in range(32): keyAreaKeys.append([None, None, None]) for i in range(32): if not existsMasterKey(i): continue masterKey = getMasterKey(i) crypto = aes128.AESECB(masterKey) titleKeks.append(crypto.decrypt(titlekek_source).hex()) keyAreaKeys[i][0] = generateKek(key_area_key_application_source, masterKey, aes_kek_generation_source, aes_key_generation_source) keyAreaKeys[i][1] = generateKek(key_area_key_ocean_source, masterKey, aes_kek_generation_source, aes_key_generation_source) keyAreaKeys[i][2] = generateKek(key_area_key_system_source, masterKey, aes_kek_generation_source, aes_key_generation_source) except BaseException as e: Print.error(format_exc()) Print.error(str(e))
def blockCompressNsp(filePath, compressionLevel , blockSizeExponent, outputDir, threads): filePath = filePath.resolve() container = factory(filePath) container.open(str(filePath), 'rb') nszPath = outputDir.joinpath(filePath.stem + '.nsz') Print.info('Block compressing (level {0}) {1} -> {2}'.format(compressionLevel, filePath, nszPath)) try: with Pfs0.Pfs0Stream(str(nszPath)) as nsp: blockCompressContainer(container, nsp, compressionLevel, blockSizeExponent, threads) except BaseException as ex: if not ex is KeyboardInterrupt: Print.error(format_exc()) if nszPath.is_file(): nszPath.unlink() container.close() return nszPath
def CreateTargetDict(targetFolder, args, extension, filesAtTarget = {}, alreadyExists = {}): for filePath in expandFiles(targetFolder): try: filePath_str = str(filePath) if (isGame(filePath) or filePath.suffix == ".nspz" or filePath.suffix == ".nsx") and (extension == None or filePath.suffix == extension): print(filePath) Print.infoNoNewline('Extract TitleID/Version: {0} '.format(filePath.name)) filesAtTarget[filePath.name.lower()] = filePath_str extractedIdVersion = ExtractTitleIDAndVersion(filePath, args) if extractedIdVersion == None: if args.parseCnmt or args.alwaysParseCnmt: Print.error('Failed to extract TitleID/Version from booth filename "{0}" and Cnmt - Outdated keys.txt?'.format(Path(filePath).name)) else: Print.error('Failed to extract TitleID/Version from filename "{0}". Use -p to extract from Cnmt.'.format(Path(filePath).name)) continue titleID, version = extractedIdVersion titleIDEntry = alreadyExists.get(titleID) if titleIDEntry == None: titleIDEntry = {version: [filePath_str]} elif not version in titleIDEntry: titleIDEntry[version] = [filePath_str] else: titleIDEntry[version].append(filePath_str) alreadyExists[titleID] = titleIDEntry Print.info('=> {0} {1}'.format(titleID, version)) except BaseException as e: Print.info("") print_exc() Print.error('Error: ' + str(e)) return(filesAtTarget, alreadyExists)
def solidCompressNsp(filePath, compressionLevel, outputDir, threads, stusReport, id, pleaseNoPrint): filePath = filePath.resolve() container = factory(filePath) container.open(str(filePath), 'rb') nszPath = outputDir.joinpath(filePath.stem + '.nsz') Print.info( 'Solid compressing (level {0}) {1} -> {2}'.format( compressionLevel, filePath, nszPath), pleaseNoPrint) try: with Pfs0.Pfs0Stream(str(nszPath)) as nsp: processContainer(container, nsp, compressionLevel, threads, stusReport, id, pleaseNoPrint) except BaseException as ex: if not ex is KeyboardInterrupt: Print.error(format_exc()) if nszPath.is_file(): nszPath.unlink() container.close() return nszPath
def CreateTargetDict(targetFolder, parseCnmt, extension): filesAtTarget = {} alreadyExists = {} for file in scandir(str(targetFolder)): try: filePath = Path(targetFolder).joinpath(file.name) filePath_str = str(filePath) if filePath.suffix == extension: Print.infoNoNewline('Extract TitleID/Version: {0} '.format( file.name)) filesAtTarget[file.name.lower()] = filePath_str extractedIdVersion = ExtractTitleIDAndVersion(file, parseCnmt) if extractedIdVersion == None: if parseCnmt: Print.error( 'Failed to extract TitleID/Version from booth filename "{0}" and Cnmt - Outdated keys.txt?' .format(Path(filePath).name)) else: Print.error( 'Failed to extract TitleID/Version from filename "{0}". Use -p to extract from Cnmt.' .format(Path(filePath).name)) continue titleID, version = extractedIdVersion titleIDEntry = alreadyExists.get(titleID) if titleIDEntry == None: titleIDEntry = {version: [filePath_str]} elif not version in titleIDEntry: titleIDEntry[version] = [filePath_str] else: titleIDEntry[version].append(filePath_str) alreadyExists[titleID] = titleIDEntry Print.info('=> {0} {1}'.format(titleID, version)) except BaseException as e: Print.info("") print_exc() Print.error('Error: ' + str(e)) return (filesAtTarget, alreadyExists)
def main(): global err try: if len(argv) > 1: args = ParseArguments.parse() else: try: from nsz.gui.NSZ_GUI import GUI except ImportError: Print.error("Failed to import the GUI - is it installed?") return args = GUI().run() if args == None: Print.info("Done!") return if args.output: outfolderToPharse = args.output if not outfolderToPharse.endswith('/') and not outfolderToPharse.endswith('\\'): outfolderToPharse += "/" if not Path(outfolderToPharse).is_dir(): Print.error('Error: Output directory "{0}" does not exist!'.format(args.output)) return outfolder = Path(outfolderToPharse).resolve() if args.output else Path('.').resolve() Print.info('') Print.info(' NSZ v3.1 ,;:;;,') Print.info(' ;;;;;') Print.info(' .=\', ;:;;:,') Print.info(' /_\', "=. \';:;:;') Print.info(' @=:__, \,;:;:\'') Print.info(' _(\.= ;:;;\'') Print.info(' `"_( _/="`') Print.info(' `"\'') Print.info('') barManager = enlighten.get_manager() poolManager = Manager() statusReport = poolManager.list() readyForWork = Counter(0) pleaseNoPrint = Counter(0) pleaseKillYourself = Counter(0) pool = [] work = poolManager.Queue() amountOfTastkQueued = Counter(0) if args.titlekeys: extractTitlekeys(args.file) if args.extract: for f_str in args.file: for filePath in expandFiles(Path(f_str)): filePath_str = str(filePath) Print.info('Extracting "{0}" to {1}'.format(filePath_str, outfolder)) dir = outfolder.joinpath(filePath.stem) container = factory(filePath) container.open(filePath_str, 'rb') if isXciXcz(filePath): for hfs0 in container.hfs0: secureIn = hfs0 secureIn.unpack(dir.joinpath(hfs0._path), args.extractregex) else: container.unpack(dir, args.extractregex) container.close() if args.create: Print.info('Creating "{0}"'.format(args.create)) nsp = Nsp.Nsp(None, None) nsp.path = args.create nsp.pack(args.file) if args.C: targetDictNsz = CreateTargetDict(outfolder, args.parseCnmt, ".nsz") targetDictXcz = CreateTargetDict(outfolder, args.parseCnmt, ".xcz") sourceFileToDelete = [] for f_str in args.file: for filePath in expandFiles(Path(f_str)): if not isUncompressedGame(filePath): continue try: if filePath.suffix == '.nsp': if not AllowedToWriteOutfile(filePath, ".nsz", targetDictNsz, args.rm_old_version, args.overwrite, args.parseCnmt): continue elif filePath.suffix == '.xci': if not AllowedToWriteOutfile(filePath, ".xcz", targetDictXcz, args.rm_old_version, args.overwrite, args.parseCnmt): continue compress(filePath, outfolder, args, work, amountOfTastkQueued) if args.rm_source: sourceFileToDelete.append(filePath) except KeyboardInterrupt: raise except BaseException as e: Print.error('Error when compressing file: %s' % filePath) err.append({"filename":filePath,"error":format_exc() }) print_exc() bars = [] compressedSubBars = [] BAR_FMT = u'{desc}{desc_pad}{percentage:3.0f}%|{bar}| {count:{len_total}d}/{total:d} {unit} [{elapsed}<{eta}, {rate:.2f}{unit_pad}{unit}/s]' parallelTasks = min(args.multi, amountOfTastkQueued.value()) if parallelTasks < 0: parallelTasks = 4 for i in range(parallelTasks): statusReport.append([0, 0, 100]) p = Process(target=solidCompressTask, args=(work, statusReport, readyForWork, pleaseNoPrint, pleaseKillYourself, i)) p.start() pool.append(p) for i in range(parallelTasks): bar = barManager.counter(total=100, desc='Compressing', unit='MiB', color='cyan', bar_format=BAR_FMT) compressedSubBars.append(bar.add_subcounter('green')) bars.append(bar) #Ensures that all threads are started and compleaded before being requested to quit while readyForWork.value() < parallelTasks: sleep(0.2) if pleaseNoPrint.value() > 0: continue pleaseNoPrint.increment() for i in range(parallelTasks): compressedRead, compressedWritten, total = statusReport[i] if bars[i].total != total: bars[i].total = total//1048576 bars[i].count = compressedRead//1048576 compressedSubBars[i].count = compressedWritten//1048576 bars[i].refresh() pleaseNoPrint.decrement() pleaseKillYourself.increment() for i in range(readyForWork.value()): work.put(None) while readyForWork.value() > 0: sleep(0.02) for i in range(parallelTasks): bars[i].close(clear=True) #barManager.stop() #We aren’t using stop because of it printing garbage to the console. for filePath in sourceFileToDelete: delete_source_file(filePath) if args.D: targetDictNsz = CreateTargetDict(outfolder, args.parseCnmt, ".nsp") targetDictXcz = CreateTargetDict(outfolder, args.parseCnmt, ".xci") for f_str in args.file: for filePath in expandFiles(Path(f_str)): if not isCompressedGame(filePath) and not isCompressedGameFile(filePath): continue try: if filePath.suffix == '.nsz': if not AllowedToWriteOutfile(filePath, ".nsp", targetDictNsz, args.rm_old_version, args.overwrite, args.parseCnmt): continue elif filePath.suffix == '.xcz': if not AllowedToWriteOutfile(filePath, ".xci", targetDictXcz, args.rm_old_version, args.overwrite, args.parseCnmt): continue elif filePath.suffix == '.ncz': outfile = Path(changeExtension(outfolder.joinpath(filePath.name), ".nca")) if not args.overwrite and outfile.is_file(): Print.info('{0} with the same file name already exists in the output directory.\n'\ 'If you want to overwrite it use the -w parameter!'.format(outfile.name)) continue decompress(filePath, outfolder) if args.rm_source: delete_source_file(filePath) except KeyboardInterrupt: raise except BaseException as e: Print.error('Error when decompressing file: {0}'.format(filePath)) err.append({"filename":filePath, "error":format_exc()}) print_exc() if args.info: for f_str in args.file: for filePath in expandFiles(Path(f_str)): filePath_str = str(filePath) Print.info(filePath_str) f = factory(filePath) f.open(filePath_str, 'r+b') f.printInfo(args.depth+1) f.close() if args.verify and not args.C and not args.D: for f_str in args.file: for filePath in expandFiles(Path(f_str)): try: if isGame(filePath): Print.info("[VERIFY {0}] {1}".format(getExtensionName(filePath), filePath.name)) verify(filePath, False) except KeyboardInterrupt: raise except BaseException as e: Print.error('Error when verifying file: {0}'.format(filePath)) err.append({"filename":filePath,"error":format_exc()}) print_exc() if len(argv) == 1: pass except KeyboardInterrupt: Print.info('Keyboard exception') except BaseException as e: Print.info('nut exception: {0}'.format(str(e))) raise if err: Print.info('\n\033[93m\033[1mSummary of errors which occurred while processing files:') for e in err: Print.info('\033[0mError while processing {0}'.format(e["filename"])) Print.info(e["error"]) Print.info('Done!') _exit(0)
if not existsMasterKey(i): continue masterKey = getMasterKey(i) crypto = aes128.AESECB(masterKey) titleKeks.append(crypto.decrypt(titlekek_source).hex()) keyAreaKeys[i][0] = generateKek(key_area_key_application_source, masterKey, aes_kek_generation_source, aes_key_generation_source) keyAreaKeys[i][1] = generateKek(key_area_key_ocean_source, masterKey, aes_kek_generation_source, aes_key_generation_source) keyAreaKeys[i][2] = generateKek(key_area_key_system_source, masterKey, aes_kek_generation_source, aes_key_generation_source) except BaseException as e: Print.error(format_exc()) Print.error(str(e)) keyScriptPath = Path(sys.argv[0]) #While loop to get rid of things like C:\\Python37\\Scripts\\nsz.exe\\__main__.py while not keyScriptPath.is_dir(): keyScriptPath = keyScriptPath.parents[0] keypath = keyScriptPath.joinpath('keys.txt') dumpedKeys = Path.home().joinpath(".switch", "prod.keys") if keypath.is_file(): load(str(keypath)) elif dumpedKeys.is_file(): load(str(dumpedKeys)) else: errorMsg = "{0} or {1} not found!\nPlease dump your keys using https://github.com/shchmue/Lockpick_RCM/releases".format(str(keypath), str(dumpedKeys)) Print.error(errorMsg) input("Press Enter to exit...") sys.exit(1)