123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571 |
- //-----------------------------------------------------------------------------
- // 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.
- ///
- /// <h3>Accessing a Zip file</h3>
- ///
- /// 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.
- ///
- /// <b>Opening from a file on the file system</b>
- ///
- /// 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.
- ///
- /// <b>Opening a file from a stream</b>
- ///
- /// 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.
- ///
- /// <b>Closing the zip file</b>
- ///
- /// 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.
- ///
- /// <b>Example</b>
- ///
- /// @code
- /// Zip::ZipArchive za;
- /// if(za.openArchive("filename.zip", ZipArchive::Read))
- /// {
- /// // ... do stuff ...
- /// za.closeArchive();
- /// }
- /// @endcode
- ///
- /// <h3>Archiver Interface</h3>
- ///
- /// 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:
- ///
- /// <ul>
- /// <li> addFile()
- /// <li> extractFile()
- /// <li> deleteFile()
- /// </ul>
- ///
- /// <b>Example</b>
- ///
- /// @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
- ///
- /// <h3>Stream Interface</h3>
- ///
- /// 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:
- /// <ul>
- /// <li> When writing files, the whole file must be written sequentially. You
- /// cannot seek in the stream.
- /// <li> It may or may not be possible to seek in streams opened for read.
- /// Files that were not compressed in the zip file support seeking with
- /// no penalty. In all cases where the file is compressed, if seeking is
- /// supported by the decompression and/or decryption filter then it
- /// carries with it an extreme performance penalty and should be avoided.
- /// All currently available decompression filters (Deflate and BZip2) and
- /// decryption filters (Zip 2.0 and AES) support seeking, but have to reset
- /// their state and re-decompress/decrypt the entire file up to the point
- /// you are seeking to. An extreme example would be that if you had a
- /// 20MB file and were currently at the end of the file, seeking back 1 byte
- /// of the file would cause the entire file to be decompressed again. This
- /// would be a blocking operation that would lock Torque up for an appreciable
- /// chunk of time.
- /// <li> Files can only be open as #Read or #Write, but not #ReadWrite
- /// <li> Only one file can be open for read at a time, but multiple files can
- /// be open for write at a time. - [tom, 2/9/2007] Check this
- /// </ul>
- ///
- /// See the following method documentation for more information:
- ///
- /// <ul>
- /// <li> openFile()
- /// <li> closeFile()
- /// </ul>
- ///
- /// <b>CRC Checking</b>
- ///
- /// 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.
- ///
- /// <b>Example</b>
- ///
- /// @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
- ///
- /// <h3>Compressed Files</h3>
- ///
- /// 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.
- ///
- /// <h3>Encrypted Files</h3>
- ///
- /// 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.
- ///
- /// <h3>Accessing Zip files from script</h3>
- ///
- /// 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.
- ///
- /// <h3>More Examples</h3>
- ///
- /// 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. <b>Note</b>: Not valid for files in zips.
- };
- protected:
- struct ZipEntry
- {
- ZipEntry *mParent;
-
- StringTableEntry mName;
- bool mIsDirectory;
- CentralDir mCD;
-
- SimpleHashTable<struct ZipEntry> 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<ZipEntry *> mEntries;
- const char *mFilename;
- VectorPtr<ZipTempStream *> 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.
- ///
- /// <b>Note</b>: 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_
|