Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ZipFile.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under Apache 2.0 License. See LICENSE.md for details.
3 //
4 // --------------------------------------------------------------------------------------------------------------------
5 // <copyright file="ZipFile.cs" company="Matthew Leibowitz">
6 // Copyright (c) Matthew Leibowitz
7 // This code is licensed under the Apache 2.0 License
8 // http://www.apache.org/licenses/LICENSE-2.0.html
9 // </copyright>
10 // <summary>
11 // Unique class for compression/decompression file. Represents a Zip file.
12 // </summary>
13 // --------------------------------------------------------------------------------------------------------------------
14 
15 namespace System.IO.Compression.Zip
16 {
17  using System.Collections.Generic;
18  using System.Diagnostics;
19  using System.Linq;
20  using System.Text;
21 
22  /// <summary>
23  /// Unique class for compression/decompression file. Represents a Zip file.
24  /// </summary>
25  public class ZipFile : IDisposable
26  {
27  #region Constants
28 
29  /// <summary>
30  /// todo
31  /// </summary>
32  private const float SmoothingFactor = 0.005F;
33 
34  #endregion
35 
36  #region Fields
37 
38  /// <summary>
39  /// Central dir image
40  /// </summary>
41  private byte[] centralDirImage;
42 
43  /// <summary>
44  /// Stream object of storage file
45  /// </summary>
46  private Stream zipFileStream;
47 
48  #endregion
49 
50  #region Constructors and Destructors
51 
52  /// <summary>
53  /// Initializes a new instance of the <see cref="ZipFile"/> class.
54  /// Method to open an existing storage file
55  /// </summary>
56  /// <param name="filename">
57  /// Full path of Zip file to open
58  /// </param>
59  /// <returns>
60  /// A valid ZipFile object
61  /// </returns>
62  public ZipFile(string filename)
63  : this(new FileStream(filename, FileMode.Open, FileAccess.Read))
64  {
65  this.FileName = filename;
66  }
67 
68  /// <summary>
69  /// Initializes a new instance of the <see cref="ZipFile"/> class.
70  /// Method to open an existing storage from stream
71  /// </summary>
72  /// <param name="stream">
73  /// Already opened stream with zip contents
74  /// </param>
75  /// <returns>
76  /// A valid ZipFile object
77  /// </returns>
78  public ZipFile(Stream stream)
79  {
80  if (!stream.CanSeek)
81  {
82  throw new InvalidOperationException("Stream cannot seek");
83  }
84 
85  this.zipFileStream = stream;
86 
87  if (!this.ReadFileInfo())
88  {
89  throw new Exception();
90  }
91 
92  this.FileName = string.Empty;
93  }
94 
95  #endregion
96 
97  #region Public Properties
98 
99  /// <summary>
100  /// Gets the number of entries in the zip file.
101  /// </summary>
102  public int EntryCount { get; private set; }
103 
104  /// <summary>
105  /// Gets filename of the zip file.
106  /// </summary>
107  public string FileName { get; private set; }
108 
109  #endregion
110 
111  #region Public Methods and Operators
112 
113  /// <summary>
114  /// DOS Date and time are packed values with the following format:
115  /// MS-DOS date bits description:
116  /// 0-4 Day of the month (1・1)
117  /// 5-8 Month (1 = January, 2 = February, and so on)
118  /// 9-15 Year offset from 1980 (add 1980 to get actual year)
119  /// MS-DOS time bits description:
120  /// 0-4 Second divided by 2
121  /// 5-10 Minute (0・9)
122  /// 11-15 Hour (0・3 on a 24-hour clock)
123  /// </summary>
124  /// <param name="dateTime">
125  /// The date Time.
126  /// </param>
127  /// <returns>
128  /// The date time to dos time.
129  /// </returns>
130  public static uint DateTimeToDosTime(DateTime dateTime)
131  {
132  return (uint)((dateTime.Second / 2) | // seconds
133  (dateTime.Minute << 5) | // minutes
134  (dateTime.Hour << 11) | // hours
135  (dateTime.Day << 16) | // days
136  (dateTime.Month << 21) | // months
137  ((dateTime.Year - 1980) << 25)); // years
138  }
139 
140  /// <summary>
141  /// Ensures that the zip file is valid.
142  /// </summary>
143  /// <param name="validationHandler">
144  /// The class to hold the object for progress reporting
145  /// </param>
146  /// <returns>
147  /// True if the zip is valid, otherwise False.
148  /// </returns>
149  public static bool Validate(ZipFileValidationHandler validationHandler)
150  {
151  var buf = new byte[1024 * 256];
152  try
153  {
154  var zip = new ZipFile(validationHandler.Filename);
155  ZipFileEntry[] entries = zip.GetAllEntries();
156 
157  // First calculate the total compressed length
158  long totalCompressedLength = entries.Sum(entry => entry.CompressedSize);
159  float averageVerifySpeed = 0;
160  long totalBytesRemaining = totalCompressedLength;
161  validationHandler.TotalBytes = totalCompressedLength;
162 
163  // Then calculate a CRC for every file in the Zip file,
164  // comparing it to what is stored in the Zip directory
165  foreach (ZipFileEntry entry in entries)
166  {
167  if (entry.Crc32 != -1)
168  {
169  DateTime startTime = DateTime.UtcNow;
170 
171  var crc = new Crc32();
172 
173  uint offset = entry.FileOffset;
174  var length = (int)entry.CompressedSize;
175 
176  Stream raf = zip.zipFileStream;
177  raf.Seek(offset, SeekOrigin.Begin);
178 
179  while (length > 0)
180  {
181  int seek = length > buf.Length ? buf.Length : length;
182  raf.Read(buf, 0, seek);
183  crc.Update(buf, 0, seek);
184  length -= seek;
185 
186  DateTime currentTime = DateTime.UtcNow;
187  var timePassed = (float)(currentTime - startTime).TotalMilliseconds;
188  if (timePassed > 0)
189  {
190  float currentSpeedSample = seek / timePassed;
191  if (averageVerifySpeed <= 0)
192  {
193  averageVerifySpeed = (SmoothingFactor * currentSpeedSample)
194  + ((1 - SmoothingFactor) * averageVerifySpeed);
195  }
196  else
197  {
198  averageVerifySpeed = currentSpeedSample;
199  }
200 
201  totalBytesRemaining -= seek;
202  var timeRemaining = (long)(totalBytesRemaining / averageVerifySpeed);
203 
204  validationHandler.AverageSpeed = averageVerifySpeed;
205  validationHandler.CurrentBytes = totalCompressedLength - totalBytesRemaining;
206  validationHandler.TimeRemaining = timeRemaining;
207 
208  validationHandler.UpdateUi(validationHandler);
209  }
210 
211  startTime = currentTime;
212  if (validationHandler.ShouldCancel)
213  {
214  return true;
215  }
216  }
217 
218  if (crc.Value != entry.Crc32)
219  {
220  Debug.WriteLine("CRC does not match for entry: " + entry.FilenameInZip);
221  Debug.WriteLine("In file: " + entry.ZipFileName);
222 
223  return false;
224  }
225  }
226  }
227  }
228  catch (IOException e)
229  {
230  Console.WriteLine(e.ToString());
231  Console.Write(e.StackTrace);
232  return false;
233  }
234 
235  return true;
236  }
237 
238  /// <summary>
239  /// Updates central directory (if pertinent) and close the Zip storage
240  /// </summary>
241  /// <remarks>
242  /// This is a required step, unless automatic dispose is used
243  /// </remarks>
244  public void Close()
245  {
246  if (this.zipFileStream != null)
247  {
248  this.zipFileStream.Flush();
249  this.zipFileStream.Dispose();
250  this.zipFileStream = null;
251  }
252  }
253 
254  /// <summary>
255  /// Closes the Zip file stream
256  /// </summary>
257  public void Dispose()
258  {
259  this.Close();
260  }
261 
262  /// <summary>
263  /// Copy the contents of a stored file into a physical file
264  /// </summary>
265  /// <param name="zfe">
266  /// Entry information of file to extract
267  /// </param>
268  /// <param name="filename">
269  /// Name of file to store uncompressed data
270  /// </param>
271  /// <returns>
272  /// True if success, false if not.
273  /// </returns>
274  /// <remarks>
275  /// Unique compression methods are Store and Deflate
276  /// </remarks>
277  public bool ExtractFile(ZipFileEntry zfe, string filename)
278  {
279  // Make sure the parent directory exist
280  string path = Path.GetDirectoryName(filename);
281 
282  if (!string.IsNullOrWhiteSpace(path) && !Directory.Exists(path))
283  {
284  Directory.CreateDirectory(path);
285  }
286 
287  // Check it is directory. If so, do nothing
288  if (Directory.Exists(filename))
289  {
290  return true;
291  }
292 
293  Stream output = new FileStream(filename, FileMode.Create, FileAccess.Write);
294  bool result = this.ExtractFile(zfe, output);
295  if (result)
296  {
297  output.Close();
298  }
299 
300  File.SetCreationTime(filename, zfe.ModifyTime);
301  File.SetLastWriteTime(filename, zfe.ModifyTime);
302 
303  return result;
304  }
305 
306  /// <summary>
307  /// Copy the contents of a stored file into an opened stream
308  /// </summary>
309  /// <param name="zfe">
310  /// Entry information of file to extract
311  /// </param>
312  /// <param name="stream">
313  /// Stream to store the uncompressed data
314  /// </param>
315  /// <returns>
316  /// True if success, false if not.
317  /// </returns>
318  /// <remarks>
319  /// Unique compression methods are Store and Deflate
320  /// </remarks>
321  public bool ExtractFile(ZipFileEntry zfe, Stream stream)
322  {
323  if (!stream.CanWrite)
324  {
325  throw new InvalidOperationException("Stream cannot be written");
326  }
327 
328  // check signature
329  var signature = new byte[4];
330  this.zipFileStream.Seek(zfe.HeaderOffset, SeekOrigin.Begin);
331  this.zipFileStream.Read(signature, 0, 4);
332  if (BitConverter.ToUInt32(signature, 0) != 0x04034b50)
333  {
334  return false;
335  }
336 
337  // Select input stream for inflating or just reading
338  Stream inStream = this.GetZipStream(zfe);
339  if (inStream == null)
340  {
341  return false;
342  }
343 
344  // Buffered copy
345  var buffer = new byte[16384];
346  this.zipFileStream.Seek(zfe.FileOffset, SeekOrigin.Begin);
347  uint bytesPending = zfe.FileSize;
348  while (bytesPending > 0)
349  {
350  int bytesRead = inStream.Read(buffer, 0, (int)Math.Min(bytesPending, buffer.Length));
351  stream.Write(buffer, 0, bytesRead);
352  bytesPending -= (uint)bytesRead;
353  }
354 
355  stream.Flush();
356 
357  if (zfe.Method == Compression.Deflate)
358  {
359  inStream.Dispose();
360  }
361 
362  return true;
363  }
364 
365  /// <summary>
366  /// Copy the contents of a stored file into an opened stream
367  /// </summary>
368  /// <param name="zfe">
369  /// Entry information of file to extract
370  /// </param>
371  /// <param name="buffer">
372  /// Stream to store the uncompressed data
373  /// </param>
374  /// <returns>
375  /// True if success, false if not.
376  /// </returns>
377  /// <remarks>
378  /// Unique compression methods are Store and Deflate
379  /// </remarks>
380  public bool ExtractFile(ZipFileEntry zfe, byte[] buffer)
381  {
382  // check signature
383  var signature = new byte[4];
384  zipFileStream.Seek(zfe.HeaderOffset, SeekOrigin.Begin);
385  zipFileStream.Read(signature, 0, 4);
386  if (BitConverter.ToUInt32(signature, 0) != 0x04034b50)
387  return false;
388 
389  // Select input stream for inflating or just reading
390  Stream inStream = GetZipStream(zfe);
391  if (inStream == null)
392  {
393  return false;
394  }
395 
396  // Buffered copy
397  zipFileStream.Seek(zfe.FileOffset, SeekOrigin.Begin);
398  inStream.Read(buffer, 0, buffer.Length);
399 
400  if (zfe.Method == Compression.Deflate)
401  inStream.Dispose();
402 
403  return true;
404  }
405 
406  /// <summary>
407  /// Read all the file records in the central directory
408  /// </summary>
409  /// <returns>
410  /// List of all entries in directory.
411  /// </returns>
413  {
414  if (this.centralDirImage == null)
415  {
416  throw new InvalidOperationException("Central directory currently does not exist");
417  }
418 
419  var result = new List<ZipFileEntry>();
420 
421  int pointer = 0;
422  while (pointer < this.centralDirImage.Length)
423  {
424  uint signature = BitConverter.ToUInt32(this.centralDirImage, pointer);
425  if (signature != 0x02014b50)
426  {
427  break;
428  }
429 
430  result.Add(this.GetEntry(ref pointer));
431  }
432 
433  return result.ToArray();
434  }
435 
436  /// <summary>
437  /// Copy the contents of a stored file into an opened stream
438  /// </summary>
439  /// <param name="zfe">
440  /// Entry information of file to extract
441  /// </param>
442  /// <returns>
443  /// Stream to store the uncompressed data
444  /// </returns>
445  /// <remarks>
446  /// Unique compression methods are Store and Deflate
447  /// </remarks>
448  public Stream ReadFile(ZipFileEntry zfe)
449  {
450  // check signature
451  var signature = new byte[4];
452  this.zipFileStream.Seek(zfe.HeaderOffset, SeekOrigin.Begin);
453  this.zipFileStream.Read(signature, 0, 4);
454  if (BitConverter.ToUInt32(signature, 0) != 0x04034b50)
455  {
456  throw new InvalidOperationException("Not a valid zip entry.");
457  }
458 
459  // Select input stream for inflating or just reading
460  Stream inStream;
461  switch (zfe.Method)
462  {
463  case Compression.Store:
464  inStream = this.zipFileStream;
465  break;
466  case Compression.Deflate:
467  inStream = new DeflateStream(this.zipFileStream, CompressionMode.Decompress, true);
468  break;
469  default:
470  throw new InvalidOperationException("Not a valid zip entry.");
471  }
472 
473  this.zipFileStream.Seek(zfe.FileOffset, SeekOrigin.Begin);
474 
475  return new ZipStream(inStream, zfe);
476  }
477 
478  #endregion
479 
480  #region Methods
481 
482  /// <summary>
483  /// DOS Date and time are packed values with the following format:
484  /// MS-DOS date bits description:
485  /// 0-4 Day of the month (1・1)
486  /// 5-8 Month (1 = January, 2 = February, and so on)
487  /// 9-15 Year offset from 1980 (add 1980 to get actual year)
488  /// MS-DOS time bits description:
489  /// 0-4 Second divided by 2
490  /// 5-10 Minute (0・9)
491  /// 11-15 Hour (0・3 on a 24-hour clock)
492  /// </summary>
493  /// <param name="dosDateTime">
494  /// The dos Date Time.
495  /// </param>
496  /// <returns>
497  /// The .NET DateTime
498  /// </returns>
499  private static DateTime DosTimeToDateTime(uint dosDateTime)
500  {
501  return new DateTime(
502  (int)(dosDateTime >> 25) + 1980,
503  (int)(dosDateTime >> 21) & 15,
504  (int)(dosDateTime >> 16) & 31,
505  (int)(dosDateTime >> 11) & 31,
506  (int)(dosDateTime >> 5) & 63,
507  (int)(dosDateTime & 31) * 2);
508  }
509 
510  /// <summary>
511  /// Loads an entry from the zip file.
512  /// </summary>
513  /// <param name="pointer">
514  /// The pointer.
515  /// </param>
516  /// <returns>
517  /// An entry at this location
518  /// </returns>
519  private ZipFileEntry GetEntry(ref int pointer)
520  {
521  bool encodeUtf8 = (BitConverter.ToUInt16(this.centralDirImage, pointer + 8) & 0x0800) != 0;
522  ushort method = BitConverter.ToUInt16(this.centralDirImage, pointer + 10);
523  uint modifyTime = BitConverter.ToUInt32(this.centralDirImage, pointer + 12);
524  uint crc32 = BitConverter.ToUInt32(this.centralDirImage, pointer + 16);
525  uint comprSize = BitConverter.ToUInt32(this.centralDirImage, pointer + 20);
526  uint fileSize = BitConverter.ToUInt32(this.centralDirImage, pointer + 24);
527  ushort filenameSize = BitConverter.ToUInt16(this.centralDirImage, pointer + 28);
528  ushort extraSize = BitConverter.ToUInt16(this.centralDirImage, pointer + 30);
529  ushort commentSize = BitConverter.ToUInt16(this.centralDirImage, pointer + 32);
530  uint headerOffset = BitConverter.ToUInt32(this.centralDirImage, pointer + 42);
531  int commentsStart = 46 + filenameSize + extraSize;
532  int headerSize = commentsStart + commentSize;
533  Encoding encoder = encodeUtf8 ? Encoding.UTF8 : Encoding.Default;
534  string comment = null;
535  if (commentSize > 0)
536  {
537  comment = encoder.GetString(this.centralDirImage, pointer + commentsStart, commentSize);
538  }
539 
540  var zfe = new ZipFileEntry
541  {
542  Method = (Compression)method,
543  FilenameInZip = encoder.GetString(this.centralDirImage, pointer + 46, filenameSize),
544  FileOffset = this.GetFileOffset(headerOffset),
545  FileSize = fileSize,
546  CompressedSize = comprSize,
547  HeaderOffset = headerOffset,
548  HeaderSize = (uint)headerSize,
549  Crc32 = crc32,
550  ModifyTime = DosTimeToDateTime(modifyTime),
551  Comment = comment ?? string.Empty,
552  ZipFileName = this.FileName
553  };
554  pointer += headerSize;
555  return zfe;
556  }
557 
558  /// <summary>
559  /// Returns the file offset based on the header's offset.
560  /// </summary>
561  /// <param name="headerOffset">
562  /// The header offset.
563  /// </param>
564  /// <returns>
565  /// The file offset.
566  /// </returns>
567  private uint GetFileOffset(uint headerOffset)
568  {
569  var buffer = new byte[2];
570 
571  this.zipFileStream.Seek(headerOffset + 26, SeekOrigin.Begin);
572  this.zipFileStream.Read(buffer, 0, 2);
573  ushort filenameSize = BitConverter.ToUInt16(buffer, 0);
574  this.zipFileStream.Read(buffer, 0, 2);
575  ushort extraSize = BitConverter.ToUInt16(buffer, 0);
576 
577  long offset = 30 + filenameSize + extraSize + headerOffset;
578 
579  return (uint)offset;
580  }
581 
582  /// <summary>
583  /// Returns a stream for the specified zip file entry.
584  /// </summary>
585  /// <param name="zfe">
586  /// The zip file entry for which to fetch a stream.
587  /// </param>
588  /// <returns>
589  /// A stream for the specified zip file entry.
590  /// </returns>
591  private Stream GetZipStream(ZipFileEntry zfe)
592  {
593  switch (zfe.Method)
594  {
595  case Compression.Store:
596  return this.zipFileStream;
597  case Compression.Deflate:
598  return new DeflateStream(this.zipFileStream, CompressionMode.Decompress, true);
599  default:
600  return null;
601  }
602  }
603 
604  /// <summary>
605  /// Reads the end-of-central-directory record
606  /// </summary>
607  /// <returns>
608  /// True if the read was successful, false if there ws an error.
609  /// </returns>
610  private bool ReadFileInfo()
611  {
612  if (this.zipFileStream.Length < 22)
613  {
614  return false;
615  }
616 
617  try
618  {
619  this.zipFileStream.Seek(-17, SeekOrigin.End);
620  var br = new BinaryReader(this.zipFileStream);
621  do
622  {
623  this.zipFileStream.Seek(-5, SeekOrigin.Current);
624  uint sig = br.ReadUInt32();
625  if (sig == 0x06054b50)
626  {
627  this.zipFileStream.Seek(6, SeekOrigin.Current);
628 
629  this.EntryCount = br.ReadUInt16();
630  int centralSize = br.ReadInt32();
631  uint centralDirOffset = br.ReadUInt32();
632  ushort commentSize = br.ReadUInt16();
633 
634  // check if comment field is the very last data in file
635  if (this.zipFileStream.Position + commentSize != this.zipFileStream.Length)
636  {
637  return false;
638  }
639 
640  // Copy entire central directory to a memory buffer
641  this.centralDirImage = new byte[centralSize];
642  this.zipFileStream.Seek(centralDirOffset, SeekOrigin.Begin);
643  this.zipFileStream.Read(this.centralDirImage, 0, centralSize);
644 
645  // Leave the pointer at the begining of central dir, to append new files
646  this.zipFileStream.Seek(centralDirOffset, SeekOrigin.Begin);
647  return true;
648  }
649  }
650  while (this.zipFileStream.Position > 0);
651  }
652  catch (Exception ex)
653  {
654  Debug.WriteLine(ex.Message);
655  }
656 
657  return false;
658  }
659 
660  #endregion
661  }
662 }
Represents an entry in Zip file directory
Definition: ZipFileEntry.cs:20
Unique class for compression/decompression file. Represents a Zip file.
Definition: ZipFile.cs:25
uint CompressedSize
Gets the compressed file size.
Definition: ZipFileEntry.cs:32
System.Text.Encoding Encoding
System.IO.FileMode FileMode
Definition: ScriptSync.cs:33
ZipFile(Stream stream)
Initializes a new instance of the ZipFile class. Method to open an existing storage from stream ...
Definition: ZipFile.cs:78
ZipFile(string filename)
Initializes a new instance of the ZipFile class. Method to open an existing storage file ...
Definition: ZipFile.cs:62
ZipFileEntry[] GetAllEntries()
Read all the file records in the central directory
Definition: ZipFile.cs:412
bool ExtractFile(ZipFileEntry zfe, Stream stream)
Copy the contents of a stored file into an opened stream
Definition: ZipFile.cs:321
bool ExtractFile(ZipFileEntry zfe, byte[] buffer)
Copy the contents of a stored file into an opened stream
Definition: ZipFile.cs:380
bool ShouldCancel
Gets or sets a value indicating whether ShouldCancel.
uint Crc32
Gets the 32-bit checksum of entire file.
Definition: ZipFileEntry.cs:37
void Close()
Updates central directory (if pertinent) and close the Zip storage
Definition: ZipFile.cs:244
bool ExtractFile(ZipFileEntry zfe, string filename)
Copy the contents of a stored file into a physical file
Definition: ZipFile.cs:277
static uint DateTimeToDosTime(DateTime dateTime)
DOS Date and time are packed values with the following format: MS-DOS date bits description: 0-4 Day ...
Definition: ZipFile.cs:130
void Dispose()
Closes the Zip file stream
Definition: ZipFile.cs:257
Compression
Compression method enumeration
Definition: Compression.cs:20
Stream ReadFile(ZipFileEntry zfe)
Copy the contents of a stored file into an opened stream
Definition: ZipFile.cs:448
Compression Method
Gets the compression method.
Definition: ZipFileEntry.cs:67
static bool Validate(ZipFileValidationHandler validationHandler)
Ensures that the zip file is valid.
Definition: ZipFile.cs:149