zipArchive.h
Engine/source/core/util/zip/zipArchive.h
Classes:
class
Class for accessing Zip files.
Namespaces:
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