zipArchive.cpp

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

More...

Namespaces:

namespace

Namespace for the zip code.

Detailed Description

  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 "platform/platform.h"
 25#include "core/util/zip/zipArchive.h"
 26
 27#include "core/stream/stream.h"
 28#include "core/stream/fileStream.h"
 29#include "core/filterStream.h"
 30#include "core/util/zip/zipCryptStream.h"
 31#include "core/crc.h"
 32//#include "core/resManager.h"
 33
 34#include "console/console.h"
 35
 36#include "core/util/zip/compressor.h"
 37#include "core/util/zip/zipTempStream.h"
 38#include "core/util/zip/zipStatFilter.h"
 39
 40#ifdef TORQUE_ZIP_AES
 41#include "core/zipAESCryptStream.h"
 42#include "core/zip/crypto/aes.h"
 43#endif
 44
 45#include "core/util/safeDelete.h"
 46
 47#include "app/version.h"
 48
 49namespace Zip
 50{
 51
 52//-----------------------------------------------------------------------------
 53// Constructor/Destructor
 54//-----------------------------------------------------------------------------
 55
 56ZipArchive::ZipArchive() :
 57   mStream(NULL),
 58   mDiskStream(NULL),
 59   mMode(Read),
 60   mRoot(NULL),
 61   mFilename(NULL)
 62{
 63}
 64
 65ZipArchive::~ZipArchive()
 66{
 67   closeArchive();
 68}
 69
 70//-----------------------------------------------------------------------------
 71// Protected Methods
 72//-----------------------------------------------------------------------------
 73
 74bool ZipArchive::readCentralDirectory()
 75{
 76   mEntries.clear();
 77   SAFE_DELETE(mRoot);
 78   mRoot = new ZipEntry;
 79   mRoot->mName = "";
 80   mRoot->mIsDirectory = true;
 81   mRoot->mCD.setFilename("");
 82
 83   if(! mEOCD.findInStream(mStream))
 84      return false;
 85
 86   if(! mEOCD.read(mStream))
 87      return false;
 88
 89   if(mEOCD.mDiskNum != mEOCD.mStartCDDiskNum ||
 90      mEOCD.mNumEntriesInThisCD != mEOCD.mTotalEntriesInCD)
 91   {
 92      if(isVerbose())
 93         Con::errorf("ZipArchive::readCentralDirectory - %s: Zips that span multiple disks are not supported.", mFilename ? mFilename : "<no filename>");
 94      return false;
 95   }
 96
 97   if(! mStream->setPosition(mEOCD.mCDOffset))
 98      return false;
 99
100   for(S32 i = 0;i < mEOCD.mNumEntriesInThisCD;++i)
101   {
102      ZipEntry *ze = new ZipEntry;
103      if(! ze->mCD.read(mStream))
104      {
105         delete ze;
106
107         if(isVerbose())
108            Con::errorf("ZipArchive::readCentralDirectory - %s: Error reading central directory.", mFilename ? mFilename : "<no filename>");
109         return false;
110      }
111
112      insertEntry(ze);
113   }
114
115   return true;
116}
117
118void ZipArchive::dumpCentralDirectory(ZipEntry* entry, String* indent)
119{
120   // if entry is null, use root
121   if (entry == NULL)
122      entry = mRoot;
123
124   if (!entry)
125      return;
126
127   String emptyIndent;
128   if (indent == NULL)
129      indent = &emptyIndent;
130
131   Con::printf("%s%s%s", indent->c_str(), entry->mIsDirectory ? "/" : "", entry->mName.c_str());
132   for (Map<String,ZipEntry*>::Iterator iter = entry->mChildren.begin();
133      iter != entry->mChildren.end();
134      ++iter)
135   {
136      String newIdent = *indent + "   ";
137      dumpCentralDirectory((*iter).value, &(newIdent));
138   }
139}
140
141//-----------------------------------------------------------------------------
142
143void ZipArchive::insertEntry(ZipEntry *ze)
144{
145   char path[1024];
146   dStrncpy(path, ze->mCD.mFilename.c_str(), sizeof(path));
147   path[sizeof(path) - 1] = 0;
148
149   for(S32 i = 0;i < dStrlen(path);++i)
150   {
151      if(path[i] == '\\')
152         path[i] = '/';
153   }
154
155   ZipEntry *root = mRoot;
156
157   char *ptr = path, *slash = NULL;
158   do
159   {
160      slash = dStrchr(ptr, '/');
161      if(slash)
162      {
163         // Add the directory
164         *slash = 0;
165
166         // try to get root, create if not found
167         ZipEntry *newEntry = NULL;
168         if (!root->mChildren.tryGetValue(ptr, newEntry))
169         {
170            newEntry = new ZipEntry;
171            newEntry->mParent = root;
172            newEntry->mName = String(ptr);
173            newEntry->mIsDirectory = true;
174            newEntry->mCD.setFilename(path);
175
176            root->mChildren[ptr] = newEntry;
177         }
178
179         root = newEntry;
180         
181         *slash = '/';
182         ptr = slash + 1;
183      }
184      else
185      {
186         // Add the file.
187         if(*ptr)
188         {
189            ze->mIsDirectory = false;
190            ze->mName = ptr;
191            ze->mParent = root;
192            root->mChildren[ptr] = ze;
193            mEntries.push_back(ze);
194         }
195         else
196         {
197            // [tom, 2/6/2007] If ptr is empty, this was a directory entry. Since
198            // we created a new entry for it above, we need to delete the old
199            // pointer otherwise it will leak as it won't have got inserted.
200
201            delete ze;
202         }
203      }
204   } while(slash);
205}
206
207void ZipArchive::removeEntry(ZipEntry *ze)
208{
209   if(ze == mRoot)
210   {
211      // [tom, 2/1/2007] We don't want to remove the root as it should always
212      // be removed through closeArchive()
213      AssertFatal(0, "ZipArchive::removeEntry - Attempting to remove the root");
214      return;
215   }
216
217   // Can't iterate the hash table, so we can't do this safely
218   AssertFatal(!ze->mIsDirectory, "ZipArchive::removeEntry - Cannot remove a directory");
219
220   // See if we have a temporary file for this entry
221   Vector<ZipTempStream *>::iterator i;
222   for(i = mTempFiles.begin();i != mTempFiles.end();++i)
223   {
224      if((*i)->getCentralDir() == &ze->mCD)
225      {
226         SAFE_DELETE(*i);
227         mTempFiles.erase(i);
228
229         break;
230      }
231   }
232   
233   // Remove from the tree
234   Vector<ZipEntry *>::iterator j;
235   for(j = mEntries.begin();j != mEntries.end();++j)
236   {
237      if(*j == ze)
238      {
239         mEntries.erase(j);
240         break;
241      }
242   }
243
244   // [tom, 2/2/2007] This must be last, as ze is no longer valid once it's
245   // removed from the parent.
246   ZipEntry *z = ze->mParent->mChildren[ze->mName];
247   ze->mParent->mChildren.erase(ze->mName);
248   delete z;
249}
250
251//-----------------------------------------------------------------------------
252
253CentralDir *ZipArchive::findFileInfo(const char *filename)
254{
255   ZipEntry *ze = findZipEntry(filename);
256   return ze ? &ze->mCD : NULL;
257}
258
259ZipArchive::ZipEntry *ZipArchive::findZipEntry(const char *filename)
260{
261   char path[1024];
262   dStrncpy(path, filename, sizeof(path));
263   path[sizeof(path) - 1] = 0;
264
265   for(S32 i = 0;i < dStrlen(path);++i)
266   {
267      if(path[i] == '\\')
268         path[i] = '/';
269   }
270
271   ZipEntry *root = mRoot;
272
273   char *ptr = path, *slash = NULL;
274   do
275   {
276      slash = dStrchr(ptr, '/');
277      if(slash)
278      {
279         // Find the directory
280         *slash = 0;
281
282         // check child dict for new root
283         ZipEntry *newRoot = NULL;
284         if (!root->mChildren.tryGetValue(ptr, newRoot))
285            return NULL;
286
287         root = newRoot;
288
289         ptr = slash + 1;
290      }
291      else
292      {
293         // Find the file
294         ZipEntry* entry = NULL;
295         if (root->mChildren.tryGetValue(ptr, entry))
296            return entry;
297      }
298   } while(slash);
299
300   return NULL;
301}
302
303//-----------------------------------------------------------------------------
304
305Stream *ZipArchive::createNewFile(const char *filename, Compressor *method)
306{
307   ZipEntry *ze = new ZipEntry;
308   ze->mIsDirectory = false;
309   ze->mCD.setFilename(filename);
310   insertEntry(ze);
311
312   ZipTempStream *stream = new ZipTempStream(&ze->mCD);
313   if(stream->open())
314   {
315      Stream *retStream = method->createWriteStream(&ze->mCD, stream);
316      if(retStream == NULL)
317      {
318         delete stream;
319         return NULL;
320      }
321
322      ZipStatFilter *filter = new ZipStatFilter(&ze->mCD);
323      if(! filter->attachStream(retStream))
324      {
325         delete filter;
326         delete retStream;
327         delete stream;
328         return NULL;
329      }
330
331      ze->mCD.mCompressMethod = method->getMethod();
332      ze->mCD.mInternalFlags |= CDFileOpen;
333
334      return filter;
335   }
336
337   return NULL;
338}
339
340void ZipArchive::updateFile(ZipTempStream *stream)
341{
342   CentralDir *cd = stream->getCentralDir();
343   
344   // [tom, 1/23/2007] Uncompressed size and CRC32 are updated by ZipStatFilter
345   cd->mCompressedSize = stream->getStreamSize();
346   cd->mInternalFlags |= CDFileDirty;
347   cd->mInternalFlags &= ~<a href="/coding/namespace/namespacezip/#namespacezip_1ad771ec68ae52065ffc275a917e05b7b3a20443c9b2b3971b1d0ecbb81874b0055">CDFileOpen</a>;
348
349   // Upper byte should be zero, lower is version as major * 10 + minor
350   cd->mVersionMadeBy = (getVersionNumber() / 100) & 0xff;
351   cd->mExtractVer = 20;
352
353   U32 dosTime = currentTimeToDOSTime();
354   cd->mModTime = dosTime & 0x0000ffff;
355   cd->mModDate = (dosTime & 0xffff0000) >> 16;
356
357   mTempFiles.push_back(stream);  
358}
359
360//-----------------------------------------------------------------------------
361
362U32 ZipArchive::localTimeToDOSTime(const Torque::Time::DateTime &dt)
363{
364   // DOS time format 
365   // http://msdn.microsoft.com/en-us/library/ms724274(VS.85).aspx
366   return TimeToDOSTime(Torque::Time(dt));
367}
368
369U32 ZipArchive::TimeToDOSTime(const Torque::Time& t)
370{
371   S32 year,month,day,hour,minute,second,microsecond;
372   t.get(&year, &month, &day, &hour, &minute, &second, &microsecond);
373
374   if(year > 1980) // De Do Do Do, De Da Da Da
375      year -= 1980;
376
377   return (((day) + (32 * (month)) + (512 * year)) << 16) | ((second/2) + (32* minute) + (2048 * (U32)hour));
378}
379
380Torque::Time ZipArchive::DOSTimeToTime(U16 time, U16 date)
381{
382   Torque::Time::DateTime dt;
383   dt.microsecond = 0;
384   dt.hour = (time & 0xF800) >> 11;
385   dt.minute = (time & 0x07E0) >> 5;
386   dt.second = (time & 0x001F)*2;
387
388   dt.year = ((date & 0xFE00) >> 9) + 1980;
389   dt.month = (date & 0x01E0) >> 5;
390   dt.day = (date & 0x001F);
391
392   return Torque::Time(dt);
393}
394
395Torque::Time ZipArchive::DOSTimeToTime(U32 dosTime)
396{
397   U16 time = dosTime & 0x0000ffff;
398   U16 date = (dosTime & 0xffff0000) >> 16;
399
400   return ZipArchive::DOSTimeToTime(time, date);
401}
402
403U32 ZipArchive::currentTimeToDOSTime()
404{
405   Torque::Time::DateTime dt;
406   Torque::Time::getCurrentDateTime(dt);
407
408   return localTimeToDOSTime(dt);
409}
410
411//-----------------------------------------------------------------------------
412
413// [tom, 1/24/2007] The general idea here is we want to create a new file,
414// copy any data from the old zip file and add the new stuff. Once the new
415// zip is created, delete the old one and rename the new one.
416
417bool ZipArchive::rebuildZip()
418{
419   String newZipName;
420   FileStream tempFile;
421   Stream *zipFile = mStream;
422
423   // FIXME [tom, 1/24/2007] Temporary for expediting testing
424   if(mFilename == NULL)
425      return false;
426
427  if(mMode == ReadWrite)
428  {
429     newZipName = String(mFilename) + ".new";
430
431     if(! tempFile.open(newZipName, mMode == Write ? Torque::FS::File::Write : Torque::FS::File::ReadWrite))
432        return false;
433
434     zipFile = &tempFile;
435  }
436
437   // Copy any unmodified files
438   for(S32 i = 0;i < mEntries.size();++i)
439   {
440      ZipEntry *entry = mEntries[i];
441
442      // Directories are internal only for lookup purposes
443      if(entry->mIsDirectory || (entry->mCD.mInternalFlags & (CDFileDirty | CDFileDeleted)))
444         continue;
445
446      copyFileToNewZip(&entry->mCD, zipFile);
447   }
448
449   // Copy any dirty files
450   for(S32 i = 0;i < mTempFiles.size();++i)
451   {
452      ZipTempStream *zts = mTempFiles[i];
453
454      writeDirtyFileToNewZip(zts, zipFile);
455      zts->close();
456      delete zts;
457      mTempFiles[i] = NULL;
458   }
459   mTempFiles.clear();
460
461   // Write central directory
462   mEOCD.mCDOffset = zipFile->getPosition();
463   mEOCD.mNumEntriesInThisCD = 0;
464   
465   for(S32 i = 0;i < mEntries.size();++i)
466   {
467      ZipEntry *entry = mEntries[i];
468
469      // [tom, 1/24/2007] Directories are internal only for lookup purposes
470      if(entry->mIsDirectory || (entry->mCD.mInternalFlags & CDFileDeleted) != 0)
471         continue;
472
473      ++mEOCD.mNumEntriesInThisCD;
474      if(! entry->mCD.write(zipFile))
475         break;
476   }
477
478   mEOCD.mCDSize = zipFile->getPosition() - mEOCD.mCDOffset;
479   mEOCD.mTotalEntriesInCD = mEOCD.mNumEntriesInThisCD;
480
481   mEOCD.mDiskNum = 0;
482   mEOCD.mStartCDDiskNum = 0;
483
484   mEOCD.write(zipFile);
485
486   if(mMode == ReadWrite)
487   {
488      // Close file, replace old zip with it
489      tempFile.close();
490
491      // [tom, 2/1/2007] The disk stream must be closed else we can't rename
492      // the file. Since rebuildZip() is only called from closeArchive() this
493      // should be safe.
494      if(mDiskStream)
495      {
496         mDiskStream->close();
497
498         delete mDiskStream;
499         mDiskStream = NULL;
500      }
501
502      String oldRename;
503      oldRename = String(mFilename) + ".old";
504      
505      if(! Torque::FS::Rename(mFilename, oldRename))
506         return false;
507
508      if(! Torque::FS::Rename(newZipName, mFilename))
509         return false;
510
511      Torque::FS::Remove(oldRename);
512   }
513   return true;
514}
515
516bool ZipArchive::writeDirtyFileToNewZip(ZipTempStream *fileStream, Stream *zipStream)
517{
518   CentralDir *cdir = fileStream->getCentralDir();
519   FileHeader fh(*cdir);
520   fh.setFilename(cdir->mFilename);
521
522   cdir->mLocalHeadOffset = zipStream->getPosition();
523
524   // Write header and file
525   if(! fh.write(zipStream))
526      return false;
527
528   if(! fileStream->rewind())
529      return false;
530
531   return zipStream->copyFrom(fileStream);
532}
533
534bool ZipArchive::copyFileToNewZip(CentralDir *cdir, Stream *newZipStream)
535{
536   // [tom, 1/24/2007] Using the stored compressor allows us to copy the raw
537   // data regardless of compression method without having to re-compress it.
538   Compressor *comp = Compressor::findCompressor(Stored);
539   if(comp == NULL)
540      return false;
541
542   if(! mStream->setPosition(cdir->mLocalHeadOffset))
543      return false;
544
545   // Copy file header
546   // FIXME [tom, 1/24/2007] This will currently not copy the extra fields
547   FileHeader fh;
548   if(! fh.read(mStream))
549      return false;
550
551   cdir->mLocalHeadOffset = newZipStream->getPosition();
552
553   if(! fh.write(newZipStream))
554      return false;
555
556   // Copy file data
557   Stream *readS = comp->createReadStream(cdir, mStream);
558   if(readS == NULL)
559      return false;
560
561   bool ret = newZipStream->copyFrom(readS);
562
563   // [tom, 1/24/2007] closeFile() just frees the relevant filters and
564   // thus it is safe to call from here.
565   closeFile(readS);
566
567   return ret;
568}
569
570//-----------------------------------------------------------------------------
571// Public Methods
572//-----------------------------------------------------------------------------
573
574void ZipArchive::setFilename(const char *filename)
575{
576   SAFE_FREE(mFilename);
577   if(filename)
578      mFilename = dStrdup(filename);
579}
580
581//-----------------------------------------------------------------------------
582
583bool ZipArchive::openArchive(const char *filename, AccessMode mode /* = Read */)
584{
585   if(mode != Read && mode != Write && mode != ReadWrite)
586      return false;
587
588   closeArchive();
589
590   mDiskStream = new FileStream;
591   if(mDiskStream->open(filename, (Torque::FS::File::AccessMode)mode))
592   {
593      setFilename(filename);
594
595      if(openArchive(mDiskStream, mode))
596         return true;
597   }
598   
599   // Cleanup just in case openArchive() failed
600   closeArchive();
601
602   return false;
603}
604
605bool ZipArchive::openArchive(Stream *stream, AccessMode mode /* = Read */)
606{
607   if(mode != Read && mode != Write && mode != ReadWrite)
608      return false;
609
610   mStream = stream;
611   mMode = mode;
612
613   if(mode == Read || mode == ReadWrite)
614   {
615      bool ret = readCentralDirectory();
616      if(mode == Read)
617         return ret;
618
619      return true;
620   }
621   else
622   {
623      mEntries.clear();
624      SAFE_DELETE(mRoot);
625      mRoot = new ZipEntry;
626      mRoot->mName = "";
627      mRoot->mIsDirectory = true;
628      mRoot->mCD.setFilename("");
629   }
630
631   return true;
632}
633
634void ZipArchive::closeArchive()
635{
636   if(mMode == Write || mMode == ReadWrite)
637      rebuildZip();
638
639   // Free any remaining temporary files
640   for(S32 i = 0;i < mTempFiles.size();++i)
641   {
642      SAFE_DELETE(mTempFiles[i]);
643   }
644   mTempFiles.clear();
645
646   // Close the zip file stream and clean up
647   if(mDiskStream)
648   {
649      mDiskStream->close();
650
651      delete mDiskStream;
652      mDiskStream = NULL;
653   }
654
655   mStream = NULL;
656
657   SAFE_FREE(mFilename);
658   SAFE_DELETE(mRoot);
659   mEntries.clear();
660}
661
662//-----------------------------------------------------------------------------
663Stream * ZipArchive::openFile(const char *filename, AccessMode mode /* = Read */)
664{
665   ZipEntry* ze = findZipEntry(filename);
666   return openFile(filename, ze, mode);
667}
668
669Stream * ZipArchive::openFile(const char *filename, ZipEntry* ze, AccessMode mode /* = Read */)
670{
671   if(mode == Read)
672   {
673      if(ze == NULL)
674         return NULL;
675
676      return openFileForRead(&ze->mCD);
677   }
678
679   if(mode == Write)
680   {
681      if(ze)
682      {
683         if(ze->mCD.mInternalFlags & CDFileOpen)
684         {
685            if(isVerbose())
686               Con::errorf("ZipArchive::openFile - File %s is already open", filename);
687            return NULL;
688         }
689         
690         // Remove the old entry so we can create a new one
691         removeEntry(ze);
692         ze = NULL;
693      }
694
695      return createNewFile(filename, Compressor::findCompressor(Deflated));
696   }
697
698   if(isVerbose())
699      Con::errorf("ZipArchive::openFile - Files within zips can only be opened as read or write, but not both at the same time.");
700
701   return NULL;
702}
703
704void ZipArchive::closeFile(Stream *stream)
705{
706   FilterStream *currentStream, *nextStream;
707
708   nextStream = dynamic_cast<FilterStream*>(stream);
709   while (nextStream)
710   {
711      currentStream = nextStream;
712      stream = currentStream->getStream();
713
714      currentStream->detachStream();
715
716      nextStream = dynamic_cast<FilterStream*>(stream);
717
718      delete currentStream;
719   }
720
721   ZipTempStream *tempStream = dynamic_cast<ZipTempStream *>(stream);
722   if(tempStream && (tempStream->getCentralDir()->mInternalFlags & CDFileOpen))
723   {
724      // [tom, 1/23/2007] This is a temporary file we are writing to
725      // so we need to update the relevant information in the header.
726      updateFile(tempStream);
727   }
728}
729
730//-----------------------------------------------------------------------------
731
732Stream *ZipArchive::openFileForRead(const CentralDir *fileCD)
733{
734   if(mMode != Read && mMode != ReadWrite)
735      return NULL;
736
737   if((fileCD->mInternalFlags & (CDFileDeleted | CDFileOpen)) != 0)
738      return NULL;
739
740   Stream *stream = mStream;
741
742   if(fileCD->mInternalFlags & CDFileDirty)
743   {
744      // File is dirty, we need to read from the temporary file
745      for(S32 i = 0;i < mTempFiles.size();++i)
746      {
747         if(mTempFiles[i]->getCentralDir() == fileCD)
748         {
749            // Found the temporary file
750            if(! mTempFiles[i]->rewind())
751            {
752               if(isVerbose())
753                  Con::errorf("ZipArchive::openFile - %s: %s is dirty, but could not rewind temporary file?", mFilename ? mFilename : "<no filename>", fileCD->mFilename.c_str());
754               return NULL;
755            }
756
757            stream = mTempFiles[i];
758            break;
759         }
760      }
761
762      if(stream == mStream)
763      {
764         if(isVerbose())
765            Con::errorf("ZipArchive::openFile - %s: %s is dirty, but no temporary file found?", mFilename ? mFilename : "<no filename>", fileCD->mFilename.c_str());
766         return NULL;
767      }
768   }
769   else
770   {
771      // Read from the zip file directly
772      if(! mStream->setPosition(fileCD->mLocalHeadOffset))
773      {
774         if(isVerbose())
775            Con::errorf("ZipArchive::openFile - %s: Could not locate local header for file %s", mFilename ? mFilename : "<no filename>", fileCD->mFilename.c_str());
776         return NULL;
777      }
778
779      FileHeader fh;
780      if(! fh.read(mStream))
781      {
782         if(isVerbose())
783            Con::errorf("ZipArchive::openFile - %s: Could not read local header for file %s", mFilename ? mFilename : "<no filename>", fileCD->mFilename.c_str());
784         return NULL;
785      }
786   }
787
788   Stream *attachTo = stream;
789   U16 compMethod = fileCD->mCompressMethod;
790
791   if(fileCD->mFlags & Encrypted)
792   {
793      if(fileCD->mCompressMethod == AESEncrypted)
794      {
795         // [tom, 1/19/2007] Whilst AES support does exist, I'm not including it in TGB
796         // to avoid having to deal with crypto export legal issues.
797         Con::errorf("ZipArchive::openFile - %s: File %s is AES encrypted, but AES is not supported in this version.", mFilename ? mFilename : "<no filename>", fileCD->mFilename.c_str());
798      }
799      else
800      {
801         ZipCryptRStream *cryptStream = new ZipCryptRStream;
802         cryptStream->setPassword(DEFAULT_ZIP_PASSWORD);
803         cryptStream->setFileEndPos(stream->getPosition() + fileCD->mCompressedSize);
804         if(! cryptStream->attachStream(stream))
805         {
806            delete cryptStream;
807            return NULL;
808         }
809
810         attachTo = cryptStream;
811      }
812   }
813
814   Compressor *comp = Compressor::findCompressor(compMethod);
815   if(comp == NULL)
816   {
817      if(isVerbose())
818         Con::errorf("ZipArchive::openFile - %s: Unsupported compression method (%d) for file %s", mFilename ? mFilename : "<no filename>", fileCD->mCompressMethod, fileCD->mFilename.c_str());
819      return NULL;
820   }
821
822   return comp->createReadStream(fileCD, attachTo);
823}
824
825//-----------------------------------------------------------------------------
826
827bool ZipArchive::addFile(const char *filename, const char *pathInZip, bool replace /* = true */)
828{
829   FileStream f;
830   if (!f.open(filename, Torque::FS::File::Read))
831      return false;
832
833   const CentralDir *cd = findFileInfo(pathInZip);
834   if(! replace && cd && (cd->mInternalFlags & CDFileDeleted) == 0)
835      return false;
836
837   Stream *dest = openFile(pathInZip, Write);
838   if(dest == NULL)
839   {
840      f.close();
841      return false;
842   }
843
844   bool ret = dest->copyFrom(&f);
845
846   closeFile(dest);
847   f.close();
848
849   return ret;
850}
851
852bool ZipArchive::extractFile(const char *pathInZip, const char *filename, bool *crcFail /* = NULL */)
853{
854   if(crcFail)
855      *crcFail = false;
856
857   const CentralDir *realCD = findFileInfo(pathInZip);
858   if(realCD == NULL)
859      return false;
860   
861   FileStream dest;
862   if(! dest.open(filename, Torque::FS::File::Write))
863      return false;
864
865   Stream *source = openFile(pathInZip, Read);
866   if(source == NULL)
867   {
868      dest.close();
869      return false;
870   }
871
872   // [tom, 2/7/2007] CRC checking the lazy man's way
873   // ZipStatFilter only fails if it doesn't have a central directory, so this is safe
874   CentralDir fakeCD;
875   ZipStatFilter zsf(&fakeCD);
876   zsf.attachStream(source);
877
878   bool ret = dest.copyFrom(&zsf);
879
880   zsf.detachStream();
881
882   if(ret && fakeCD.mCRC32 != realCD->mCRC32)
883   {
884      if(crcFail)
885         *crcFail = true;
886
887      if(isVerbose())
888         Con::errorf("ZipArchive::extractFile - CRC failure extracting file %s", pathInZip);
889      ret = false;
890   }
891   
892   closeFile(source);
893   dest.close();
894
895   return ret;
896}
897
898bool ZipArchive::deleteFile(const char *filename)
899{
900   if(mMode != Write && mMode != ReadWrite)
901      return false;
902
903   CentralDir *cd = findFileInfo(filename);
904   if(cd == NULL)
905      return false;
906
907   cd->mInternalFlags |= CDFileDeleted;
908
909   // CodeReview [tom, 2/9/2007] If this is a file we have a temporary file for,
910   // we should probably delete it here rather then waiting til the archive is closed.
911
912   return true;
913}
914
915//-----------------------------------------------------------------------------
916
917bool ZipArchive::isVerbose()
918{
919   return Con::getBoolVariable("$pref::Zip::Verbose");
920}
921
922void ZipArchive::setVerbose(bool verbose)
923{
924   Con::setBoolVariable("$pref::Zip::Verbose", verbose);
925}
926
927} // end namespace Zip
928