zipArchive.h

Engine/source/core/util/zip/zipArchive.h

More...

Classes:

class

Class for accessing Zip files.

Namespaces:

namespace

Namespace for the zip code.

Public Defines

define
DEFAULT_ZIP_PASSWORD() "changeme"

Password to use when opening encrypted zip files. Change this to whatever the password is for your zips.

Detailed Description

Public Defines

DEFAULT_ZIP_PASSWORD() "changeme"

Password to use when opening encrypted zip files. Change this to whatever the password is for your zips.

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2012 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24#include "core/util/zip/fileHeader.h"
 25#include "core/util/zip/centralDir.h"
 26#include "core/util/zip/compressor.h"
 27
 28#include "core/stream/fileStream.h"
 29
 30#include "core/util/tVector.h"
 31#include "core/util/tDictionary.h"
 32#include "core/util/timeClass.h"
 33
 34#ifndef _ZIPARCHIVE_H_
 35#define _ZIPARCHIVE_H_
 36
 37/// @addtogroup zip_group Zip Code
 38// @{
 39
 40/// Password to use when opening encrypted zip files. Change this to whatever the password is for your zips.
 41#define DEFAULT_ZIP_PASSWORD     "changeme"
 42
 43class ZipTestWrite;
 44class ZipTestRead;
 45class ZipTestMisc;
 46
 47namespace Zip
 48{
 49
 50/// @addtogroup zip_group Zip Code
 51// @{
 52
 53// Forward Refs
 54class ZipTempStream;
 55
 56// [tom, 10/18/2006] This will be split up into a separate interface for allowing
 57// the resource manager to handle any kind of archive relatively easily.
 58
 59// [tom, 2/9/2007] At least, it was designed so that it could be. It may not
 60// actually happen for a very long time, if ever ;-)
 61
 62//-----------------------------------------------------------------------------
 63/// @brief Class for accessing Zip files
 64///
 65/// ZipArchive provides two interfaces for reading or writing zip files.
 66/// The first is a stream based interface that should be familiar to anyone
 67/// who's ever written code, and the other is an archiver interface that
 68/// should be familiar to anyone who's ever used a standard Zip application.
 69///
 70/// The two interfaces are not mutually exclusive so you can use both if
 71/// you wish. For example, you may want to use the stream interface to add
 72/// a configuration file to a zip without having to create a temporary file
 73/// and then add a number of existing files using the addFile() method.
 74///
 75/// Both interfaces have their advantages and disadvantages, which will
 76/// be discussed below.
 77///
 78/// <h3>Accessing a Zip file</h3>
 79///
 80/// Before you can access any files in the zip, you first need to open the
 81/// archive. This is the same regardless of which interface you use, and there
 82/// are two ways to accomplish it.
 83///
 84/// <b>Opening from a file on the file system</b>
 85/// 
 86/// The simplest method of opening a zip file is to use openArchive(const char *, AccessMode)
 87/// to open a file that is on the disk. 
 88/// 
 89/// When opening a zip file on the file system, the filename is automatically set.
 90///
 91/// <b>Opening a file from a stream</b>
 92/// 
 93/// A more advanced way to open the zip file is from an arbitrary stream. The
 94/// only requirements are that the stream supports seeking and was opened with
 95/// the correct access mode. Use the openArchive(Stream *, AccessMode) method to
 96/// do this.
 97/// 
 98/// Opening zip files from arbitrary streams is a very powerful feature and
 99/// opens many interesting doors. For example, combined with some small changes
100/// to the resource manager and startup code, it was possible to implement a
101/// VFS that allows the entire game to run from a single executable with no
102/// external files.
103/// 
104/// Note that the filename is not automatically set when you open the zip file
105/// from a stream. The filename is used in error reporting and by the resource
106/// manager, so you may wish to set it to something meaningful.
107/// 
108/// Regardless of which method you use to open the file, the #AccessMode controls
109/// what you can do with it. If you open the archive as #ReadWrite, you can both
110/// write to and read from files in the zip. However, it is not possible to open
111/// files in the zip as #ReadWrite.
112/// 
113/// <b>Closing the zip file</b>
114/// 
115/// When you are done with the zip file, call closeArchive() to free any resources
116/// and rebuild the zip file if it was open for #Write.
117/// 
118/// <b>Example</b>
119/// 
120/// @code
121/// Zip::ZipArchive za;
122/// if(za.openArchive("filename.zip", ZipArchive::Read))
123/// {
124///    // ... do stuff ...
125///    za.closeArchive();
126/// }
127/// @endcode
128///
129/// <h3>Archiver Interface</h3>
130/// 
131/// The archiver style interface allows you to add, extract and delete files in
132/// the zip in a way similar to that of an standard archiver application.
133/// 
134/// While the archiver interface is simple to use, it is blocking and thus
135/// difficult to use asynchronously. If you require zip file support and
136/// responsive UI then you should consider using the stream interface instead.
137/// 
138/// See the following method documentation for more information:
139/// 
140/// <ul>
141///   <li> addFile()
142///   <li> extractFile()
143///   <li> deleteFile()
144/// </ul>
145///
146/// <b>Example</b>
147/// 
148/// @code
149/// Zip::ZipArchive za;
150/// if(za.openArchive("filename.zip", ZipArchive::ReadWrite))
151/// {
152///    // Extract a file
153///    za.extractFile("test.txt", "test.txt");
154///    // Add a file
155///    za.addFile("test.txt", "test.txt");
156///    // Delete a file
157///    za.deleteFile("test.txt");
158///
159///    za.closeArchive();
160/// }
161/// @endcode
162/// 
163/// <h3>Stream Interface</h3>
164/// 
165/// The stream based interface allows you to access files within the zip
166/// in a similar way to accessing the file system through the ResourceManager.
167/// 
168/// There are a few small caveats to the stream interface:
169/// <ul>
170///   <li> When writing files, the whole file must be written sequentially. You
171///        cannot seek in the stream.
172///   <li> It may or may not be possible to seek in streams opened for read.
173///        Files that were not compressed in the zip file support seeking with
174///        no penalty. In all cases where the file is compressed, if seeking is
175///        supported by the decompression and/or decryption filter then it
176///        carries with it an extreme performance penalty and should be avoided.
177///        All currently available decompression filters (Deflate and BZip2) and
178///        decryption filters (Zip 2.0 and AES) support seeking, but have to reset
179///        their state and re-decompress/decrypt the entire file up to the point
180///        you are seeking to. An extreme example would be that if you had a
181///        20MB file and were currently at the end of the file, seeking back 1 byte
182///        of the file would cause the entire file to be decompressed again. This
183///        would be a blocking operation that would lock Torque up for an appreciable
184///        chunk of time.
185///   <li> Files can only be open as #Read or #Write, but not #ReadWrite
186///   <li> Only one file can be open for read at a time, but multiple files can
187///        be open for write at a time. - [tom, 2/9/2007] Check this
188/// </ul>
189/// 
190/// See the following method documentation for more information:
191/// 
192/// <ul>
193///   <li> openFile()
194///   <li> closeFile()
195/// </ul>
196///
197/// <b>CRC Checking</b>
198/// 
199/// Unlike the archiver interface, there is no automatic CRC checking when
200/// reading from files using the stream interface. If you will only be
201/// reading files sequentially, see the documentation for ZipStatFilter
202/// for a useful trick to get easy CRC checking.
203///
204/// <b>Example</b>
205/// 
206/// @code
207/// Zip::ZipArchive za;
208/// if(za.openArchive("filename.zip", ZipArchive::Write))
209/// {
210///    // Write to the file
211///    Stream *stream;
212///    if(stream = za.openFile("test.txt", ZipArchive::Write))
213///    {
214///       stream->writeLine((U8 *)"Hello, Zipped World!");
215///       za.closeFile(stream);
216///    }
217///
218///    za.closeArchive();
219/// }
220/// @endcode
221/// 
222/// <h3>Compressed Files</h3>
223/// 
224/// The zip code included with stock Torque supports "stored" (uncompressed) files
225/// and deflate compressed files. The code is easily extensible to support any
226/// compression format that the Zip file format supports.
227/// 
228/// In addition to the deflate and stored formats, BZip2 is supported but not
229/// included with stock Torque. BZip2 support will be released as a resource in
230/// the future.
231/// 
232/// <h3>Encrypted Files</h3>
233/// 
234/// Preliminary support for Encrypted/Passworded files is included in TGB Pro only.
235/// Currently, only Zip 2.0 encryption is supported by the stock code. AES support
236/// exists and may be released as a resource in the future.
237/// 
238/// To set the password used for zips, you need to modify the #DEFAULT_ZIP_PASSWORD
239/// define in core/zip/zipArchive.h. This password will be used for all zips that
240/// require a password. The default password is changeme. This may be used by
241/// TGB Binary users to test encrypted zips with their game. Shipping with the
242/// default password is not recommended for obvious reasons.
243/// 
244/// The intended use of encrypted zips is for preventing casual copying of your
245/// game's assets. Zip 2.0 encryption has known weaknesses that allow an attacker
246/// to decrypt the contents of the zip. AES encryption is significantly more secure,
247/// but as the password must be stored in the executable it will not stop a
248/// determined attacker.
249/// 
250/// A script accessible mechanism for setting the password does not currently exist.
251/// To use encrypted mod zips, if the password was in script then the password
252/// would be clearly visible to anyone that cared to poke around in your scripts.
253/// 
254/// Encrypted zip support will be improved in a future version. For now, a more
255/// secure method of storing the password is left as an exercise for the reader.
256/// 
257/// <h3>Accessing Zip files from script</h3>
258/// 
259/// ZipArchive is a C++ class and thus cannot be used from script. However,
260/// a wrapper is provided to allow script access to zips. See the documentation
261/// on ZipObject for more information.
262///
263/// <h3>More Examples</h3>
264/// 
265/// More in depth example code than that featured here can be found in the
266/// unit tests for the zip code (in the core/zip/unitTests directory)
267/// and the script code for the packaging utility.
268/// 
269//-----------------------------------------------------------------------------
270class ZipArchive : public StrongRefBase
271{
272   friend class ::ZipTestWrite;
273   friend class ::ZipTestRead;
274   friend class ::ZipTestMisc;
275
276public:
277   /// Access modes for zip files and files within the zip
278   enum AccessMode
279   {
280      Read = Torque::FS::File::Read,               //!< Open a zip or file in a zip for reading
281      Write = Torque::FS::File::Write,             //!< Open a zip or file in a zip for writing
282      ReadWrite = Torque::FS::File::ReadWrite      //!< Open a zip file for reading and writing. <b>Note</b>: Not valid for files in zips.
283   };
284
285   struct ZipEntry
286   {
287      ZipEntry *mParent;
288      
289      String mName;
290
291      bool mIsDirectory;
292      CentralDir mCD;
293      
294      Map<String,ZipEntry*> mChildren;
295
296      ZipEntry()
297      {
298         mName = "";
299         mIsDirectory = false;
300         mParent = NULL;
301      }
302   };
303protected:
304
305   Stream *mStream;
306   FileStream *mDiskStream;
307   AccessMode mMode;
308
309   EndOfCentralDir mEOCD;
310
311   // mRoot forms a tree of entries for fast queries given a file path
312   // mEntries allows easy iteration of the entire file list
313   ZipEntry *mRoot;
314   Vector<ZipEntry*> mEntries;
315
316   const char *mFilename;
317
318   Vector<ZipTempStream*> mTempFiles;
319
320   bool readCentralDirectory();
321
322   void insertEntry(ZipEntry *ze);
323   void removeEntry(ZipEntry *ze);
324   
325   Stream *createNewFile(const char *filename, Compressor *method);
326   Stream *createNewFile(const char *filename, const char *method)
327   {
328      return createNewFile(filename, Compressor::findCompressor(method));
329   }
330   Stream *createNewFile(const char *filename, S32 method)
331   {
332      return createNewFile(filename, Compressor::findCompressor(method));
333   }
334
335   void updateFile(ZipTempStream *stream);
336   bool rebuildZip();
337   bool copyFileToNewZip(CentralDir *cdir, Stream *newZipStream);
338   bool writeDirtyFileToNewZip(ZipTempStream *fileStream, Stream *zipStream);
339
340public:
341   ZipEntry* getRoot() { return mRoot; }
342   ZipEntry* findZipEntry(const char *filename);
343   static U32 localTimeToDOSTime(const Torque::Time::DateTime &dt);
344   static Torque::Time DOSTimeToTime(U16 time, U16 date);
345   static Torque::Time DOSTimeToTime(U32 datetime);
346   static U32 TimeToDOSTime(const Torque::Time& t);
347   static U32 currentTimeToDOSTime();
348   void dumpCentralDirectory(ZipEntry* entry = NULL, String* indent = NULL);
349
350public:
351   ZipArchive();
352   virtual ~ZipArchive();
353
354   /// @name Miscellaneous Methods
355   // @{
356   
357   //-----------------------------------------------------------------------------
358   /// @brief Set the filename of the zip file.
359   ///
360   /// The zip filename is used by the resource manager and for error reporting.
361   ///
362   /// <b>Note</b>: The filename is set automatically when you open the file.
363   ///
364   /// @param filename Filename of the zip file
365   //-----------------------------------------------------------------------------
366   void setFilename(const char *filename);
367
368   /// Set the disk stream pointer.  The ZipArchive is then responsible for 
369   /// deleting the stream when appropriate and the caller should not do the same.
370   /// This function should only be called after openArchive(Stream*) has been 
371   /// successfully executed.
372   void setDiskStream(FileStream* stream) { mDiskStream = stream; }
373
374   //-----------------------------------------------------------------------------
375   /// @brief Get the filename of the zip file.
376   ///
377   /// @returns Filename of the zip file, or NULL if none set
378   //-----------------------------------------------------------------------------
379   const char *getFilename()                          { return mFilename; }
380
381   //-----------------------------------------------------------------------------
382   /// @brief Determine if the Zip code is in verbose mode
383   ///
384   /// Verbose mode causes the Zip code to provide diagnostic error messages
385   /// when things go wrong. It can be enabled or disabled through script by
386   /// setting the $pref::Zip::Verbose variable to true to enable it or false
387   /// to disable it.
388   ///
389   /// Verbose mode is mostly useful when tracking down issues with opening
390   /// a zip file without having to resort to using a debugger.
391   ///
392   /// @returns The value of $pref::Zip::Verbose
393   /// @see ZipArchive::setVerbose()
394   //-----------------------------------------------------------------------------
395   bool isVerbose();
396
397   //-----------------------------------------------------------------------------
398   /// @brief Turn verbose mode on or off.
399   ///
400   /// This sets the $pref::Zip::Verbose variable.
401   ///
402   /// See isVerbose() for a discussion on verbose mode.
403   ///
404   /// @param verbose True to enable verbose mode, false to disable
405   /// @see ZipArchive::isVerbose()
406   //-----------------------------------------------------------------------------
407   void setVerbose(bool verbose);
408   // @}
409
410   /// @name Archive Access Methods
411   // @{
412
413   //-----------------------------------------------------------------------------
414   /// @brief Open a zip archive from a file
415   ///
416   /// The archive must be closed with closeArchive() when you are done with it.
417   ///
418   /// @param filename Filename of zip file to open
419   /// @param mode Access mode. May be Read, Write or ReadWrite
420   /// @return true for success, false for failure
421   /// @see ZipArchive::openArchive(Stream *, AccessMode), ZipArchive::closeArchive()
422   //-----------------------------------------------------------------------------
423   virtual bool openArchive(const char *filename, AccessMode mode = Read);
424
425   //-----------------------------------------------------------------------------
426   /// @brief Open a zip archive from a stream
427   ///
428   /// The stream must support seeking and must support the specified access
429   /// mode. For example, if the stream is opened for Read you cannot specify
430   /// Write to openArchive(). However, if the stream is open for ReadWrite
431   /// then you can specify any one of Read, Write or ReadWrite as the mode
432   /// argument to openArchive().
433   ///
434   /// The archive must be closed with closeArchive() when you are done with it.
435   ///
436   /// @param stream Pointer to stream to open the zip archive from
437   /// @param mode Access mode. May be Read, Write or ReadWrite
438   /// @return true for success, false for failure
439   /// @see ZipArchive::openArchive(const char *, AccessMode), ZipArchive::closeArchive()
440   //-----------------------------------------------------------------------------
441
442   // CodeReview [tom, 2/9/2007] I just thought, if we open a stream directly
443   // for write then rebuilding the zip file probably won't work. This needs to
444   // be checked and either fixed or worked around.
445
446   virtual bool openArchive(Stream *stream, AccessMode mode = Read);
447
448   //-----------------------------------------------------------------------------
449   /// @brief Close the zip archive and free any resources
450   ///
451   /// @see ZipArchive::openArchive(Stream *, AccessMode), ZipArchive::openArchive(const char *, AccessMode)
452   //-----------------------------------------------------------------------------
453   virtual void closeArchive();
454   // @}
455
456   /// @name Stream Based File Access Methods
457   // @{
458
459   //-----------------------------------------------------------------------------
460   /// @brief Open a file within the zip file
461   ///
462   /// The access mode can only be Read or Write. It is not possible to open
463   /// a file within the zip as ReadWrite.
464   ///
465   /// The returned stream must be freed with closeFile(). Do not delete it
466   /// directly.
467   ///
468   /// In verbose mode, openFile() will display additional error information 
469   /// in the console when it fails.
470   ///
471   /// @param filename Filename of the file in the zip
472   /// @param mode Access mode. May be Read or Write
473   /// @param ze Output zip entry.  May be unspecified.
474   /// @return Pointer to stream or NULL for failure
475   /// @see ZipArchive::closeFile(), ZipArchive::isVerbose()
476   //-----------------------------------------------------------------------------
477   virtual Stream *openFile(const char *filename, AccessMode mode = Read);
478   virtual Stream *openFile(const char *filename, ZipEntry* ze, AccessMode = Read);
479
480   //-----------------------------------------------------------------------------
481   /// @brief Close a file opened through openFile()
482   ///
483   /// @param stream Stream to close
484   /// @see ZipArchive::openFile(const char *, AccessMode)
485   //-----------------------------------------------------------------------------
486   virtual void closeFile(Stream *stream);
487
488   //-----------------------------------------------------------------------------
489   /// @brief Open a file within the zip file for read
490   ///
491   /// This method exists mainly for the integration with the resource manager.
492   /// Unless there is good reason to use this method, it is better to use the
493   /// openFile() method instead.
494   ///
495   /// @param fileCD Pointer to central directory of the file to open
496   /// @return Pointer to stream or NULL for failure
497   /// @see ZipArchive::openFile(const char *, AccessMode), ZipArchive::closeFile()
498   //-----------------------------------------------------------------------------
499   Stream *openFileForRead(const CentralDir *fileCD);
500   // @}
501
502   /// @name Archiver Style File Access Methods
503   // @{
504   
505   //-----------------------------------------------------------------------------
506   /// @brief Add a file to the zip
507   ///
508   /// If replace is false and the file already exists in the zip, this function
509   /// will fail and return false. If replace is true, the existing file will be
510   /// overwritten.
511   ///
512   /// @param filename Filename on the local file system to add
513   /// @param pathInZip The path and filename in the zip file to give this file
514   /// @param replace true to replace existing files, false otherwise
515   /// @return true for success, false for failure
516   /// @see ZipArchive::extractFile(), ZipArchive::deleteFile(), ZipArchive::isVerbose()
517   //-----------------------------------------------------------------------------
518   bool addFile(const char *filename, const char *pathInZip, bool replace = true);
519
520   //-----------------------------------------------------------------------------
521   /// @brief Extract a file from the zip
522   ///
523   /// The file will be created through the resource manager and so must be
524   /// in a location that is writable.
525   ///
526   /// The file will be CRC checked during extraction and extractFile() will
527   /// return false if the CRC check failed. The CRC check is just an advisory,
528   /// the output file will still exist if the CRC check failed. It is up to
529   /// the caller to decide what to do in the event of a CRC failure.
530   ///
531   /// You can optionally pass a pointer to a bool to determine if a CRC check
532   /// failed. If extractFile() returns false and crcFail is false then the failure
533   /// was not CRC related. If crcFail is true and extractFile() returns false,
534   /// then the CRC check failed but the file otherwise extracted OK. You can
535   /// take your chances as to whether the data is valid or not, if you wish.
536   ///
537   /// In verbose mode, extractFile() will display an error in the console when
538   /// a file fails the CRC check.
539   ///
540   /// @param pathInZip The path and filename in the zip file to extract
541   /// @param filename Filename on the local file system to extract to
542   /// @param crcFail Pointer to a boolean that receives the result of the CRC check
543   /// @return true for success, false for failure
544   /// @see ZipArchive::addFile(), ZipArchive::deleteFile(), ZipArchive::isVerbose()
545   //-----------------------------------------------------------------------------
546   bool extractFile(const char *pathInZip, const char *filename, bool *crcFail = NULL);
547
548   //-----------------------------------------------------------------------------
549   /// @brief Delete a file from the zip
550   ///
551   /// Flags a file as deleted so it is removed when the zip file is rebuilt.
552   ///
553   /// @param filename Filename in the zip to delete
554   /// @return true for success, false for failure
555   /// @see ZipArchive::addFile(), ZipArchive::extractFile(), ZipArchive::isVerbose()
556   //-----------------------------------------------------------------------------
557   bool deleteFile(const char *filename);
558   // @}
559
560   /// @name Enumeration Methods
561   // @{
562
563   //-----------------------------------------------------------------------------
564   /// @brief Get number of entries in the central directory
565   ///
566   /// @see ZipArchive::findFileInfo(const char *)
567   //-----------------------------------------------------------------------------
568   U32 numEntries() const                             { return mEntries.size(); }
569
570   //-----------------------------------------------------------------------------
571   /// Get a central directory entry
572   //-----------------------------------------------------------------------------
573   const CentralDir & operator[](const U32 idx) const { return mEntries[idx]->mCD; }
574
575   //-----------------------------------------------------------------------------
576   /// @brief Find a file in the zip
577   ///
578   /// @param filename Path and filename to find
579   /// @return Pointer to the central directory entry
580   //-----------------------------------------------------------------------------
581   CentralDir *findFileInfo(const char *filename);
582   // @}
583};
584
585// @}
586
587} // end namespace Zip
588
589// @}
590
591#endif // _ZIPARCHIVE_H_
592