diff --git a/Core/Scripts/IO/ZipArchiveStorage.cs b/Core/Scripts/IO/ZipArchiveStorage.cs
new file mode 100644
index 0000000..572f425
--- /dev/null
+++ b/Core/Scripts/IO/ZipArchiveStorage.cs
@@ -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;
+
+///
+/// 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());
+ }
+ }
+}
diff --git a/Core/Scripts/IO/ZipArchiveStorage.cs.meta b/Core/Scripts/IO/ZipArchiveStorage.cs.meta
new file mode 100644
index 0000000..303fb3c
--- /dev/null
+++ b/Core/Scripts/IO/ZipArchiveStorage.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: b5aadac20fc53d04abe0492399479ce5
+timeCreated: 1528580594
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: