700 lines
27 KiB
C#
700 lines
27 KiB
C#
|
using System;
|
||
|
|
||
|
namespace TomShane.Neoforce.External.Zip
|
||
|
{
|
||
|
internal class ZipEntry
|
||
|
{
|
||
|
|
||
|
private const int ZipEntrySignature = 0x04034b50;
|
||
|
private const int ZipEntryDataDescriptorSignature= 0x08074b50;
|
||
|
|
||
|
private bool _Debug = false;
|
||
|
|
||
|
private DateTime _LastModified;
|
||
|
public DateTime LastModified
|
||
|
{
|
||
|
get { return _LastModified; }
|
||
|
}
|
||
|
|
||
|
// when this is set, we trim the volume (eg C:\) off any fully-qualified pathname,
|
||
|
// before writing the ZipEntry into the ZipFile.
|
||
|
private bool _TrimVolumeFromFullyQualifiedPaths= true; // by default, trim them.
|
||
|
public bool TrimVolumeFromFullyQualifiedPaths
|
||
|
{
|
||
|
get { return _TrimVolumeFromFullyQualifiedPaths; }
|
||
|
set { _TrimVolumeFromFullyQualifiedPaths= value; }
|
||
|
}
|
||
|
|
||
|
private string _FileName;
|
||
|
public string FileName
|
||
|
{
|
||
|
get { return _FileName; }
|
||
|
}
|
||
|
|
||
|
private Int16 _VersionNeeded;
|
||
|
public Int16 VersionNeeded
|
||
|
{
|
||
|
get { return _VersionNeeded; }
|
||
|
}
|
||
|
|
||
|
private Int16 _BitField;
|
||
|
public Int16 BitField
|
||
|
{
|
||
|
get { return _BitField; }
|
||
|
}
|
||
|
|
||
|
private Int16 _CompressionMethod;
|
||
|
public Int16 CompressionMethod
|
||
|
{
|
||
|
get { return _CompressionMethod; }
|
||
|
}
|
||
|
|
||
|
private Int32 _CompressedSize;
|
||
|
public Int32 CompressedSize
|
||
|
{
|
||
|
get { return _CompressedSize; }
|
||
|
}
|
||
|
|
||
|
private Int32 _UncompressedSize;
|
||
|
public Int32 UncompressedSize
|
||
|
{
|
||
|
get { return _UncompressedSize; }
|
||
|
}
|
||
|
|
||
|
public Double CompressionRatio
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return 100 * (1.0 - (1.0 * CompressedSize) / (1.0 * UncompressedSize));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private Int32 _LastModDateTime;
|
||
|
private Int32 _Crc32;
|
||
|
private byte[] _Extra;
|
||
|
|
||
|
private byte[] __filedata;
|
||
|
private byte[] _FileData
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (__filedata == null)
|
||
|
{
|
||
|
}
|
||
|
return __filedata;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private System.IO.MemoryStream _UnderlyingMemoryStream;
|
||
|
private System.IO.Compression.DeflateStream _CompressedStream;
|
||
|
private System.IO.Compression.DeflateStream CompressedStream
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (_CompressedStream == null)
|
||
|
{
|
||
|
_UnderlyingMemoryStream = new System.IO.MemoryStream();
|
||
|
bool LeaveUnderlyingStreamOpen = true;
|
||
|
_CompressedStream = new System.IO.Compression.DeflateStream(_UnderlyingMemoryStream,
|
||
|
System.IO.Compression.CompressionMode.Compress,
|
||
|
LeaveUnderlyingStreamOpen);
|
||
|
}
|
||
|
return _CompressedStream;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private byte[] _header;
|
||
|
internal byte[] Header
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return _header;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private int _RelativeOffsetOfHeader;
|
||
|
|
||
|
|
||
|
private static bool ReadHeader(System.IO.Stream s, ZipEntry ze)
|
||
|
{
|
||
|
int signature = TomShane.Neoforce.External.Zip.Shared.ReadSignature(s);
|
||
|
|
||
|
// return null if this is not a local file header signature
|
||
|
if (SignatureIsNotValid(signature))
|
||
|
{
|
||
|
s.Seek(-4, System.IO.SeekOrigin.Current);
|
||
|
if (ze._Debug) System.Console.WriteLine(" ZipEntry::Read(): Bad signature ({0:X8}) at position {1}", signature, s.Position);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
byte[] block = new byte[26];
|
||
|
int n = s.Read(block, 0, block.Length);
|
||
|
if (n != block.Length) return false;
|
||
|
|
||
|
int i = 0;
|
||
|
ze._VersionNeeded = (short)(block[i++] + block[i++] * 256);
|
||
|
ze._BitField = (short)(block[i++] + block[i++] * 256);
|
||
|
ze._CompressionMethod = (short)(block[i++] + block[i++] * 256);
|
||
|
ze._LastModDateTime = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
|
||
|
|
||
|
// the PKZIP spec says that if bit 3 is set (0x0008), then the CRC, Compressed size, and uncompressed size
|
||
|
// come directly after the file data. The only way to find it is to scan the zip archive for the signature of
|
||
|
// the Data Descriptor, and presume that that signature does not appear in the (compressed) data of the compressed file.
|
||
|
|
||
|
if ((ze._BitField & 0x0008) != 0x0008)
|
||
|
{
|
||
|
ze._Crc32 = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
|
||
|
ze._CompressedSize = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
|
||
|
ze._UncompressedSize = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// the CRC, compressed size, and uncompressed size are stored later in the stream.
|
||
|
// here, we advance the pointer.
|
||
|
i += 12;
|
||
|
}
|
||
|
|
||
|
Int16 filenameLength = (short)(block[i++] + block[i++] * 256);
|
||
|
Int16 extraFieldLength = (short)(block[i++] + block[i++] * 256);
|
||
|
|
||
|
block = new byte[filenameLength];
|
||
|
n = s.Read(block, 0, block.Length);
|
||
|
ze._FileName = TomShane.Neoforce.External.Zip.Shared.StringFromBuffer(block, 0, block.Length);
|
||
|
|
||
|
ze._Extra = new byte[extraFieldLength];
|
||
|
n = s.Read(ze._Extra, 0, ze._Extra.Length);
|
||
|
|
||
|
// transform the time data into something usable
|
||
|
ze._LastModified = TomShane.Neoforce.External.Zip.Shared.PackedToDateTime(ze._LastModDateTime);
|
||
|
|
||
|
// actually get the compressed size and CRC if necessary
|
||
|
if ((ze._BitField & 0x0008) == 0x0008)
|
||
|
{
|
||
|
long posn = s.Position;
|
||
|
long SizeOfDataRead = TomShane.Neoforce.External.Zip.Shared.FindSignature(s, ZipEntryDataDescriptorSignature);
|
||
|
if (SizeOfDataRead == -1) return false;
|
||
|
|
||
|
// read 3x 4-byte fields (CRC, Compressed Size, Uncompressed Size)
|
||
|
block = new byte[12];
|
||
|
n = s.Read(block, 0, block.Length);
|
||
|
if (n != 12) return false;
|
||
|
i = 0;
|
||
|
ze._Crc32 = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
|
||
|
ze._CompressedSize = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
|
||
|
ze._UncompressedSize = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
|
||
|
|
||
|
if (SizeOfDataRead != ze._CompressedSize)
|
||
|
throw new Exception("Data format error (bit 3 is set)");
|
||
|
|
||
|
// seek back to previous position, to read file data
|
||
|
s.Seek(posn, System.IO.SeekOrigin.Begin);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
private static bool SignatureIsNotValid(int signature)
|
||
|
{
|
||
|
return (signature != ZipEntrySignature);
|
||
|
}
|
||
|
|
||
|
|
||
|
public static ZipEntry Read(System.IO.Stream s)
|
||
|
{
|
||
|
return Read(s, false);
|
||
|
}
|
||
|
|
||
|
|
||
|
internal static ZipEntry Read(System.IO.Stream s, bool TurnOnDebug)
|
||
|
{
|
||
|
ZipEntry entry = new ZipEntry();
|
||
|
entry._Debug = TurnOnDebug;
|
||
|
if (!ReadHeader(s, entry)) return null;
|
||
|
|
||
|
entry.__filedata = new byte[entry.CompressedSize];
|
||
|
int n = s.Read(entry._FileData, 0, entry._FileData.Length);
|
||
|
if (n != entry._FileData.Length)
|
||
|
{
|
||
|
throw new Exception("badly formatted zip file.");
|
||
|
}
|
||
|
// finally, seek past the (already read) Data descriptor if necessary
|
||
|
if ((entry._BitField & 0x0008) == 0x0008)
|
||
|
{
|
||
|
s.Seek(16, System.IO.SeekOrigin.Current);
|
||
|
}
|
||
|
return entry;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
internal static ZipEntry Create(String filename)
|
||
|
{
|
||
|
ZipEntry entry = new ZipEntry();
|
||
|
entry._FileName = filename;
|
||
|
|
||
|
entry._LastModified = System.IO.File.GetLastWriteTime(filename);
|
||
|
// adjust the time if the .NET BCL thinks it is in DST.
|
||
|
// see the note elsewhere in this file for more info.
|
||
|
if (entry._LastModified.IsDaylightSavingTime())
|
||
|
{
|
||
|
System.DateTime AdjustedTime = entry._LastModified - new System.TimeSpan(1, 0, 0);
|
||
|
entry._LastModDateTime = TomShane.Neoforce.External.Zip.Shared.DateTimeToPacked(AdjustedTime);
|
||
|
}
|
||
|
else
|
||
|
entry._LastModDateTime = TomShane.Neoforce.External.Zip.Shared.DateTimeToPacked(entry._LastModified);
|
||
|
|
||
|
// we don't actually slurp in the file until the caller invokes Write on this entry.
|
||
|
|
||
|
return entry;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
public void Extract()
|
||
|
{
|
||
|
Extract(".");
|
||
|
}
|
||
|
|
||
|
public void Extract(System.IO.Stream s)
|
||
|
{
|
||
|
Extract(null, s);
|
||
|
}
|
||
|
|
||
|
public void Extract(string basedir)
|
||
|
{
|
||
|
Extract(basedir, null);
|
||
|
}
|
||
|
|
||
|
|
||
|
internal System.IO.Stream GetStream()
|
||
|
{
|
||
|
System.IO.MemoryStream memstream = new System.IO.MemoryStream(_FileData);
|
||
|
|
||
|
if (CompressedSize == UncompressedSize)
|
||
|
return memstream;
|
||
|
|
||
|
return new System.IO.Compression.DeflateStream(
|
||
|
memstream, System.IO.Compression.CompressionMode.Decompress);
|
||
|
}
|
||
|
|
||
|
// pass in either basedir or s, but not both.
|
||
|
// In other words, you can extract to a stream or to a directory, but not both!
|
||
|
private void Extract(string basedir, System.IO.Stream s)
|
||
|
{
|
||
|
string TargetFile = null;
|
||
|
if (basedir != null)
|
||
|
{
|
||
|
TargetFile = System.IO.Path.Combine(basedir, FileName);
|
||
|
|
||
|
// check if a directory
|
||
|
if (FileName.EndsWith("/"))
|
||
|
{
|
||
|
if (!System.IO.Directory.Exists(TargetFile))
|
||
|
System.IO.Directory.CreateDirectory(TargetFile);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else if (s != null)
|
||
|
{
|
||
|
if (FileName.EndsWith("/"))
|
||
|
// extract a directory to streamwriter? nothing to do!
|
||
|
return;
|
||
|
}
|
||
|
else throw new Exception("Invalid input.");
|
||
|
|
||
|
|
||
|
using (System.IO.MemoryStream memstream = new System.IO.MemoryStream(_FileData))
|
||
|
{
|
||
|
|
||
|
System.IO.Stream input = null;
|
||
|
try
|
||
|
{
|
||
|
|
||
|
if (CompressedSize == UncompressedSize)
|
||
|
{
|
||
|
// the System.IO.Compression.DeflateStream class does not handle uncompressed data.
|
||
|
// so if an entry is not compressed, then we just translate the bytes directly.
|
||
|
input = memstream;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
input = new System.IO.Compression.DeflateStream(memstream, System.IO.Compression.CompressionMode.Decompress);
|
||
|
}
|
||
|
|
||
|
|
||
|
if (TargetFile != null)
|
||
|
{
|
||
|
// ensure the target path exists
|
||
|
if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(TargetFile)))
|
||
|
{
|
||
|
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(TargetFile));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
System.IO.Stream output = null;
|
||
|
try
|
||
|
{
|
||
|
if (TargetFile != null)
|
||
|
output = new System.IO.FileStream(TargetFile, System.IO.FileMode.CreateNew);
|
||
|
else
|
||
|
output = s;
|
||
|
|
||
|
|
||
|
byte[] bytes = new byte[4096];
|
||
|
int n;
|
||
|
|
||
|
if (_Debug)
|
||
|
{
|
||
|
Console.WriteLine("{0}: _FileData.Length= {1}", TargetFile, _FileData.Length);
|
||
|
Console.WriteLine("{0}: memstream.Position: {1}", TargetFile, memstream.Position);
|
||
|
n = _FileData.Length;
|
||
|
if (n > 1000)
|
||
|
{
|
||
|
n = 500;
|
||
|
Console.WriteLine("{0}: truncating dump from {1} to {2} bytes...", TargetFile, _FileData.Length, n);
|
||
|
}
|
||
|
for (int j = 0; j < n; j += 2)
|
||
|
{
|
||
|
if ((j > 0) && (j % 40 == 0))
|
||
|
System.Console.WriteLine();
|
||
|
System.Console.Write(" {0:X2}", _FileData[j]);
|
||
|
if (j + 1 < n)
|
||
|
System.Console.Write("{0:X2}", _FileData[j + 1]);
|
||
|
}
|
||
|
System.Console.WriteLine("\n");
|
||
|
}
|
||
|
|
||
|
n = 1; // anything non-zero
|
||
|
while (n != 0)
|
||
|
{
|
||
|
if (_Debug) Console.WriteLine("{0}: about to read...", TargetFile);
|
||
|
n = input.Read(bytes, 0, bytes.Length);
|
||
|
if (_Debug) Console.WriteLine("{0}: got {1} bytes", TargetFile, n);
|
||
|
if (n > 0)
|
||
|
{
|
||
|
if (_Debug) Console.WriteLine("{0}: about to write...", TargetFile);
|
||
|
output.Write(bytes, 0, n);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
// we only close the output stream if we opened it.
|
||
|
if ((output != null) && (TargetFile != null))
|
||
|
{
|
||
|
output.Close();
|
||
|
output.Dispose();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (TargetFile != null)
|
||
|
{
|
||
|
// We may have to adjust the last modified time to compensate
|
||
|
// for differences in how the .NET Base Class Library deals
|
||
|
// with daylight saving time (DST) versus how the Windows
|
||
|
// filesystem deals with daylight saving time. See
|
||
|
// http://blogs.msdn.com/oldnewthing/archive/2003/10/24/55413.aspx for some context.
|
||
|
|
||
|
// in a nutshell: Daylight savings time rules change regularly. In
|
||
|
// 2007, for example, the inception week of DST changed. In 1977,
|
||
|
// DST was in place all year round. in 1945, likewise. And so on.
|
||
|
// Win32 does not attempt to guess which time zone rules were in
|
||
|
// effect at the time in question. It will render a time as
|
||
|
// "standard time" and allow the app to change to DST as necessary.
|
||
|
// .NET makes a different choice.
|
||
|
|
||
|
// -------------------------------------------------------
|
||
|
// Compare the output of FileInfo.LastWriteTime.ToString("f") with
|
||
|
// what you see in the property sheet for a file that was last
|
||
|
// written to on the other side of the DST transition. For example,
|
||
|
// suppose the file was last modified on October 17, during DST but
|
||
|
// DST is not currently in effect. Explorer's file properties
|
||
|
// reports Thursday, October 17, 2003, 8:45:38 AM, but .NETs
|
||
|
// FileInfo reports Thursday, October 17, 2003, 9:45 AM.
|
||
|
|
||
|
// Win32 says, "Thursday, October 17, 2002 8:45:38 AM PST". Note:
|
||
|
// Pacific STANDARD Time. Even though October 17 of that year
|
||
|
// occurred during Pacific Daylight Time, Win32 displays the time as
|
||
|
// standard time because that's what time it is NOW.
|
||
|
|
||
|
// .NET BCL assumes that the current DST rules were in place at the
|
||
|
// time in question. So, .NET says, "Well, if the rules in effect
|
||
|
// now were also in effect on October 17, 2003, then that would be
|
||
|
// daylight time" so it displays "Thursday, October 17, 2003, 9:45
|
||
|
// AM PDT" - daylight time.
|
||
|
|
||
|
// So .NET gives a value which is more intuitively correct, but is
|
||
|
// also potentially incorrect, and which is not invertible. Win32
|
||
|
// gives a value which is intuitively incorrect, but is strictly
|
||
|
// correct.
|
||
|
// -------------------------------------------------------
|
||
|
|
||
|
// With this adjustment, I add one hour to the tweaked .NET time, if
|
||
|
// necessary. That is to say, if the time in question had occurred
|
||
|
// in what the .NET BCL assumed to be DST (an assumption that may be
|
||
|
// wrong given the constantly changing DST rules).
|
||
|
|
||
|
#if !XBOX
|
||
|
if (LastModified.IsDaylightSavingTime())
|
||
|
{
|
||
|
DateTime AdjustedLastModified = LastModified + new System.TimeSpan(1, 0, 0);
|
||
|
System.IO.File.SetLastWriteTime(TargetFile, AdjustedLastModified);
|
||
|
}
|
||
|
else
|
||
|
System.IO.File.SetLastWriteTime(TargetFile, LastModified);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
// we only close the output stream if we opened it.
|
||
|
// we cannot use using() here because in some cases we do not want to Dispose the stream!
|
||
|
if ((input != null) && (input != memstream))
|
||
|
{
|
||
|
input.Close();
|
||
|
input.Dispose();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
internal void WriteCentralDirectoryEntry(System.IO.Stream s)
|
||
|
{
|
||
|
byte[] bytes = new byte[4096];
|
||
|
int i = 0;
|
||
|
// signature
|
||
|
bytes[i++] = (byte)(ZipDirEntry.ZipDirEntrySignature & 0x000000FF);
|
||
|
bytes[i++] = (byte)((ZipDirEntry.ZipDirEntrySignature & 0x0000FF00) >> 8);
|
||
|
bytes[i++] = (byte)((ZipDirEntry.ZipDirEntrySignature & 0x00FF0000) >> 16);
|
||
|
bytes[i++] = (byte)((ZipDirEntry.ZipDirEntrySignature & 0xFF000000) >> 24);
|
||
|
|
||
|
// Version Made By
|
||
|
bytes[i++] = Header[4];
|
||
|
bytes[i++] = Header[5];
|
||
|
|
||
|
// Version Needed, Bitfield, compression method, lastmod,
|
||
|
// crc, sizes, filename length and extra field length -
|
||
|
// are all the same as the local file header. So just copy them
|
||
|
int j = 0;
|
||
|
for (j = 0; j < 26; j++)
|
||
|
bytes[i + j] = Header[4 + j];
|
||
|
|
||
|
i += j; // positioned at next available byte
|
||
|
|
||
|
// File Comment Length
|
||
|
bytes[i++] = 0;
|
||
|
bytes[i++] = 0;
|
||
|
|
||
|
// Disk number start
|
||
|
bytes[i++] = 0;
|
||
|
bytes[i++] = 0;
|
||
|
|
||
|
// internal file attrs
|
||
|
bytes[i++] = 1;
|
||
|
bytes[i++] = 0;
|
||
|
|
||
|
// external file attrs
|
||
|
bytes[i++] = 0x20;
|
||
|
bytes[i++] = 0;
|
||
|
bytes[i++] = 0xb6;
|
||
|
bytes[i++] = 0x81;
|
||
|
|
||
|
// relative offset of local header (I think this can be zero)
|
||
|
bytes[i++] = (byte)(_RelativeOffsetOfHeader & 0x000000FF);
|
||
|
bytes[i++] = (byte)((_RelativeOffsetOfHeader & 0x0000FF00) >> 8);
|
||
|
bytes[i++] = (byte)((_RelativeOffsetOfHeader & 0x00FF0000) >> 16);
|
||
|
bytes[i++] = (byte)((_RelativeOffsetOfHeader & 0xFF000000) >> 24);
|
||
|
|
||
|
if (_Debug) System.Console.WriteLine("\ninserting filename into CDS: (length= {0})", Header.Length - 30);
|
||
|
// actual filename (starts at offset 34 in header)
|
||
|
for (j = 0; j < Header.Length - 30; j++)
|
||
|
{
|
||
|
bytes[i + j] = Header[30 + j];
|
||
|
if (_Debug) System.Console.Write(" {0:X2}", bytes[i + j]);
|
||
|
}
|
||
|
if (_Debug) System.Console.WriteLine();
|
||
|
i += j;
|
||
|
|
||
|
s.Write(bytes, 0, i);
|
||
|
}
|
||
|
|
||
|
|
||
|
private void WriteHeader(System.IO.Stream s, byte[] bytes)
|
||
|
{
|
||
|
// write the header info
|
||
|
|
||
|
int i = 0;
|
||
|
// signature
|
||
|
bytes[i++] = (byte)(ZipEntrySignature & 0x000000FF);
|
||
|
bytes[i++] = (byte)((ZipEntrySignature & 0x0000FF00) >> 8);
|
||
|
bytes[i++] = (byte)((ZipEntrySignature & 0x00FF0000) >> 16);
|
||
|
bytes[i++] = (byte)((ZipEntrySignature & 0xFF000000) >> 24);
|
||
|
|
||
|
// version needed
|
||
|
Int16 FixedVersionNeeded = 0x14; // from examining existing zip files
|
||
|
bytes[i++] = (byte)(FixedVersionNeeded & 0x00FF);
|
||
|
bytes[i++] = (byte)((FixedVersionNeeded & 0xFF00) >> 8);
|
||
|
|
||
|
// bitfield
|
||
|
Int16 BitField = 0x00; // from examining existing zip files
|
||
|
bytes[i++] = (byte)(BitField & 0x00FF);
|
||
|
bytes[i++] = (byte)((BitField & 0xFF00) >> 8);
|
||
|
|
||
|
// compression method
|
||
|
Int16 CompressionMethod = 0x08; // 0x08 = Deflate
|
||
|
bytes[i++] = (byte)(CompressionMethod & 0x00FF);
|
||
|
bytes[i++] = (byte)((CompressionMethod & 0xFF00) >> 8);
|
||
|
|
||
|
// LastMod
|
||
|
bytes[i++] = (byte)(_LastModDateTime & 0x000000FF);
|
||
|
bytes[i++] = (byte)((_LastModDateTime & 0x0000FF00) >> 8);
|
||
|
bytes[i++] = (byte)((_LastModDateTime & 0x00FF0000) >> 16);
|
||
|
bytes[i++] = (byte)((_LastModDateTime & 0xFF000000) >> 24);
|
||
|
|
||
|
// CRC32 (Int32)
|
||
|
CRC32 crc32 = new CRC32();
|
||
|
UInt32 crc = 0;
|
||
|
using (System.IO.Stream input = System.IO.File.OpenRead(FileName))
|
||
|
{
|
||
|
crc = crc32.GetCrc32AndCopy(input, CompressedStream);
|
||
|
}
|
||
|
CompressedStream.Close(); // to get the footer bytes written to the underlying stream
|
||
|
|
||
|
bytes[i++] = (byte)(crc & 0x000000FF);
|
||
|
bytes[i++] = (byte)((crc & 0x0000FF00) >> 8);
|
||
|
bytes[i++] = (byte)((crc & 0x00FF0000) >> 16);
|
||
|
bytes[i++] = (byte)((crc & 0xFF000000) >> 24);
|
||
|
|
||
|
// CompressedSize (Int32)
|
||
|
Int32 isz = (Int32)_UnderlyingMemoryStream.Length;
|
||
|
UInt32 sz = (UInt32)isz;
|
||
|
bytes[i++] = (byte)(sz & 0x000000FF);
|
||
|
bytes[i++] = (byte)((sz & 0x0000FF00) >> 8);
|
||
|
bytes[i++] = (byte)((sz & 0x00FF0000) >> 16);
|
||
|
bytes[i++] = (byte)((sz & 0xFF000000) >> 24);
|
||
|
|
||
|
// UncompressedSize (Int32)
|
||
|
if (_Debug) System.Console.WriteLine("Uncompressed Size: {0}", crc32.TotalBytesRead);
|
||
|
bytes[i++] = (byte)(crc32.TotalBytesRead & 0x000000FF);
|
||
|
bytes[i++] = (byte)((crc32.TotalBytesRead & 0x0000FF00) >> 8);
|
||
|
bytes[i++] = (byte)((crc32.TotalBytesRead & 0x00FF0000) >> 16);
|
||
|
bytes[i++] = (byte)((crc32.TotalBytesRead & 0xFF000000) >> 24);
|
||
|
|
||
|
// filename length (Int16)
|
||
|
Int16 length = (Int16)FileName.Length;
|
||
|
// see note below about TrimVolumeFromFullyQualifiedPaths.
|
||
|
if ( (TrimVolumeFromFullyQualifiedPaths) && (FileName[1]==':') && (FileName[2]=='\\')) length-=3;
|
||
|
bytes[i++] = (byte)(length & 0x00FF);
|
||
|
bytes[i++] = (byte)((length & 0xFF00) >> 8);
|
||
|
|
||
|
// extra field length (short)
|
||
|
Int16 ExtraFieldLength = 0x00;
|
||
|
bytes[i++] = (byte)(ExtraFieldLength & 0x00FF);
|
||
|
bytes[i++] = (byte)((ExtraFieldLength & 0xFF00) >> 8);
|
||
|
|
||
|
// Tue, 27 Mar 2007 16:35
|
||
|
|
||
|
// Creating a zip that contains entries with "fully qualified" pathnames
|
||
|
// can result in a zip archive that is unreadable by Windows Explorer.
|
||
|
// Such archives are valid according to other tools but not to explorer.
|
||
|
// To avoid this, we can trim off the leading volume name and slash (eg
|
||
|
// c:\) when creating (writing) a zip file. We do this by default and we
|
||
|
// leave the old behavior available with the
|
||
|
// TrimVolumeFromFullyQualifiedPaths flag - set it to false to get the old
|
||
|
// behavior. It only affects zip creation.
|
||
|
|
||
|
// actual filename
|
||
|
char[] c = ( (TrimVolumeFromFullyQualifiedPaths) && (FileName[1]==':') && (FileName[2]=='\\')) ?
|
||
|
FileName.Substring(3).ToCharArray() : // trim off volume letter, colon, and slash
|
||
|
FileName.ToCharArray();
|
||
|
int j = 0;
|
||
|
|
||
|
if (_Debug)
|
||
|
{
|
||
|
System.Console.WriteLine("local header: writing filename, {0} chars", c.Length);
|
||
|
System.Console.WriteLine("starting offset={0}", i);
|
||
|
}
|
||
|
for (j = 0; (j < c.Length) && (i + j < bytes.Length); j++)
|
||
|
{
|
||
|
bytes[i + j] = System.BitConverter.GetBytes(c[j])[0];
|
||
|
if (_Debug) System.Console.Write(" {0:X2}", bytes[i + j]);
|
||
|
}
|
||
|
if (_Debug) System.Console.WriteLine();
|
||
|
|
||
|
i += j;
|
||
|
|
||
|
// extra field (we always write nothing in this implementation)
|
||
|
// ;;
|
||
|
|
||
|
// remember the file offset of this header
|
||
|
_RelativeOffsetOfHeader = (int)s.Length;
|
||
|
|
||
|
|
||
|
if (_Debug)
|
||
|
{
|
||
|
System.Console.WriteLine("\nAll header data:");
|
||
|
for (j = 0; j < i; j++)
|
||
|
System.Console.Write(" {0:X2}", bytes[j]);
|
||
|
System.Console.WriteLine();
|
||
|
}
|
||
|
// finally, write the header to the stream
|
||
|
s.Write(bytes, 0, i);
|
||
|
|
||
|
// preserve this header data for use with the central directory structure.
|
||
|
_header = new byte[i];
|
||
|
if (_Debug) System.Console.WriteLine("preserving header of {0} bytes", _header.Length);
|
||
|
for (j = 0; j < i; j++)
|
||
|
_header[j] = bytes[j];
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
internal void Write(System.IO.Stream s)
|
||
|
{
|
||
|
byte[] bytes = new byte[4096];
|
||
|
int n;
|
||
|
|
||
|
// write the header:
|
||
|
WriteHeader(s, bytes);
|
||
|
|
||
|
// write the actual file data:
|
||
|
_UnderlyingMemoryStream.Position = 0;
|
||
|
|
||
|
if (_Debug)
|
||
|
{
|
||
|
Console.WriteLine("{0}: writing compressed data to zipfile...", FileName);
|
||
|
Console.WriteLine("{0}: total data length: {1}", FileName, _UnderlyingMemoryStream.Length);
|
||
|
}
|
||
|
while ((n = _UnderlyingMemoryStream.Read(bytes, 0, bytes.Length)) != 0)
|
||
|
{
|
||
|
|
||
|
if (_Debug)
|
||
|
{
|
||
|
Console.WriteLine("{0}: transferring {1} bytes...", FileName, n);
|
||
|
|
||
|
for (int j = 0; j < n; j += 2)
|
||
|
{
|
||
|
if ((j > 0) && (j % 40 == 0))
|
||
|
System.Console.WriteLine();
|
||
|
System.Console.Write(" {0:X2}", bytes[j]);
|
||
|
if (j + 1 < n)
|
||
|
System.Console.Write("{0:X2}", bytes[j + 1]);
|
||
|
}
|
||
|
System.Console.WriteLine("\n");
|
||
|
}
|
||
|
|
||
|
s.Write(bytes, 0, n);
|
||
|
}
|
||
|
|
||
|
//_CompressedStream.Close();
|
||
|
//_CompressedStream= null;
|
||
|
_UnderlyingMemoryStream.Close();
|
||
|
_UnderlyingMemoryStream = null;
|
||
|
}
|
||
|
}
|
||
|
}
|