/// <summary> /// Reads the local file header in the ZIP archive /// </summary> /// <returns>The local file header</returns> private ZipLocalFileHeader ReadFileHeader() { ZipLocalFileHeader header = new ZipLocalFileHeader(); header.Signature = ReadULong(); header.VersionToExtract = ReadUShort(); header.Flags = ReadUShort(); header.CompressionMethod = ReadUShort(); header.LastModTime = ReadUShort(); header.LastModDate = ReadUShort(); header.CRC32 = ReadULong(); header.CompressedSize = ReadULong(); header.UncompressedSize = ReadULong(); header.FileNameLength = ReadUShort(); header.ExtraFieldLength = ReadUShort(); header.Filename = ReadUtf8String(header.FileNameLength); if (header.ExtraFieldLength > 0) { SkipBytes(header.ExtraFieldLength); } // Invoke the OnEntityEvent event. We need to do some work to get there, through... // -- Create a new archive information record EntityInformation info = new EntityInformation(); // -- Incorporate bits from the file header info.CompressedSize = header.CompressedSize; info.UncompressedSize = header.UncompressedSize; info.StoredName = header.Filename; // -- Get the absolute path of the file info.AbsoluteName = ""; if (DataWriter != null) { info.AbsoluteName = DataWriter.GetAbsoluteFilePath(header.Filename); } // -- Get some bits from the currently open archive file info.PartNumber = CurrentPartNumber ?? 1; info.PartOffset = InputStream.Position; // -- Create the event arguments object EntityEventArgs args = new EntityEventArgs(info); // -- Finally, invoke the event OnEntityEvent(args); return(header); }
/// <summary> /// Processes a data block in a JPA file located in the current file position /// </summary> /// <param name="dataBlockHeader">The header of the block being processed</param> /// <param name="token">A cancellation token, allowing the called to cancel the processing</param> public void ProcessDataBlock(ZipLocalFileHeader dataBlockHeader, CancellationToken token) { // Get the compression type TCompressionType CompressionType = TCompressionType.Uncompressed; switch (dataBlockHeader.CompressionMethod) { case 0: CompressionType = TCompressionType.Uncompressed; break; case 8: CompressionType = TCompressionType.GZip; break; case 12: CompressionType = TCompressionType.BZip2; break; default: throw new InvalidArchiveException(Language.ResourceManager.GetString("ERR_FORMAT_INVALID_COMPRESSION_METHOD")); } // Decide on the file type TEntityType EntityType = TEntityType.File; if (dataBlockHeader.Filename.EndsWith("/")) { EntityType = TEntityType.Directory; } else if (dataBlockHeader.VersionToExtract == BitConverter.ToUInt16(new byte[] { 0x10, 0x03 }, 0)) { EntityType = TEntityType.Symlink; } // Process the data block ProcessDataBlock(dataBlockHeader.CompressedSize, dataBlockHeader.UncompressedSize, CompressionType, EntityType, dataBlockHeader.Filename, token); }
private static void GetStreamingAssetsInfoFromJar(string apkPath, List <string> paths, List <PartInfo> parts) { using (var stream = File.OpenRead(apkPath)) using (var reader = new BinaryReader(stream)) { if (!stream.CanRead) { throw new ArgumentException(); } if (!stream.CanSeek) { throw new ArgumentException(); } long expectedNumberOfEntries; long centralDirectoryStart; ZipArchiveUtils.ReadEndOfCentralDirectory(stream, reader, out expectedNumberOfEntries, out centralDirectoryStart); try { stream.Seek(centralDirectoryStart, SeekOrigin.Begin); long numberOfEntries = 0; ZipCentralDirectoryFileHeader header; const int prefixLength = 7; const string prefix = "assets/"; const string assetsPrefix = "assets/bin/"; Debug.Assert(prefixLength == prefix.Length); while (ZipCentralDirectoryFileHeader.TryReadBlock(reader, out header)) { if (header.CompressedSize != header.UncompressedSize) { // we only want uncompressed files } else { var fileName = Encoding.UTF8.GetString(header.Filename); if (fileName.StartsWith(prefix)) { // ignore normal assets... if (fileName.StartsWith(assetsPrefix)) { // Note: if you put bin directory in your StreamingAssets you will get false negative here } else { var relativePath = fileName.Substring(prefixLength - 1); var entry = new PartInfo() { crc32 = header.Crc32, offset = header.RelativeOffsetOfLocalHeader, // this offset will need fixing later on size = header.UncompressedSize }; var index = paths.BinarySearch(relativePath, StringComparer.OrdinalIgnoreCase); if (index >= 0) { throw new System.InvalidOperationException("Paths duplicate! " + fileName); } paths.Insert(~index, relativePath); parts.Insert(~index, entry); } } } numberOfEntries++; } if (numberOfEntries != expectedNumberOfEntries) { throw new ZipArchiveException("Number of entries does not match"); } } catch (EndOfStreamException ex) { throw new ZipArchiveException("CentralDirectoryInvalid", ex); } // now fix offsets for (int i = 0; i < parts.Count; ++i) { var entry = parts[i]; stream.Seek(entry.offset, SeekOrigin.Begin); if (!ZipLocalFileHeader.TrySkipBlock(reader)) { throw new ZipArchiveException("Local file header corrupt"); } entry.offset = stream.Position; parts[i] = entry; } } }
private static void GetStreamingAssetsInfoFromJar(string apkPath, List <string> paths, List <PartInfo> parts) { using (var stream = File.OpenRead(apkPath)) using (var reader = new BinaryReader(stream)) { if (!stream.CanRead) { throw new ArgumentException(); } if (!stream.CanSeek) { throw new ArgumentException(); } long expectedNumberOfEntries; long centralDirectoryStart; ZipArchiveUtils.ReadEndOfCentralDirectory(stream, reader, out expectedNumberOfEntries, out centralDirectoryStart); try { stream.Seek(centralDirectoryStart, SeekOrigin.Begin); long numberOfEntries = 0; ZipCentralDirectoryFileHeader header; const int prefixLength = 7; const string prefix = "assets/"; const string assetsPrefix = "assets/bin/"; Debug.Assert(prefixLength == prefix.Length); while (ZipCentralDirectoryFileHeader.TryReadBlock(reader, out header)) { if (header.CompressedSize != header.UncompressedSize) { #if UNITY_ASSERTIONS var fileName = Encoding.UTF8.GetString(header.Filename); if (fileName.StartsWith(prefix) && !fileName.StartsWith(assetsPrefix)) { bool isStreamingAsset = true; AndroidIsCompressedFileStreamingAsset(fileName, ref isStreamingAsset); if (isStreamingAsset) { Debug.LogAssertionFormat("BetterStreamingAssets: file {0} is where Streaming Assets are put, but is compressed. " + "If this is a App Bundle build, see README for a possible workaround. " + "If this file is not a Streaming Asset (has been on purpose by hand or by another plug-in), implement " + "BetterStreamingAssets.AndroidIsCompressedFileStreamingAsset partial method to prevent this message from appearing again. ", fileName); } } #endif // we only want uncompressed files } else { var fileName = Encoding.UTF8.GetString(header.Filename); if (fileName.EndsWith("/")) { // there's some strangeness when it comes to OBB: directories are listed as files // simply ignoring them should be enough Debug.Assert(header.UncompressedSize == 0); } else if (fileName.StartsWith(prefix)) { // ignore normal assets... if (fileName.StartsWith(assetsPrefix)) { // Note: if you put bin directory in your StreamingAssets you will get false negative here } else { var relativePath = fileName.Substring(prefixLength - 1); var entry = new PartInfo() { crc32 = header.Crc32, offset = header.RelativeOffsetOfLocalHeader, // this offset will need fixing later on size = header.UncompressedSize }; var index = paths.BinarySearch(relativePath, StringComparer.OrdinalIgnoreCase); if (index >= 0) { throw new System.InvalidOperationException("Paths duplicate! " + fileName); } paths.Insert(~index, relativePath); parts.Insert(~index, entry); } } } numberOfEntries++; } if (numberOfEntries != expectedNumberOfEntries) { throw new ZipArchiveException("Number of entries does not match"); } } catch (EndOfStreamException ex) { throw new ZipArchiveException("CentralDirectoryInvalid", ex); } // now fix offsets for (int i = 0; i < parts.Count; ++i) { var entry = parts[i]; stream.Seek(entry.offset, SeekOrigin.Begin); if (!ZipLocalFileHeader.TrySkipBlock(reader)) { throw new ZipArchiveException("Local file header corrupt"); } entry.offset = stream.Position; parts[i] = entry; } } }
/// <summary> /// Implements the Extract method which extracts a backup archive. A DataWriter must be already assigned and configured or an /// exception will be raised. /// </summary> public override void Extract(CancellationToken token) { // Initialize Close(); Progress.FilePosition = 0; Progress.RunningCompressed = 0; Progress.RunningUncompressed = 0; Progress.Status = ExtractionStatus.Running; Progress.LastException = null; ProgressEventArgs args; ZipEndOfCentralDirectoryRecord eocdRecord; try { // Read the End of Central Directory header eocdRecord = ReadEndOfCentralDirectory(); // Reset the position to the very start of the archive Close(); Progress.FilePosition = 0; Progress.RunningCompressed = 0; Progress.RunningUncompressed = 0; // Check for a spanned archive signature CheckForSpannedArchiveSignature(); // Invoke event at start of extraction args = new ProgressEventArgs(Progress); OnProgressEvent(args); while ((CurrentPartNumber != null) && (Progress.FilePosition < eocdRecord.TotalSize)) { // We have reached the start of the Central Directory, i.e. we're finished extracting if ((CurrentPartNumber == (eocdRecord.CDDisk + 1)) && (Progress.FilePosition == eocdRecord.CDOffset)) { break; } ZipLocalFileHeader fileHeader = ReadFileHeader(); if (token.IsCancellationRequested) { token.ThrowIfCancellationRequested(); } ProcessDataBlock(fileHeader, token); args = new ProgressEventArgs(Progress); OnProgressEvent(args); } } catch (OperationCanceledException cancelledException) { // The operation was cancelled. Set the state to Idle and reset the extraction. Close(); Progress.FilePosition = 0; Progress.RunningCompressed = 0; Progress.RunningUncompressed = 0; Progress.Status = ExtractionStatus.Idle; Progress.LastException = cancelledException; // Invoke an event notifying susbcribers about the cancelation. args = new ProgressEventArgs(Progress); OnProgressEvent(args); return; } catch (Exception errorException) { // Any other exception. Close the option file and set the status to error. Close(); Progress.Status = ExtractionStatus.Error; Progress.LastException = errorException; // Invoke an event notifying of the error state args = new ProgressEventArgs(Progress); OnProgressEvent(args); return; } // Invoke an event signaling the end of extraction Progress.Status = ExtractionStatus.Finished; args = new ProgressEventArgs(Progress); OnProgressEvent(args); }