using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Linq; using System.IO.Compression; using System.Runtime.InteropServices; /// /// https://en.wikipedia.org/wiki/Zip_(file_format) /// namespace UniGLTF.Zip { enum CompressionMethod : ushort { Stored = 0, // The file is stored (no compression) Shrink = 1, // The file is Shrunk Reduced1 = 2, // The file is Reduced with compression factor 1 Reduced2 = 3, // The file is Reduced with compression factor 2 Reduced3 = 4, // The file is Reduced with compression factor 3 Reduced4 = 5, // The file is Reduced with compression factor 4 Imploded = 6, // The file is Imploded Reserved = 7, // Reserved for Tokenizing compression algorithm Deflated = 8, // The file is Deflated } class ZipParseException : Exception { public ZipParseException(string msg) : base(msg) { } } class EOCD { public ushort NumberOfThisDisk; public ushort DiskWhereCentralDirectoryStarts; public ushort NumberOfCentralDirectoryRecordsOnThisDisk; public ushort TotalNumberOfCentralDirectoryRecords; public int SizeOfCentralDirectoryBytes; public int OffsetOfStartOfCentralDirectory; public string Comment; public override string ToString() { return string.Format("", NumberOfCentralDirectoryRecordsOnThisDisk, OffsetOfStartOfCentralDirectory, Comment ); } static int FindEOCD(byte[] bytes) { for (int i = bytes.Length - 22; i >= 0; --i) { if (bytes[i] == 0x50 && bytes[i + 1] == 0x4b && bytes[i + 2] == 0x05 && bytes[i + 3] == 0x06) { return i; } } throw new ZipParseException("EOCD is not found"); } public static EOCD Parse(Byte[] bytes) { var pos = FindEOCD(bytes); using (var ms = new MemoryStream(bytes, pos, bytes.Length - pos, false)) using (var r = new BinaryReader(ms)) { var sig = r.ReadInt32(); if (sig != 0x06054b50) throw new ZipParseException("invalid eocd signature: " + sig); var eocd = new EOCD { NumberOfThisDisk = r.ReadUInt16(), DiskWhereCentralDirectoryStarts = r.ReadUInt16(), NumberOfCentralDirectoryRecordsOnThisDisk = r.ReadUInt16(), TotalNumberOfCentralDirectoryRecords = r.ReadUInt16(), SizeOfCentralDirectoryBytes = r.ReadInt32(), OffsetOfStartOfCentralDirectory = r.ReadInt32(), }; var commentLength = r.ReadUInt16(); var commentBytes = r.ReadBytes(commentLength); eocd.Comment = Encoding.ASCII.GetString(commentBytes); return eocd; } } } abstract class CommonHeader { public Encoding Encoding = Encoding.UTF8; public Byte[] Bytes; public int Offset; public abstract int Signature { get; } protected CommonHeader(Byte[] bytes, int offset) { var sig = BitConverter.ToInt32(bytes, offset); if (sig != Signature) { throw new ZipParseException("invalid central directory file signature: " + sig); } Bytes = bytes; Offset = offset; var start = offset + 4; using (var ms = new MemoryStream(bytes, start, bytes.Length - start, false)) using (var r = new BinaryReader(ms)) { ReadBefore(r); Read(r); ReadAfter(r); } } public UInt16 VersionNeededToExtract; public UInt16 GeneralPurposeBitFlag; public CompressionMethod CompressionMethod; public UInt16 FileLastModificationTime; public UInt16 FileLastModificationDate; public Int32 CRC32; public Int32 CompressedSize; public Int32 UncompressedSize; public UInt16 FileNameLength; public UInt16 ExtraFieldLength; public abstract int FixedFieldLength { get; } public abstract int Length { get; } public string FileName { get { return Encoding.GetString(Bytes, Offset + FixedFieldLength, FileNameLength); } } public ArraySegment ExtraField { get { return new ArraySegment(Bytes, Offset + FixedFieldLength + FileNameLength, ExtraFieldLength); } } public override string ToString() { return string.Format("", FileName, CompressedSize, UncompressedSize, CompressionMethod ); } public abstract void ReadBefore(BinaryReader r); public void Read(BinaryReader r) { VersionNeededToExtract = r.ReadUInt16(); GeneralPurposeBitFlag = r.ReadUInt16(); CompressionMethod = (CompressionMethod)r.ReadUInt16(); FileLastModificationTime = r.ReadUInt16(); FileLastModificationDate = r.ReadUInt16(); CRC32 = r.ReadInt32(); CompressedSize = r.ReadInt32(); UncompressedSize = r.ReadInt32(); FileNameLength = r.ReadUInt16(); ExtraFieldLength = r.ReadUInt16(); } public abstract void ReadAfter(BinaryReader r); } class CentralDirectoryFileHeader : CommonHeader { public override int Signature { get { return 0x02014b50; } } public CentralDirectoryFileHeader(Byte[] bytes, int offset) : base(bytes, offset) { } public UInt16 VersionMadeBy; public UInt16 FileCommentLength; public UInt16 DiskNumberWhereFileStarts; public UInt16 InternalFileAttributes; public Int32 ExternalFileAttributes; public Int32 RelativeOffsetOfLocalFileHeader; public override int FixedFieldLength { get { return 46; } } public string FileComment { get { return Encoding.GetString(Bytes, Offset + 46 + FileNameLength + ExtraFieldLength, FileCommentLength); } } public override int Length { get { return FixedFieldLength + FileNameLength + ExtraFieldLength + FileCommentLength; } } public override void ReadBefore(BinaryReader r) { VersionMadeBy = r.ReadUInt16(); } public override void ReadAfter(BinaryReader r) { FileCommentLength = r.ReadUInt16(); DiskNumberWhereFileStarts = r.ReadUInt16(); InternalFileAttributes = r.ReadUInt16(); ExternalFileAttributes = r.ReadInt32(); RelativeOffsetOfLocalFileHeader = r.ReadInt32(); } } class LocalFileHeader : CommonHeader { public override int FixedFieldLength { get { return 30; } } public override int Signature { get { return 0x04034b50; } } public override int Length { get { return FixedFieldLength + FileNameLength + ExtraFieldLength; } } public LocalFileHeader(Byte[] bytes, int offset) : base(bytes, offset) { } public override void ReadBefore(BinaryReader r) { } public override void ReadAfter(BinaryReader r) { } } class ZipArchiveStorage : IStorage { public override string ToString() { return string.Format("", String.Join("", Entries.Select(x => x.ToString() + "\n").ToArray())); } public List Entries = new List(); public static ZipArchiveStorage Parse(byte[] bytes) { var eocd = EOCD.Parse(bytes); var archive = new ZipArchiveStorage(); var pos = eocd.OffsetOfStartOfCentralDirectory; for (int i = 0; i < eocd.NumberOfCentralDirectoryRecordsOnThisDisk; ++i) { var file = new CentralDirectoryFileHeader(bytes, pos); archive.Entries.Add(file); pos += file.Length; } return archive; } public Byte[] Extract(CentralDirectoryFileHeader header) { var local = new LocalFileHeader(header.Bytes, header.RelativeOffsetOfLocalFileHeader); var pos = local.Offset + local.Length; var dst = new Byte[local.UncompressedSize]; #if true using (var s = new MemoryStream(header.Bytes, pos, local.CompressedSize, false)) using (var deflateStream = new DeflateStream(s, CompressionMode.Decompress)) { int dst_pos = 0; for (int remain = dst.Length; remain > 0;) { var readSize = deflateStream.Read(dst, dst_pos, remain); dst_pos += readSize; remain -= readSize; } } #else var size=RawInflate.RawInflateImport.RawInflate(dst, 0, dst.Length, header.Bytes, pos, header.CompressedSize); #endif return dst; } public string ExtractToString(CentralDirectoryFileHeader header, Encoding encoding) { var local = new LocalFileHeader(header.Bytes, header.RelativeOffsetOfLocalFileHeader); var pos = local.Offset + local.Length; using (var s = new MemoryStream(header.Bytes, pos, local.CompressedSize, false)) using (var deflateStream = new DeflateStream(s, CompressionMode.Decompress)) using (var r = new StreamReader(deflateStream, encoding)) { return r.ReadToEnd(); } } public ArraySegment Get(string url) { var found = Entries.FirstOrDefault(x => x.FileName == url); if (found == null) { throw new FileNotFoundException("[ZipArchive]" + url); } switch (found.CompressionMethod) { case CompressionMethod.Deflated: return new ArraySegment(Extract(found)); case CompressionMethod.Stored: return new ArraySegment(found.Bytes, found.RelativeOffsetOfLocalFileHeader, found.CompressedSize); } throw new NotImplementedException(found.CompressionMethod.ToString()); } public string GetPath(string url) { return null; } } }