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 <zlib.h> // use system lib
\r
22 // -----------------------------------------------------------------------------
\r
24 // -----------------------------------------------------------------------------
\r
27 CArchiveLoaderZIP::CArchiveLoaderZIP(io::IFileSystem* fs)
\r
31 setDebugName("CArchiveLoaderZIP");
\r
35 //! returns true if the file maybe is able to be loaded by this class
\r
36 bool CArchiveLoaderZIP::isALoadableFileFormat(const io::path& filename) const
\r
38 return core::hasFileExtension(filename, "zip", "pk3") ||
\r
39 core::hasFileExtension(filename, "gz", "tgz");
\r
42 //! Check to see if the loader can create archives of this type.
\r
43 bool CArchiveLoaderZIP::isALoadableFileFormat(E_FILE_ARCHIVE_TYPE fileType) const
\r
45 return (fileType == EFAT_ZIP || fileType == EFAT_GZIP);
\r
49 //! Creates an archive from the filename
\r
50 /** \param file File handle to check.
\r
51 \return Pointer to newly created archive, or 0 upon error. */
\r
52 IFileArchive* CArchiveLoaderZIP::createArchive(const io::path& filename, bool ignoreCase, bool ignorePaths) const
\r
54 IFileArchive *archive = 0;
\r
55 io::IReadFile* file = FileSystem->createAndOpenFile(filename);
\r
59 archive = createArchive(file, ignoreCase, ignorePaths);
\r
66 //! creates/loads an archive from the file.
\r
67 //! \return Pointer to the created archive. Returns 0 if loading failed.
\r
68 IFileArchive* CArchiveLoaderZIP::createArchive(io::IReadFile* file, bool ignoreCase, bool ignorePaths) const
\r
70 IFileArchive *archive = 0;
\r
76 file->read(&sig, 2);
\r
78 #ifdef __BIG_ENDIAN__
\r
79 sig = os::Byteswap::byteswap(sig);
\r
84 bool isGZip = (sig == 0x8b1f);
\r
86 archive = new CZipReader(FileSystem, file, ignoreCase, ignorePaths, isGZip);
\r
91 //! Check if the file might be loaded by this class
\r
92 /** Check might look into the file.
\r
93 \param file File handle to check.
\r
94 \return True if file seems to be loadable. */
\r
95 bool CArchiveLoaderZIP::isALoadableFileFormat(io::IReadFile* file) const
\r
97 SZIPFileHeader header;
\r
99 file->read( &header.Sig, 4 );
\r
100 #ifdef __BIG_ENDIAN__
\r
101 header.Sig = os::Byteswap::byteswap(header.Sig);
\r
104 return header.Sig == 0x04034b50 || // ZIP
\r
105 (header.Sig&0xffff) == 0x8b1f; // gzip
\r
108 // -----------------------------------------------------------------------------
\r
110 // -----------------------------------------------------------------------------
\r
112 CZipReader::CZipReader(IFileSystem* fs, IReadFile* file, bool ignoreCase, bool ignorePaths, bool isGZip)
\r
113 : CFileList((file ? file->getFileName() : io::path("")), ignoreCase, ignorePaths), FileSystem(fs), File(file), IsGZip(isGZip)
\r
116 setDebugName("CZipReader");
\r
123 // load file entries
\r
125 while (scanGZipHeader()) { }
\r
127 while (scanZipHeader()) { }
\r
133 CZipReader::~CZipReader()
\r
140 //! get the archive type
\r
141 E_FILE_ARCHIVE_TYPE CZipReader::getType() const
\r
143 return IsGZip ? EFAT_GZIP : EFAT_ZIP;
\r
146 const IFileList* CZipReader::getFileList() const
\r
152 //! scans for a local header, returns false if there is no more local file header.
\r
153 //! The gzip file format seems to think that there can be multiple files in a gzip file
\r
155 bool CZipReader::scanGZipHeader()
\r
157 SZipFileEntry entry;
\r
159 memset(&entry.header, 0, sizeof(SZIPFileHeader));
\r
162 SGZIPMemberHeader header;
\r
163 if (File->read(&header, sizeof(SGZIPMemberHeader)) == sizeof(SGZIPMemberHeader))
\r
166 #ifdef __BIG_ENDIAN__
\r
167 header.sig = os::Byteswap::byteswap(header.sig);
\r
168 header.time = os::Byteswap::byteswap(header.time);
\r
171 // check header value
\r
172 if (header.sig != 0x8b1f)
\r
175 // now get the file info
\r
176 if (header.flags & EGZF_EXTRA_FIELDS)
\r
178 // read lenth of extra data
\r
181 File->read(&dataLen, 2);
\r
183 #ifdef __BIG_ENDIAN__
\r
184 dataLen = os::Byteswap::byteswap(dataLen);
\r
188 File->seek(dataLen, true);
\r
191 io::path ZipFileName = "";
\r
193 if (header.flags & EGZF_FILE_NAME)
\r
199 ZipFileName.append(c);
\r
206 ZipFileName = Path;
\r
207 core::deletePathFromFilename(ZipFileName);
\r
209 // rename tgz to tar or remove gz extension
\r
210 if (core::hasFileExtension(ZipFileName, "tgz"))
\r
212 ZipFileName[ ZipFileName.size() - 2] = 'a';
\r
213 ZipFileName[ ZipFileName.size() - 1] = 'r';
\r
215 else if (core::hasFileExtension(ZipFileName, "gz"))
\r
217 ZipFileName[ ZipFileName.size() - 3] = 0;
\r
218 ZipFileName.validate();
\r
222 if (header.flags & EGZF_COMMENT)
\r
229 if (header.flags & EGZF_CRC16)
\r
230 File->seek(2, true);
\r
232 // we are now at the start of the data blocks
\r
233 entry.Offset = File->getPos();
\r
235 entry.header.FilenameLength = ZipFileName.size();
\r
237 entry.header.CompressionMethod = header.compressionMethod;
\r
238 entry.header.DataDescriptor.CompressedSize = (File->getSize() - 8) - File->getPos();
\r
240 // seek to file end
\r
241 File->seek(entry.header.DataDescriptor.CompressedSize, true);
\r
244 File->read(&entry.header.DataDescriptor.CRC32, 4);
\r
245 // read uncompressed size
\r
246 File->read(&entry.header.DataDescriptor.UncompressedSize, 4);
\r
248 #ifdef __BIG_ENDIAN__
\r
249 entry.header.DataDescriptor.CRC32 = os::Byteswap::byteswap(entry.header.DataDescriptor.CRC32);
\r
250 entry.header.DataDescriptor.UncompressedSize = os::Byteswap::byteswap(entry.header.DataDescriptor.UncompressedSize);
\r
253 // now we've filled all the fields, this is just a standard deflate block
\r
254 addItem(ZipFileName, entry.Offset, entry.header.DataDescriptor.UncompressedSize, false, 0);
\r
255 FileInfo.push_back(entry);
\r
258 // there's only one block of data in a gzip file
\r
262 //! scans for a local header, returns false if there is no more local file header.
\r
263 bool CZipReader::scanZipHeader(bool ignoreGPBits)
\r
265 io::path ZipFileName = "";
\r
266 SZipFileEntry entry;
\r
268 memset(&entry.header, 0, sizeof(SZIPFileHeader));
\r
270 File->read(&entry.header, sizeof(SZIPFileHeader));
\r
272 #ifdef __BIG_ENDIAN__
\r
273 entry.header.Sig = os::Byteswap::byteswap(entry.header.Sig);
\r
274 entry.header.VersionToExtract = os::Byteswap::byteswap(entry.header.VersionToExtract);
\r
275 entry.header.GeneralBitFlag = os::Byteswap::byteswap(entry.header.GeneralBitFlag);
\r
276 entry.header.CompressionMethod = os::Byteswap::byteswap(entry.header.CompressionMethod);
\r
277 entry.header.LastModFileTime = os::Byteswap::byteswap(entry.header.LastModFileTime);
\r
278 entry.header.LastModFileDate = os::Byteswap::byteswap(entry.header.LastModFileDate);
\r
279 entry.header.DataDescriptor.CRC32 = os::Byteswap::byteswap(entry.header.DataDescriptor.CRC32);
\r
280 entry.header.DataDescriptor.CompressedSize = os::Byteswap::byteswap(entry.header.DataDescriptor.CompressedSize);
\r
281 entry.header.DataDescriptor.UncompressedSize = os::Byteswap::byteswap(entry.header.DataDescriptor.UncompressedSize);
\r
282 entry.header.FilenameLength = os::Byteswap::byteswap(entry.header.FilenameLength);
\r
283 entry.header.ExtraFieldLength = os::Byteswap::byteswap(entry.header.ExtraFieldLength);
\r
286 if (entry.header.Sig != 0x04034b50)
\r
287 return false; // local file headers end here.
\r
291 c8 *tmp = new c8 [ entry.header.FilenameLength + 2 ];
\r
292 File->read(tmp, entry.header.FilenameLength);
\r
293 tmp[entry.header.FilenameLength] = 0;
\r
298 if (entry.header.ExtraFieldLength)
\r
299 File->seek(entry.header.ExtraFieldLength, true);
\r
301 // if bit 3 was set, use CentralDirectory for setup
\r
302 if (!ignoreGPBits && entry.header.GeneralBitFlag & ZIP_INFO_IN_DATA_DESCRIPTOR)
\r
304 SZIPFileCentralDirEnd dirEnd;
\r
307 // First place where the end record could be stored
\r
308 File->seek(File->getSize()-22);
\r
309 const char endID[] = {0x50, 0x4b, 0x05, 0x06, 0x0};
\r
310 char tmp[5]={'\0'};
\r
312 // search for the end record ID
\r
313 while (!found && File->getPos()>0)
\r
316 File->read(tmp, 4);
\r
320 if (!strcmp(endID, tmp))
\r
336 File->seek(-seek, true);
\r
338 File->read(&dirEnd, sizeof(dirEnd));
\r
339 #ifdef __BIG_ENDIAN__
\r
340 dirEnd.NumberDisk = os::Byteswap::byteswap(dirEnd.NumberDisk);
\r
341 dirEnd.NumberStart = os::Byteswap::byteswap(dirEnd.NumberStart);
\r
342 dirEnd.TotalDisk = os::Byteswap::byteswap(dirEnd.TotalDisk);
\r
343 dirEnd.TotalEntries = os::Byteswap::byteswap(dirEnd.TotalEntries);
\r
344 dirEnd.Size = os::Byteswap::byteswap(dirEnd.Size);
\r
345 dirEnd.Offset = os::Byteswap::byteswap(dirEnd.Offset);
\r
346 dirEnd.CommentLength = os::Byteswap::byteswap(dirEnd.CommentLength);
\r
348 FileInfo.reallocate(dirEnd.TotalEntries);
\r
349 File->seek(dirEnd.Offset);
\r
350 while (scanCentralDirectoryHeader()) { }
\r
354 // store position in file
\r
355 entry.Offset = File->getPos();
\r
356 // move forward length of data
\r
357 File->seek(entry.header.DataDescriptor.CompressedSize, true);
\r
360 //os::Debuginfo::print("added file from archive", ZipFileName.c_str());
\r
363 addItem(ZipFileName, entry.Offset, entry.header.DataDescriptor.UncompressedSize, ZipFileName.lastChar()=='/', FileInfo.size());
\r
364 FileInfo.push_back(entry);
\r
370 //! scans for a local header, returns false if there is no more local file header.
\r
371 bool CZipReader::scanCentralDirectoryHeader()
\r
373 io::path ZipFileName = "";
\r
374 SZIPFileCentralDirFileHeader entry;
\r
375 File->read(&entry, sizeof(SZIPFileCentralDirFileHeader));
\r
377 #ifdef __BIG_ENDIAN__
\r
378 entry.Sig = os::Byteswap::byteswap(entry.Sig);
\r
379 entry.VersionMadeBy = os::Byteswap::byteswap(entry.VersionMadeBy);
\r
380 entry.VersionToExtract = os::Byteswap::byteswap(entry.VersionToExtract);
\r
381 entry.GeneralBitFlag = os::Byteswap::byteswap(entry.GeneralBitFlag);
\r
382 entry.CompressionMethod = os::Byteswap::byteswap(entry.CompressionMethod);
\r
383 entry.LastModFileTime = os::Byteswap::byteswap(entry.LastModFileTime);
\r
384 entry.LastModFileDate = os::Byteswap::byteswap(entry.LastModFileDate);
\r
385 entry.CRC32 = os::Byteswap::byteswap(entry.CRC32);
\r
386 entry.CompressedSize = os::Byteswap::byteswap(entry.CompressedSize);
\r
387 entry.UncompressedSize = os::Byteswap::byteswap(entry.UncompressedSize);
\r
388 entry.FilenameLength = os::Byteswap::byteswap(entry.FilenameLength);
\r
389 entry.ExtraFieldLength = os::Byteswap::byteswap(entry.ExtraFieldLength);
\r
390 entry.FileCommentLength = os::Byteswap::byteswap(entry.FileCommentLength);
\r
391 entry.DiskNumberStart = os::Byteswap::byteswap(entry.DiskNumberStart);
\r
392 entry.InternalFileAttributes = os::Byteswap::byteswap(entry.InternalFileAttributes);
\r
393 entry.ExternalFileAttributes = os::Byteswap::byteswap(entry.ExternalFileAttributes);
\r
394 entry.RelativeOffsetOfLocalHeader = os::Byteswap::byteswap(entry.RelativeOffsetOfLocalHeader);
\r
397 if (entry.Sig != 0x02014b50)
\r
398 return false; // central dir headers end here.
\r
400 const long pos = File->getPos();
\r
401 File->seek(entry.RelativeOffsetOfLocalHeader);
\r
402 scanZipHeader(true);
\r
403 File->seek(pos+entry.FilenameLength+entry.ExtraFieldLength+entry.FileCommentLength);
\r
404 FileInfo.getLast().header.DataDescriptor.CompressedSize=entry.CompressedSize;
\r
405 FileInfo.getLast().header.DataDescriptor.UncompressedSize=entry.UncompressedSize;
\r
406 FileInfo.getLast().header.DataDescriptor.CRC32=entry.CRC32;
\r
407 Files.getLast().Size=entry.UncompressedSize;
\r
412 //! opens a file by file name
\r
413 IReadFile* CZipReader::createAndOpenFile(const io::path& filename)
\r
415 s32 index = findFile(filename, false);
\r
418 return createAndOpenFile(index);
\r
423 //! opens a file by index
\r
424 IReadFile* CZipReader::createAndOpenFile(u32 index)
\r
426 // Irrlicht supports 0, 8, 12, 14, 99
\r
427 //0 - The file is stored (no compression)
\r
428 //1 - The file is Shrunk
\r
429 //2 - The file is Reduced with compression factor 1
\r
430 //3 - The file is Reduced with compression factor 2
\r
431 //4 - The file is Reduced with compression factor 3
\r
432 //5 - The file is Reduced with compression factor 4
\r
433 //6 - The file is Imploded
\r
434 //7 - Reserved for Tokenizing compression algorithm
\r
435 //8 - The file is Deflated
\r
436 //9 - Reserved for enhanced Deflating
\r
437 //10 - PKWARE Date Compression Library Imploding
\r
438 //12 - bzip2 - Compression Method from libbz2, WinZip 10
\r
439 //14 - LZMA - Compression Method, WinZip 12
\r
440 //96 - Jpeg compression - Compression Method, WinZip 12
\r
441 //97 - WavPack - Compression Method, WinZip 11
\r
442 //98 - PPMd - Compression Method, WinZip 10
\r
443 //99 - AES encryption, WinZip 9
\r
445 const SZipFileEntry &e = FileInfo[Files[index].ID];
\r
447 s16 actualCompressionMethod=e.header.CompressionMethod;
\r
448 IReadFile* decrypted=0;
\r
449 u8* decryptedBuf=0;
\r
450 u32 decryptedSize=e.header.DataDescriptor.CompressedSize;
\r
451 switch(actualCompressionMethod)
\r
453 case 0: // no compression
\r
458 return createLimitReadFile(Files[index].FullName, File, e.Offset, decryptedSize);
\r
462 const u32 uncompressedSize = e.header.DataDescriptor.UncompressedSize;
\r
463 c8* pBuf = new c8[ uncompressedSize ];
\r
466 snprintf_irr ( buf, 64, "Not enough memory for decompressing %s", Files[index].FullName.c_str() );
\r
467 os::Printer::log( buf, ELL_ERROR);
\r
473 u8 *pcData = decryptedBuf;
\r
476 pcData = new u8[decryptedSize];
\r
479 snprintf_irr ( buf, 64, "Not enough memory for decompressing %s", Files[index].FullName.c_str() );
\r
480 os::Printer::log( buf, ELL_ERROR);
\r
485 //memset(pcData, 0, decryptedSize);
\r
486 File->seek(e.Offset);
\r
487 File->read(pcData, decryptedSize);
\r
490 // Setup the inflate stream.
\r
494 stream.next_in = (Bytef*)pcData;
\r
495 stream.avail_in = (uInt)decryptedSize;
\r
496 stream.next_out = (Bytef*)pBuf;
\r
497 stream.avail_out = uncompressedSize;
\r
498 stream.zalloc = (alloc_func)0;
\r
499 stream.zfree = (free_func)0;
\r
501 // Perform inflation. wbits < 0 indicates no zlib header inside the data.
\r
502 err = inflateInit2(&stream, -MAX_WBITS);
\r
505 err = inflate(&stream, Z_FINISH);
\r
506 inflateEnd(&stream);
\r
507 if (err == Z_STREAM_END)
\r
510 inflateEnd(&stream);
\r
520 snprintf_irr ( buf, 64, "Error decompressing %s", Files[index].FullName.c_str() );
\r
521 os::Printer::log( buf, ELL_ERROR);
\r
526 return FileSystem->createMemoryReadFile(pBuf, uncompressedSize, Files[index].FullName, true);
\r
530 os::Printer::log("bzip2 decompression not supported. File cannot be read.", ELL_ERROR);
\r
535 os::Printer::log("lzma decompression not supported. File cannot be read.", ELL_ERROR);
\r
539 // If we come here with an encrypted file, decryption support is missing
\r
540 os::Printer::log("Decryption support not enabled. File cannot be read.", ELL_ERROR);
\r
543 snprintf_irr ( buf, 64, "file has unsupported compression method. %s", Files[index].FullName.c_str() );
\r
544 os::Printer::log( buf, ELL_ERROR);
\r
550 } // end namespace io
\r
551 } // end namespace irr
\r