zipArchive.h 23 KB

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