1 // Copyright (C) 2002-2012 Nikolaus Gebhardt
\r
2 // This file is part of the "Irrlicht Engine".
\r
3 // For conditions of distribution and use, see copyright notice in irrlicht.h
\r
5 #include "CZipReader.h"
\r
10 #include "CFileList.h"
\r
11 #include "CReadFile.h"
\r
12 #include "coreutil.h"
\r
14 #include "IrrCompileConfig.h"
\r
15 #include <zlib.h> // use system lib
\r
23 // -----------------------------------------------------------------------------
\r
25 // -----------------------------------------------------------------------------
\r
28 CArchiveLoaderZIP::CArchiveLoaderZIP(io::IFileSystem* fs)
\r
32 setDebugName("CArchiveLoaderZIP");
\r
36 //! returns true if the file maybe is able to be loaded by this class
\r
37 bool CArchiveLoaderZIP::isALoadableFileFormat(const io::path& filename) const
\r
39 return core::hasFileExtension(filename, "zip", "pk3") ||
\r
40 core::hasFileExtension(filename, "gz", "tgz");
\r
43 //! Check to see if the loader can create archives of this type.
\r
44 bool CArchiveLoaderZIP::isALoadableFileFormat(E_FILE_ARCHIVE_TYPE fileType) const
\r
46 return (fileType == EFAT_ZIP || fileType == EFAT_GZIP);
\r
50 //! Creates an archive from the filename
\r
51 /** \param file File handle to check.
\r
52 \return Pointer to newly created archive, or 0 upon error. */
\r
53 IFileArchive* CArchiveLoaderZIP::createArchive(const io::path& filename, bool ignoreCase, bool ignorePaths) const
\r
55 IFileArchive *archive = 0;
\r
56 io::IReadFile* file = FileSystem->createAndOpenFile(filename);
\r
60 archive = createArchive(file, ignoreCase, ignorePaths);
\r
67 //! creates/loads an archive from the file.
\r
68 //! \return Pointer to the created archive. Returns 0 if loading failed.
\r
69 IFileArchive* CArchiveLoaderZIP::createArchive(io::IReadFile* file, bool ignoreCase, bool ignorePaths) const
\r
71 IFileArchive *archive = 0;
\r
77 file->read(&sig, 2);
\r
79 #ifdef __BIG_ENDIAN__
\r
80 sig = os::Byteswap::byteswap(sig);
\r
85 bool isGZip = (sig == 0x8b1f);
\r
87 archive = new CZipReader(FileSystem, file, ignoreCase, ignorePaths, isGZip);
\r
92 //! Check if the file might be loaded by this class
\r
93 /** Check might look into the file.
\r
94 \param file File handle to check.
\r
95 \return True if file seems to be loadable. */
\r
96 bool CArchiveLoaderZIP::isALoadableFileFormat(io::IReadFile* file) const
\r
98 SZIPFileHeader header;
\r
100 file->read( &header.Sig, 4 );
\r
101 #ifdef __BIG_ENDIAN__
\r
102 header.Sig = os::Byteswap::byteswap(header.Sig);
\r
105 return header.Sig == 0x04034b50 || // ZIP
\r
106 (header.Sig&0xffff) == 0x8b1f; // gzip
\r
109 // -----------------------------------------------------------------------------
\r
111 // -----------------------------------------------------------------------------
\r
113 CZipReader::CZipReader(IFileSystem* fs, IReadFile* file, bool ignoreCase, bool ignorePaths, bool isGZip)
\r
114 : CFileList((file ? file->getFileName() : io::path("")), ignoreCase, ignorePaths), FileSystem(fs), File(file), IsGZip(isGZip)
\r
117 setDebugName("CZipReader");
\r
124 // load file entries
\r
126 while (scanGZipHeader()) { }
\r
128 while (scanZipHeader()) { }
\r
134 CZipReader::~CZipReader()
\r
141 //! get the archive type
\r
142 E_FILE_ARCHIVE_TYPE CZipReader::getType() const
\r
144 return IsGZip ? EFAT_GZIP : EFAT_ZIP;
\r
147 const IFileList* CZipReader::getFileList() const
\r
153 //! scans for a local header, returns false if there is no more local file header.
\r
154 //! The gzip file format seems to think that there can be multiple files in a gzip file
\r
156 bool CZipReader::scanGZipHeader()
\r
158 SZipFileEntry entry;
\r
160 memset(&entry.header, 0, sizeof(SZIPFileHeader));
\r
163 SGZIPMemberHeader header;
\r
164 if (File->read(&header, sizeof(SGZIPMemberHeader)) == sizeof(SGZIPMemberHeader))
\r
167 #ifdef __BIG_ENDIAN__
\r
168 header.sig = os::Byteswap::byteswap(header.sig);
\r
169 header.time = os::Byteswap::byteswap(header.time);
\r
172 // check header value
\r
173 if (header.sig != 0x8b1f)
\r
176 // now get the file info
\r
177 if (header.flags & EGZF_EXTRA_FIELDS)
\r
179 // read lenth of extra data
\r
182 File->read(&dataLen, 2);
\r
184 #ifdef __BIG_ENDIAN__
\r
185 dataLen = os::Byteswap::byteswap(dataLen);
\r
189 File->seek(dataLen, true);
\r
192 io::path ZipFileName = "";
\r
194 if (header.flags & EGZF_FILE_NAME)
\r
200 ZipFileName.append(c);
\r
207 ZipFileName = Path;
\r
208 core::deletePathFromFilename(ZipFileName);
\r
210 // rename tgz to tar or remove gz extension
\r
211 if (core::hasFileExtension(ZipFileName, "tgz"))
\r
213 ZipFileName[ ZipFileName.size() - 2] = 'a';
\r
214 ZipFileName[ ZipFileName.size() - 1] = 'r';
\r
216 else if (core::hasFileExtension(ZipFileName, "gz"))
\r
218 ZipFileName[ ZipFileName.size() - 3] = 0;
\r
219 ZipFileName.validate();
\r
223 if (header.flags & EGZF_COMMENT)
\r
230 if (header.flags & EGZF_CRC16)
\r
231 File->seek(2, true);
\r
233 // we are now at the start of the data blocks
\r
234 entry.Offset = File->getPos();
\r
236 entry.header.FilenameLength = ZipFileName.size();
\r
238 entry.header.CompressionMethod = header.compressionMethod;
\r
239 entry.header.DataDescriptor.CompressedSize = (File->getSize() - 8) - File->getPos();
\r
241 // seek to file end
\r
242 File->seek(entry.header.DataDescriptor.CompressedSize, true);
\r
245 File->read(&entry.header.DataDescriptor.CRC32, 4);
\r
246 // read uncompressed size
\r
247 File->read(&entry.header.DataDescriptor.UncompressedSize, 4);
\r
249 #ifdef __BIG_ENDIAN__
\r
250 entry.header.DataDescriptor.CRC32 = os::Byteswap::byteswap(entry.header.DataDescriptor.CRC32);
\r
251 entry.header.DataDescriptor.UncompressedSize = os::Byteswap::byteswap(entry.header.DataDescriptor.UncompressedSize);
\r
254 // now we've filled all the fields, this is just a standard deflate block
\r
255 addItem(ZipFileName, entry.Offset, entry.header.DataDescriptor.UncompressedSize, false, 0);
\r
256 FileInfo.push_back(entry);
\r
259 // there's only one block of data in a gzip file
\r
263 //! scans for a local header, returns false if there is no more local file header.
\r
264 bool CZipReader::scanZipHeader(bool ignoreGPBits)
\r
266 io::path ZipFileName = "";
\r
267 SZipFileEntry entry;
\r
269 memset(&entry.header, 0, sizeof(SZIPFileHeader));
\r
271 File->read(&entry.header, sizeof(SZIPFileHeader));
\r
273 #ifdef __BIG_ENDIAN__
\r
274 entry.header.Sig = os::Byteswap::byteswap(entry.header.Sig);
\r
275 entry.header.VersionToExtract = os::Byteswap::byteswap(entry.header.VersionToExtract);
\r
276 entry.header.GeneralBitFlag = os::Byteswap::byteswap(entry.header.GeneralBitFlag);
\r
277 entry.header.CompressionMethod = os::Byteswap::byteswap(entry.header.CompressionMethod);
\r
278 entry.header.LastModFileTime = os::Byteswap::byteswap(entry.header.LastModFileTime);
\r
279 entry.header.LastModFileDate = os::Byteswap::byteswap(entry.header.LastModFileDate);
\r
280 entry.header.DataDescriptor.CRC32 = os::Byteswap::byteswap(entry.header.DataDescriptor.CRC32);
\r
281 entry.header.DataDescriptor.CompressedSize = os::Byteswap::byteswap(entry.header.DataDescriptor.CompressedSize);
\r
282 entry.header.DataDescriptor.UncompressedSize = os::Byteswap::byteswap(entry.header.DataDescriptor.UncompressedSize);
\r
283 entry.header.FilenameLength = os::Byteswap::byteswap(entry.header.FilenameLength);
\r
284 entry.header.ExtraFieldLength = os::Byteswap::byteswap(entry.header.ExtraFieldLength);
\r
287 if (entry.header.Sig != 0x04034b50)
\r
288 return false; // local file headers end here.
\r
292 c8 *tmp = new c8 [ entry.header.FilenameLength + 2 ];
\r
293 File->read(tmp, entry.header.FilenameLength);
\r
294 tmp[entry.header.FilenameLength] = 0;
\r
299 if (entry.header.ExtraFieldLength)
\r
300 File->seek(entry.header.ExtraFieldLength, true);
\r
302 // if bit 3 was set, use CentralDirectory for setup
\r
303 if (!ignoreGPBits && entry.header.GeneralBitFlag & ZIP_INFO_IN_DATA_DESCRIPTOR)
\r
305 SZIPFileCentralDirEnd dirEnd;
\r
308 // First place where the end record could be stored
\r
309 File->seek(File->getSize()-22);
\r
310 const char endID[] = {0x50, 0x4b, 0x05, 0x06, 0x0};
\r
311 char tmp[5]={'\0'};
\r
313 // search for the end record ID
\r
314 while (!found && File->getPos()>0)
\r
317 File->read(tmp, 4);
\r
321 if (!strcmp(endID, tmp))
\r
337 File->seek(-seek, true);
\r
339 File->read(&dirEnd, sizeof(dirEnd));
\r
340 #ifdef __BIG_ENDIAN__
\r
341 dirEnd.NumberDisk = os::Byteswap::byteswap(dirEnd.NumberDisk);
\r
342 dirEnd.NumberStart = os::Byteswap::byteswap(dirEnd.NumberStart);
\r
343 dirEnd.TotalDisk = os::Byteswap::byteswap(dirEnd.TotalDisk);
\r
344 dirEnd.TotalEntries = os::Byteswap::byteswap(dirEnd.TotalEntries);
\r
345 dirEnd.Size = os::Byteswap::byteswap(dirEnd.Size);
\r
346 dirEnd.Offset = os::Byteswap::byteswap(dirEnd.Offset);
\r
347 dirEnd.CommentLength = os::Byteswap::byteswap(dirEnd.CommentLength);
\r
349 FileInfo.reallocate(dirEnd.TotalEntries);
\r
350 File->seek(dirEnd.Offset);
\r
351 while (scanCentralDirectoryHeader()) { }
\r
355 // store position in file
\r
356 entry.Offset = File->getPos();
\r
357 // move forward length of data
\r
358 File->seek(entry.header.DataDescriptor.CompressedSize, true);
\r
361 //os::Debuginfo::print("added file from archive", ZipFileName.c_str());
\r
364 addItem(ZipFileName, entry.Offset, entry.header.DataDescriptor.UncompressedSize, ZipFileName.lastChar()=='/', FileInfo.size());
\r
365 FileInfo.push_back(entry);
\r
371 //! scans for a local header, returns false if there is no more local file header.
\r
372 bool CZipReader::scanCentralDirectoryHeader()
\r
374 io::path ZipFileName = "";
\r
375 SZIPFileCentralDirFileHeader entry;
\r
376 File->read(&entry, sizeof(SZIPFileCentralDirFileHeader));
\r
378 #ifdef __BIG_ENDIAN__
\r
379 entry.Sig = os::Byteswap::byteswap(entry.Sig);
\r
380 entry.VersionMadeBy = os::Byteswap::byteswap(entry.VersionMadeBy);
\r
381 entry.VersionToExtract = os::Byteswap::byteswap(entry.VersionToExtract);
\r
382 entry.GeneralBitFlag = os::Byteswap::byteswap(entry.GeneralBitFlag);
\r
383 entry.CompressionMethod = os::Byteswap::byteswap(entry.CompressionMethod);
\r
384 entry.LastModFileTime = os::Byteswap::byteswap(entry.LastModFileTime);
\r
385 entry.LastModFileDate = os::Byteswap::byteswap(entry.LastModFileDate);
\r
386 entry.CRC32 = os::Byteswap::byteswap(entry.CRC32);
\r
387 entry.CompressedSize = os::Byteswap::byteswap(entry.CompressedSize);
\r
388 entry.UncompressedSize = os::Byteswap::byteswap(entry.UncompressedSize);
\r
389 entry.FilenameLength = os::Byteswap::byteswap(entry.FilenameLength);
\r
390 entry.ExtraFieldLength = os::Byteswap::byteswap(entry.ExtraFieldLength);
\r
391 entry.FileCommentLength = os::Byteswap::byteswap(entry.FileCommentLength);
\r
392 entry.DiskNumberStart = os::Byteswap::byteswap(entry.DiskNumberStart);
\r
393 entry.InternalFileAttributes = os::Byteswap::byteswap(entry.InternalFileAttributes);
\r
394 entry.ExternalFileAttributes = os::Byteswap::byteswap(entry.ExternalFileAttributes);
\r
395 entry.RelativeOffsetOfLocalHeader = os::Byteswap::byteswap(entry.RelativeOffsetOfLocalHeader);
\r
398 if (entry.Sig != 0x02014b50)
\r
399 return false; // central dir headers end here.
\r
401 const long pos = File->getPos();
\r
402 File->seek(entry.RelativeOffsetOfLocalHeader);
\r
403 scanZipHeader(true);
\r
404 File->seek(pos+entry.FilenameLength+entry.ExtraFieldLength+entry.FileCommentLength);
\r
405 FileInfo.getLast().header.DataDescriptor.CompressedSize=entry.CompressedSize;
\r
406 FileInfo.getLast().header.DataDescriptor.UncompressedSize=entry.UncompressedSize;
\r
407 FileInfo.getLast().header.DataDescriptor.CRC32=entry.CRC32;
\r
408 Files.getLast().Size=entry.UncompressedSize;
\r
413 //! opens a file by file name
\r
414 IReadFile* CZipReader::createAndOpenFile(const io::path& filename)
\r
416 s32 index = findFile(filename, false);
\r
419 return createAndOpenFile(index);
\r
424 //! opens a file by index
\r
425 IReadFile* CZipReader::createAndOpenFile(u32 index)
\r
427 // Irrlicht supports 0, 8, 12, 14, 99
\r
428 //0 - The file is stored (no compression)
\r
429 //1 - The file is Shrunk
\r
430 //2 - The file is Reduced with compression factor 1
\r
431 //3 - The file is Reduced with compression factor 2
\r
432 //4 - The file is Reduced with compression factor 3
\r
433 //5 - The file is Reduced with compression factor 4
\r
434 //6 - The file is Imploded
\r
435 //7 - Reserved for Tokenizing compression algorithm
\r
436 //8 - The file is Deflated
\r
437 //9 - Reserved for enhanced Deflating
\r
438 //10 - PKWARE Date Compression Library Imploding
\r
439 //12 - bzip2 - Compression Method from libbz2, WinZip 10
\r
440 //14 - LZMA - Compression Method, WinZip 12
\r
441 //96 - Jpeg compression - Compression Method, WinZip 12
\r
442 //97 - WavPack - Compression Method, WinZip 11
\r
443 //98 - PPMd - Compression Method, WinZip 10
\r
444 //99 - AES encryption, WinZip 9
\r
446 const SZipFileEntry &e = FileInfo[Files[index].ID];
\r
448 s16 actualCompressionMethod=e.header.CompressionMethod;
\r
449 IReadFile* decrypted=0;
\r
450 u8* decryptedBuf=0;
\r
451 u32 decryptedSize=e.header.DataDescriptor.CompressedSize;
\r
452 switch(actualCompressionMethod)
\r
454 case 0: // no compression
\r
459 return createLimitReadFile(Files[index].FullName, File, e.Offset, decryptedSize);
\r
463 const u32 uncompressedSize = e.header.DataDescriptor.UncompressedSize;
\r
464 c8* pBuf = new c8[ uncompressedSize ];
\r
467 snprintf_irr ( buf, 64, "Not enough memory for decompressing %s", Files[index].FullName.c_str() );
\r
468 os::Printer::log( buf, ELL_ERROR);
\r
474 u8 *pcData = decryptedBuf;
\r
477 pcData = new u8[decryptedSize];
\r
480 snprintf_irr ( buf, 64, "Not enough memory for decompressing %s", Files[index].FullName.c_str() );
\r
481 os::Printer::log( buf, ELL_ERROR);
\r
486 //memset(pcData, 0, decryptedSize);
\r
487 File->seek(e.Offset);
\r
488 File->read(pcData, decryptedSize);
\r
491 // Setup the inflate stream.
\r
495 stream.next_in = (Bytef*)pcData;
\r
496 stream.avail_in = (uInt)decryptedSize;
\r
497 stream.next_out = (Bytef*)pBuf;
\r
498 stream.avail_out = uncompressedSize;
\r
499 stream.zalloc = (alloc_func)0;
\r
500 stream.zfree = (free_func)0;
\r
502 // Perform inflation. wbits < 0 indicates no zlib header inside the data.
\r
503 err = inflateInit2(&stream, -MAX_WBITS);
\r
506 err = inflate(&stream, Z_FINISH);
\r
507 inflateEnd(&stream);
\r
508 if (err == Z_STREAM_END)
\r
511 inflateEnd(&stream);
\r
521 snprintf_irr ( buf, 64, "Error decompressing %s", Files[index].FullName.c_str() );
\r
522 os::Printer::log( buf, ELL_ERROR);
\r
527 return FileSystem->createMemoryReadFile(pBuf, uncompressedSize, Files[index].FullName, true);
\r
531 os::Printer::log("bzip2 decompression not supported. File cannot be read.", ELL_ERROR);
\r
536 os::Printer::log("lzma decompression not supported. File cannot be read.", ELL_ERROR);
\r
540 // If we come here with an encrypted file, decryption support is missing
\r
541 os::Printer::log("Decryption support not enabled. File cannot be read.", ELL_ERROR);
\r
544 snprintf_irr ( buf, 64, "file has unsupported compression method. %s", Files[index].FullName.c_str() );
\r
545 os::Printer::log( buf, ELL_ERROR);
\r
551 } // end namespace io
\r
552 } // end namespace irr
\r