def __decompressNsz(filePath, outputDir, write, raiseVerificationException, statusReportInfo): fileHashes = [] # FileExistingChecks.ExtractHashes(filePath) container = factory(filePath) container.open(str(filePath), 'rb') for f in container: if isCompressedGameFile(f._path): fileHashes.append(f._path.split('.')[0]) if write: filename = changeExtension(filePath, '.nsp') outPath = filename if outputDir is None else os.path.join( outputDir, os.path.basename(filename)) Print.info('Decompressing %s -> %s' % (filePath, outPath)) if Config.dryRun: return outPath with Pfs0Stream(outPath) as nsp: __decompressContainer(container, nsp, fileHashes, write, raiseVerificationException, statusReportInfo) return outPath else: __decompressContainer(container, None, fileHashes, write, raiseVerificationException, statusReportInfo) container.close() return None
def repack(self): newNsp = Pfs0Stream(self._path[:-4] + '.nsp') for nspF in self.hfs0['secure']: f = newNsp.add(nspF._path, nspF.size) nspF.rewind() i = 0 pageSize = 0x10000 while True: buf = nspF.read(pageSize) if len(buf) == 0: break i += len(buf) f.write(buf) newNsp.close()
def compress(filePath, compressionLevel=19, outputDir=None): filePath = os.path.abspath(filePath) CHUNK_SZ = 0x1000000 if outputDir is None: nszPath = filePath[0:-1] + 'z' else: nszPath = os.path.join(outputDir, os.path.basename(filePath[0:-1] + 'z')) nszPath = os.path.abspath(nszPath) Print.info('compressing (level %d) %s -> %s' % (compressionLevel, filePath, nszPath)) if Config.dryRun: return None container = Fs.factory(filePath) container.open(filePath, 'rb') newNsp = Pfs0Stream(nszPath) for nspf in container: if isinstance(nspf, Fs.Nca) and ((nspf.header.contentType == Fs.Type.Content.PROGRAM or nspf.header.contentType == Fs.Type.Content.PUBLICDATA) or int(nspf.header.titleId, 16) <= 0x0100000000001000): if nspf.size > ncaHeaderSize * 2: cctx = zstandard.ZstdCompressor(level=compressionLevel) newFileName = nspf._path[0:-1] + 'z' f = newNsp.add(newFileName, nspf.size) start = f.tell() nspf.seek(0) h = nspf.read(ncaHeaderSize) #crypto = aes128.AESXTS(uhx(Keys.get('header_key'))) #d = crypto.decrypt(h) # if d[0x200:0x204] == b'NCA3': # d = d[0:0x200] + b'NCZ3' + d[0x204:] # h = crypto.encrypt(d) # else: # raise IOError('unknown NCA magic') # self.partition(0x0, 0xC00, self.header, Fs.Type.Crypto.XTS, uhx(Keys.get('header_key'))) f.write(h) written = ncaHeaderSize compressor = cctx.stream_writer(f) sections = [] sectionsTmp = [] for fs in sortedFs(nspf): sectionsTmp += fs.getEncryptionSections() currentOffset = ncaHeaderSize for fs in sectionsTmp: if fs.offset < ncaHeaderSize: if fs.offset + fs.size < ncaHeaderSize: currentOffset = fs.offset + fs.size continue else: fs.size -= ncaHeaderSize - fs.offset fs.offset = ncaHeaderSize elif fs.offset > currentOffset: sections.append(BaseFs.EncryptedSection(currentOffset, fs.offset - currentOffset, Fs.Type.Crypto.NONE, None, None)) elif fs.offset < currentOffset: raise IOError("misaligned nca partitions") sections.append(fs) currentOffset = fs.offset + fs.size header = b'NCZSECTN' header += len(sections).to_bytes(8, 'little') i = 0 for fs in sections: i += 1 header += fs.offset.to_bytes(8, 'little') header += fs.size.to_bytes(8, 'little') header += fs.cryptoType.to_bytes(8, 'little') header += b'\x00' * 8 header += fs.cryptoKey header += fs.cryptoCounter f.write(header) written += len(header) bar = Status.create(nspf.size, desc=os.path.basename(nszPath), unit='B') decompressedBytes = ncaHeaderSize bar.add(ncaHeaderSize) for section in sections: #print('offset: %x\t\tsize: %x\t\ttype: %d\t\tiv%s' % (section.offset, section.size, section.cryptoType, str(hx(section.cryptoCounter)))) o = nspf.partition(offset=section.offset, size=section.size, n=None, cryptoType=section.cryptoType, cryptoKey=section.cryptoKey, cryptoCounter=bytearray(section.cryptoCounter), autoOpen=True) while not o.eof(): buffer = o.read(CHUNK_SZ) if len(buffer) == 0: raise IOError('read failed') written += compressor.write(buffer) decompressedBytes += len(buffer) bar.add(len(buffer)) o.close() compressor.flush(zstandard.FLUSH_FRAME) bar.close() Print.info('%d written vs %d tell' % (written, f.tell() - start)) written = f.tell() - start Print.info('compressed %d%% %d -> %d - %s' % (int(written * 100 / nspf.size), decompressedBytes, written, nspf._path)) newNsp.resize(newFileName, written) continue f = newNsp.add(nspf._path, nspf.size) nspf.seek(0) while not nspf.eof(): buffer = nspf.read(CHUNK_SZ) f.write(buffer) newNsp.close() return nszPath