//----------------------------------------------------------------------------- // Copyright (c) 2013 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "io/zip/fileHeader.h" #include "io/zip/centralDir.h" #include "io/zip/compressor.h" #include "io/fileStream.h" #include "collection/simpleHashTable.h" #include "collection/vector.h" #ifndef _ZIPARCHIVE_H_ #define _ZIPARCHIVE_H_ /// @addtogroup zip_group Zip Code // @{ /// Password to use when opening encrypted zip files. Change this to whatever the password is for your zips. #define DEFAULT_ZIP_PASSWORD "changeme" class ZipTestWrite; class ZipTestRead; class ZipTestMisc; namespace Zip { /// @addtogroup zip_group Zip Code // @{ // Forward Refs class ZipTempStream; // [tom, 10/18/2006] This will be split up into a separate interface for allowing // the resource manager to handle any kind of archive relatively easily. // [tom, 2/9/2007] At least, it was designed so that it could be. It may not // actually happen for a very long time, if ever ;-) ////////////////////////////////////////////////////////////////////////// /// @brief Class for accessing Zip files /// /// ZipArchive provides two interfaces for reading or writing zip files. /// The first is a stream based interface that should be familiar to anyone /// who's ever written code, and the other is an archiver interface that /// should be familiar to anyone who's ever used a standard Zip application. /// /// The two interfaces are not mutually exclusive so you can use both if /// you wish. For example, you may want to use the stream interface to add /// a configuration file to a zip without having to create a temporary file /// and then add a number of existing files using the addFile() method. /// /// Both interfaces have their advantages and disadvantages, which will /// be discussed below. /// ///

Accessing a Zip file

/// /// Before you can access any files in the zip, you first need to open the /// archive. This is the same regardless of which interface you use, and there /// are two ways to accomplish it. /// /// Opening from a file on the file system /// /// The simplest method of opening a zip file is to use openArchive(const char *, AccessMode) /// to open a file that is on the disk. /// /// When opening a zip file on the file system, the filename is automatically set. /// /// Opening a file from a stream /// /// A more advanced way to open the zip file is from an arbitrary stream. The /// only requirements are that the stream supports seeking and was opened with /// the correct access mode. Use the openArchive(Stream *, AccessMode) method to /// do this. /// /// Opening zip files from arbitrary streams is a very powerful feature and /// opens many interesting doors. For example, combined with some small changes /// to the resource manager and startup code, it was possible to implement a /// VFS that allows the entire game to run from a single executable with no /// external files. /// /// Note that the filename is not automatically set when you open the zip file /// from a stream. The filename is used in error reporting and by the resource /// manager, so you may wish to set it to something meaningful. /// /// Regardless of which method you use to open the file, the #AccessMode controls /// what you can do with it. If you open the archive as #ReadWrite, you can both /// write to and read from files in the zip. However, it is not possible to open /// files in the zip as #ReadWrite. /// /// Closing the zip file /// /// When you are done with the zip file, call closeArchive() to free any resources /// and rebuild the zip file if it was open for #Write. /// /// Example /// /// @code /// Zip::ZipArchive za; /// if(za.openArchive("filename.zip", ZipArchive::Read)) /// { /// // ... do stuff ... /// za.closeArchive(); /// } /// @endcode /// ///

Archiver Interface

/// /// The archiver style interface allows you to add, extract and delete files in /// the zip in a way similar to that of an standard archiver application. /// /// While the archiver interface is simple to use, it is blocking and thus /// difficult to use asynchronously. If you require zip file support and /// responsive UI then you should consider using the stream interface instead. /// /// See the following method documentation for more information: /// /// /// /// Example /// /// @code /// Zip::ZipArchive za; /// if(za.openArchive("filename.zip", ZipArchive::ReadWrite)) /// { /// // Extract a file /// za.extractFile("test.txt", "test.txt"); /// // Add a file /// za.addFile("test.txt", "test.txt"); /// // Delete a file /// za.deleteFile("test.txt"); /// /// za.closeArchive(); /// } /// @endcode /// ///

Stream Interface

/// /// The stream based interface allows you to access files within the zip /// in a similar way to accessing the file system through the ResourceManager. /// /// There are a few small caveats to the stream interface: /// /// /// See the following method documentation for more information: /// /// /// /// CRC Checking /// /// Unlike the archiver interface, there is no automatic CRC checking when /// reading from files using the stream interface. If you will only be /// reading files sequentially, see the documentation for ZipStatFilter /// for a useful trick to get easy CRC checking. /// /// Example /// /// @code /// Zip::ZipArchive za; /// if(za.openArchive("filename.zip", ZipArchive::Write)) /// { /// // Write to the file /// Stream *stream; /// if(stream = za.openFile("test.txt", ZipArchive::Write)) /// { /// stream->writeLine((U8 *)"Hello, Zipped World!"); /// za.closeFile(stream); /// } /// /// za.closeArchive(); /// } /// @endcode /// ///

Compressed Files

/// /// The zip code included with stock Torque supports "stored" (uncompressed) files /// and deflate compressed files. The code is easily extensible to support any /// compression format that the Zip file format supports. /// /// In addition to the deflate and stored formats, BZip2 is supported but not /// included with stock Torque. BZip2 support will be released as a resource in /// the future. /// ///

Encrypted Files

/// /// Preliminary support for Encrypted/Passworded files is included in TGB Pro only. /// Currently, only Zip 2.0 encryption is supported by the stock code. AES support /// exists and may be released as a resource in the future. /// /// To set the password used for zips, you need to modify the #DEFAULT_ZIP_PASSWORD /// define in core/zip/zipArchive.h. This password will be used for all zips that /// require a password. The default password is changeme. This may be used by /// TGB Binary users to test encrypted zips with their game. Shipping with the /// default password is not recommended for obvious reasons. /// /// The intended use of encrypted zips is for preventing casual copying of your /// game's assets. Zip 2.0 encryption has known weaknesses that allow an attacker /// to decrypt the contents of the zip. AES encryption is significantly more secure, /// but as the password must be stored in the executable it will not stop a /// determined attacker. /// /// A script accessible mechanism for setting the password does not currently exist. /// To use encrypted mod zips, if the password was in script then the password /// would be clearly visible to anyone that cared to poke around in your scripts. /// /// Encrypted zip support will be improved in a future version. For now, a more /// secure method of storing the password is left as an exercise for the reader. /// ///

Accessing Zip files from script

/// /// ZipArchive is a C++ class and thus cannot be used from script. However, /// a wrapper is provided to allow script access to zips. See the documentation /// on ZipObject for more information. /// ///

More Examples

/// /// More in depth example code than that featured here can be found in the /// unit tests for the zip code (in the core/zip/unitTests directory) /// and the script code for the packaging utility. /// ////////////////////////////////////////////////////////////////////////// class ZipArchive { public: /// Access modes for zip files and files within the zip enum AccessMode { Read = FileStream::Read, //!< Open a zip or file in a zip for reading Write = FileStream::Write, //!< Open a zip or file in a zip for writing ReadWrite = FileStream::ReadWrite //!< Open a zip file for reading and writing. Note: Not valid for files in zips. }; protected: struct ZipEntry { ZipEntry *mParent; StringTableEntry mName; bool mIsDirectory; CentralDir mCD; SimpleHashTable mChildren; ZipEntry() { mName = ""; mIsDirectory = false; } }; Stream *mStream; FileStream *mDiskStream; AccessMode mMode; EndOfCentralDir mEOCD; // mRoot forms a tree of entries for fast queries given a file path // mEntries allows easy iteration of the entire file list ZipEntry *mRoot; VectorPtr mEntries; const char *mFilename; VectorPtr mTempFiles; bool readCentralDirectory(); void insertEntry(ZipEntry *ze); void removeEntry(ZipEntry *ze); ZipEntry *findZipEntry(const char *filename); Stream *createNewFile(const char *filename, Compressor *method); Stream *createNewFile(const char *filename, const char *method) { return createNewFile(filename, Compressor::findCompressor(method)); } Stream *createNewFile(const char *filename, S32 method) { return createNewFile(filename, Compressor::findCompressor(method)); } void updateFile(ZipTempStream *stream); bool rebuildZip(); bool copyFileToNewZip(CentralDir *cdir, Stream *newZipStream); bool writeDirtyFileToNewZip(ZipTempStream *fileStream, Stream *zipStream); U32 localTimeToDOSTime(const Platform::LocalTime &t); U32 currentTimeToDOSTime(); public: ZipArchive(); virtual ~ZipArchive(); /// @name Miscellaneous Methods // @{ ////////////////////////////////////////////////////////////////////////// /// @brief Set the filename of the zip file. /// /// The zip filename is used by the resource manager and for error reporting. /// /// Note: The filename is set automatically when you open the file. /// /// @param filename Filename of the zip file ////////////////////////////////////////////////////////////////////////// void setFilename(const char *filename); ////////////////////////////////////////////////////////////////////////// /// @brief Get the filename of the zip file. /// /// @returns Filename of the zip file, or NULL if none set ////////////////////////////////////////////////////////////////////////// const char *getFilename() { return mFilename; } ////////////////////////////////////////////////////////////////////////// /// @brief Determine if the Zip code is in verbose mode /// /// Verbose mode causes the Zip code to provide diagnostic error messages /// when things go wrong. It can be enabled or disabled through script by /// setting the $Pref::Zip::Verbose variable to true to enable it or false /// to disable it. /// /// Verbose mode is mostly useful when tracking down issues with opening /// a zip file without having to resort to using a debugger. /// /// @returns The value of $Pref::Zip::Verbose /// @see ZipArchive::setVerbose() ////////////////////////////////////////////////////////////////////////// bool isVerbose(); ////////////////////////////////////////////////////////////////////////// /// @brief Turn verbose mode on or off. /// /// This sets the $Pref::Zip::Verbose variable. /// /// See isVerbose() for a discussion on verbose mode. /// /// @param verbose True to enable verbose mode, false to disable /// @see ZipArchive::isVerbose() ////////////////////////////////////////////////////////////////////////// void setVerbose(bool verbose); // @} /// @name Archive Access Methods // @{ ////////////////////////////////////////////////////////////////////////// /// @brief Open a zip archive from a file /// /// The archive must be closed with closeArchive() when you are done with it. /// /// @param filename Filename of zip file to open /// @param mode Access mode. May be Read, Write or ReadWrite /// @return true for success, false for failure /// @see ZipArchive::openArchive(Stream *, AccessMode), ZipArchive::closeArchive() ////////////////////////////////////////////////////////////////////////// virtual bool openArchive(const char *filename, AccessMode mode = Read); ////////////////////////////////////////////////////////////////////////// /// @brief Open a zip archive from a stream /// /// The stream must support seeking and must support the specified access /// mode. For example, if the stream is opened for Read you cannot specify /// Write to openArchive(). However, if the stream is open for ReadWrite /// then you can specify any one of Read, Write or ReadWrite as the mode /// argument to openArchive(). /// /// The archive must be closed with closeArchive() when you are done with it. /// /// @param stream Pointer to stream to open the zip archive from /// @param mode Access mode. May be Read, Write or ReadWrite /// @return true for success, false for failure /// @see ZipArchive::openArchive(const char *, AccessMode), ZipArchive::closeArchive() ////////////////////////////////////////////////////////////////////////// // CodeReview [tom, 2/9/2007] I just thought, if we open a stream directly // for write then rebuilding the zip file probably won't work. This needs to // be checked and either fixed or worked around. virtual bool openArchive(Stream *stream, AccessMode mode = Read); ////////////////////////////////////////////////////////////////////////// /// @brief Close the zip archive and free any resources /// /// @see ZipArchive::openArchive(Stream *, AccessMode), ZipArchive::openArchive(const char *, AccessMode) ////////////////////////////////////////////////////////////////////////// virtual void closeArchive(); // @} /// @name Stream Based File Access Methods // @{ ////////////////////////////////////////////////////////////////////////// /// @brief Open a file within the zip file /// /// The access mode can only be Read or Write. It is not possible to open /// a file within the zip as ReadWrite. /// /// The returned stream must be freed with closeFile(). Do not delete it /// directly. /// /// In verbose mode, openFile() will display additional error information /// in the console when it fails. /// /// @param filename Filename of the file in the zip /// @param mode Access mode. May be Read or Write /// @return Pointer to stream or NULL for failure /// @see ZipArchive::closeFile(), ZipArchive::isVerbose() ////////////////////////////////////////////////////////////////////////// virtual Stream *openFile(const char *filename, AccessMode mode = Read); ////////////////////////////////////////////////////////////////////////// /// @brief Close a file opened through openFile() /// /// @param stream Stream to close /// @see ZipArchive::openFile(const char *, AccessMode) ////////////////////////////////////////////////////////////////////////// virtual void closeFile(Stream *stream); ////////////////////////////////////////////////////////////////////////// /// @brief Open a file within the zip file for read /// /// This method exists mainly for the integration with the resource manager. /// Unless there is good reason to use this method, it is better to use the /// openFile() method instead. /// /// @param fileCD Pointer to central directory of the file to open /// @return Pointer to stream or NULL for failure /// @see ZipArchive::openFile(const char *, AccessMode), ZipArchive::closeFile() ////////////////////////////////////////////////////////////////////////// Stream *openFileForRead(const CentralDir *fileCD); // @} /// @name Archiver Style File Access Methods // @{ ////////////////////////////////////////////////////////////////////////// /// @brief Add a file to the zip /// /// If replace is false and the file already exists in the zip, this function /// will fail and return false. If replace is true, the existing file will be /// overwritten. /// /// @param filename Filename on the local file system to add /// @param pathInZip The path and filename in the zip file to give this file /// @param replace true to replace existing files, false otherwise /// @return true for success, false for failure /// @see ZipArchive::extractFile(), ZipArchive::deleteFile(), ZipArchive::isVerbose() ////////////////////////////////////////////////////////////////////////// bool addFile(const char *filename, const char *pathInZip, bool replace = true); ////////////////////////////////////////////////////////////////////////// /// @brief Extract a file from the zip /// /// The file will be created through the resource manager and so must be /// in a location that is writable. /// /// The file will be CRC checked during extraction and extractFile() will /// return false if the CRC check failed. The CRC check is just an advisory, /// the output file will still exist if the CRC check failed. It is up to /// the caller to decide what to do in the event of a CRC failure. /// /// You can optionally pass a pointer to a bool to determine if a CRC check /// failed. If extractFile() returns false and crcFail is false then the failure /// was not CRC related. If crcFail is true and extractFile() returns false, /// then the CRC check failed but the file otherwise extracted OK. You can /// take your chances as to whether the data is valid or not, if you wish. /// /// In verbose mode, extractFile() will display an error in the console when /// a file fails the CRC check. /// /// @param pathInZip The path and filename in the zip file to extract /// @param filename Filename on the local file system to extract to /// @param crcFail Pointer to a boolean that receives the result of the CRC check /// @return true for success, false for failure /// @see ZipArchive::addFile(), ZipArchive::deleteFile(), ZipArchive::isVerbose() ////////////////////////////////////////////////////////////////////////// bool extractFile(const char *pathInZip, const char *filename, bool *crcFail = NULL); ////////////////////////////////////////////////////////////////////////// /// @brief Delete a file from the zip /// /// Flags a file as deleted so it is removed when the zip file is rebuilt. /// /// @param filename Filename in the zip to delete /// @return true for success, false for failure /// @see ZipArchive::addFile(), ZipArchive::extractFile(), ZipArchive::isVerbose() ////////////////////////////////////////////////////////////////////////// bool deleteFile(const char *filename); // @} /// @name Enumeration Methods // @{ ////////////////////////////////////////////////////////////////////////// /// @brief Get number of entries in the central directory /// /// @see ZipArchive::findFileInfo(const char *) ////////////////////////////////////////////////////////////////////////// U32 numEntries() const { return mEntries.size(); } ////////////////////////////////////////////////////////////////////////// /// Get a central directory entry ////////////////////////////////////////////////////////////////////////// const CentralDir & operator[](const U32 idx) const { return mEntries[idx]->mCD; } ////////////////////////////////////////////////////////////////////////// /// @brief Find a file in the zip /// /// @param filename Path and filename to find /// @return Pointer to the central directory entry ////////////////////////////////////////////////////////////////////////// CentralDir *findFileInfo(const char *filename); // @} }; // @} } // end namespace Zip // @} #endif // _ZIPARCHIVE_H_