This repository has been archived by the owner on Sep 2, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move ZipArchiveStorage.cs from UniGLTF_Test
- Loading branch information
Showing
2 changed files
with
392 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,380 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Text; | ||
using System.Linq; | ||
using System.IO.Compression; | ||
using System.Runtime.InteropServices; | ||
|
||
/// <summary> | ||
/// https://en.wikipedia.org/wiki/Zip_(file_format) | ||
/// </summary> | ||
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("<EOCD records: {0}, offset: {1}, '{2}'>", | ||
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<Byte> ExtraField | ||
{ | ||
get | ||
{ | ||
return new ArraySegment<byte>(Bytes, | ||
Offset + FixedFieldLength + FileNameLength, | ||
ExtraFieldLength); | ||
} | ||
} | ||
|
||
public override string ToString() | ||
{ | ||
return string.Format("<file {0}({1}/{2} {3})>", | ||
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("<ZIPArchive\n{0}>", String.Join("", Entries.Select(x => x.ToString() + "\n").ToArray())); | ||
} | ||
|
||
public List<CentralDirectoryFileHeader> Entries = new List<CentralDirectoryFileHeader>(); | ||
|
||
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<byte> 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<byte>(Extract(found)); | ||
|
||
case CompressionMethod.Stored: | ||
return new ArraySegment<byte>(found.Bytes, found.RelativeOffsetOfLocalFileHeader, found.CompressedSize); | ||
} | ||
|
||
throw new NotImplementedException(found.CompressionMethod.ToString()); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.