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 #ifdef __IRR_COMPILE_WITH_ZIP_ARCHIVE_LOADER_
\r
12 #include "CFileList.h"
\r
13 #include "CReadFile.h"
\r
14 #include "coreutil.h"
\r
16 #include "IrrCompileConfig.h"
\r
17 #ifdef _IRR_COMPILE_WITH_ZLIB_
\r
18 #include <zlib.h> // use system lib
\r
27 // -----------------------------------------------------------------------------
\r
29 // -----------------------------------------------------------------------------
\r
32 CArchiveLoaderZIP::CArchiveLoaderZIP(io::IFileSystem* fs)
\r
36 setDebugName("CArchiveLoaderZIP");
\r
40 //! returns true if the file maybe is able to be loaded by this class
\r
41 bool CArchiveLoaderZIP::isALoadableFileFormat(const io::path& filename) const
\r
43 return core::hasFileExtension(filename, "zip", "pk3") ||
\r
44 core::hasFileExtension(filename, "gz", "tgz");
\r
47 //! Check to see if the loader can create archives of this type.
\r
48 bool CArchiveLoaderZIP::isALoadableFileFormat(E_FILE_ARCHIVE_TYPE fileType) const
\r
50 return (fileType == EFAT_ZIP || fileType == EFAT_GZIP);
\r
54 //! Creates an archive from the filename
\r
55 /** \param file File handle to check.
\r
56 \return Pointer to newly created archive, or 0 upon error. */
\r
57 IFileArchive* CArchiveLoaderZIP::createArchive(const io::path& filename, bool ignoreCase, bool ignorePaths) const
\r
59 IFileArchive *archive = 0;
\r
60 io::IReadFile* file = FileSystem->createAndOpenFile(filename);
\r
64 archive = createArchive(file, ignoreCase, ignorePaths);
\r
71 //! creates/loads an archive from the file.
\r
72 //! \return Pointer to the created archive. Returns 0 if loading failed.
\r
73 IFileArchive* CArchiveLoaderZIP::createArchive(io::IReadFile* file, bool ignoreCase, bool ignorePaths) const
\r
75 IFileArchive *archive = 0;
\r
81 file->read(&sig, 2);
\r
83 #ifdef __BIG_ENDIAN__
\r
84 sig = os::Byteswap::byteswap(sig);
\r
89 bool isGZip = (sig == 0x8b1f);
\r
91 archive = new CZipReader(FileSystem, file, ignoreCase, ignorePaths, isGZip);
\r
96 //! Check if the file might be loaded by this class
\r
97 /** Check might look into the file.
\r
98 \param file File handle to check.
\r
99 \return True if file seems to be loadable. */
\r
100 bool CArchiveLoaderZIP::isALoadableFileFormat(io::IReadFile* file) const
\r
102 SZIPFileHeader header;
\r
104 file->read( &header.Sig, 4 );
\r
105 #ifdef __BIG_ENDIAN__
\r
106 header.Sig = os::Byteswap::byteswap(header.Sig);
\r
109 return header.Sig == 0x04034b50 || // ZIP
\r
110 (header.Sig&0xffff) == 0x8b1f; // gzip
\r
113 // -----------------------------------------------------------------------------
\r
115 // -----------------------------------------------------------------------------
\r
117 CZipReader::CZipReader(IFileSystem* fs, IReadFile* file, bool ignoreCase, bool ignorePaths, bool isGZip)
\r
118 : CFileList((file ? file->getFileName() : io::path("")), ignoreCase, ignorePaths), FileSystem(fs), File(file), IsGZip(isGZip)
\r
121 setDebugName("CZipReader");
\r
128 // load file entries
\r
130 while (scanGZipHeader()) { }
\r
132 while (scanZipHeader()) { }
\r
138 CZipReader::~CZipReader()
\r
145 //! get the archive type
\r
146 E_FILE_ARCHIVE_TYPE CZipReader::getType() const
\r
148 return IsGZip ? EFAT_GZIP : EFAT_ZIP;
\r
151 const IFileList* CZipReader::getFileList() const
\r
157 //! scans for a local header, returns false if there is no more local file header.
\r
158 //! The gzip file format seems to think that there can be multiple files in a gzip file
\r
160 bool CZipReader::scanGZipHeader()
\r
162 SZipFileEntry entry;
\r
164 memset(&entry.header, 0, sizeof(SZIPFileHeader));
\r
167 SGZIPMemberHeader header;
\r
168 if (File->read(&header, sizeof(SGZIPMemberHeader)) == sizeof(SGZIPMemberHeader))
\r
171 #ifdef __BIG_ENDIAN__
\r
172 header.sig = os::Byteswap::byteswap(header.sig);
\r
173 header.time = os::Byteswap::byteswap(header.time);
\r
176 // check header value
\r
177 if (header.sig != 0x8b1f)
\r
180 // now get the file info
\r
181 if (header.flags & EGZF_EXTRA_FIELDS)
\r
183 // read lenth of extra data
\r
186 File->read(&dataLen, 2);
\r
188 #ifdef __BIG_ENDIAN__
\r
189 dataLen = os::Byteswap::byteswap(dataLen);
\r
193 File->seek(dataLen, true);
\r
196 io::path ZipFileName = "";
\r
198 if (header.flags & EGZF_FILE_NAME)
\r
204 ZipFileName.append(c);
\r
211 ZipFileName = Path;
\r
212 core::deletePathFromFilename(ZipFileName);
\r
214 // rename tgz to tar or remove gz extension
\r
215 if (core::hasFileExtension(ZipFileName, "tgz"))
\r
217 ZipFileName[ ZipFileName.size() - 2] = 'a';
\r
218 ZipFileName[ ZipFileName.size() - 1] = 'r';
\r
220 else if (core::hasFileExtension(ZipFileName, "gz"))
\r
222 ZipFileName[ ZipFileName.size() - 3] = 0;
\r
223 ZipFileName.validate();
\r
227 if (header.flags & EGZF_COMMENT)
\r
234 if (header.flags & EGZF_CRC16)
\r
235 File->seek(2, true);
\r
237 // we are now at the start of the data blocks
\r
238 entry.Offset = File->getPos();
\r
240 entry.header.FilenameLength = ZipFileName.size();
\r
242 entry.header.CompressionMethod = header.compressionMethod;
\r
243 entry.header.DataDescriptor.CompressedSize = (File->getSize() - 8) - File->getPos();
\r
245 // seek to file end
\r
246 File->seek(entry.header.DataDescriptor.CompressedSize, true);
\r
249 File->read(&entry.header.DataDescriptor.CRC32, 4);
\r
250 // read uncompressed size
\r
251 File->read(&entry.header.DataDescriptor.UncompressedSize, 4);
\r
253 #ifdef __BIG_ENDIAN__
\r
254 entry.header.DataDescriptor.CRC32 = os::Byteswap::byteswap(entry.header.DataDescriptor.CRC32);
\r
255 entry.header.DataDescriptor.UncompressedSize = os::Byteswap::byteswap(entry.header.DataDescriptor.UncompressedSize);
\r
258 // now we've filled all the fields, this is just a standard deflate block
\r
259 addItem(ZipFileName, entry.Offset, entry.header.DataDescriptor.UncompressedSize, false, 0);
\r
260 FileInfo.push_back(entry);
\r
263 // there's only one block of data in a gzip file
\r
267 //! scans for a local header, returns false if there is no more local file header.
\r
268 bool CZipReader::scanZipHeader(bool ignoreGPBits)
\r
270 io::path ZipFileName = "";
\r
271 SZipFileEntry entry;
\r
273 memset(&entry.header, 0, sizeof(SZIPFileHeader));
\r
275 File->read(&entry.header, sizeof(SZIPFileHeader));
\r
277 #ifdef __BIG_ENDIAN__
\r
278 entry.header.Sig = os::Byteswap::byteswap(entry.header.Sig);
\r
279 entry.header.VersionToExtract = os::Byteswap::byteswap(entry.header.VersionToExtract);
\r
280 entry.header.GeneralBitFlag = os::Byteswap::byteswap(entry.header.GeneralBitFlag);
\r
281 entry.header.CompressionMethod = os::Byteswap::byteswap(entry.header.CompressionMethod);
\r
282 entry.header.LastModFileTime = os::Byteswap::byteswap(entry.header.LastModFileTime);
\r
283 entry.header.LastModFileDate = os::Byteswap::byteswap(entry.header.LastModFileDate);
\r
284 entry.header.DataDescriptor.CRC32 = os::Byteswap::byteswap(entry.header.DataDescriptor.CRC32);
\r
285 entry.header.DataDescriptor.CompressedSize = os::Byteswap::byteswap(entry.header.DataDescriptor.CompressedSize);
\r
286 entry.header.DataDescriptor.UncompressedSize = os::Byteswap::byteswap(entry.header.DataDescriptor.UncompressedSize);
\r
287 entry.header.FilenameLength = os::Byteswap::byteswap(entry.header.FilenameLength);
\r
288 entry.header.ExtraFieldLength = os::Byteswap::byteswap(entry.header.ExtraFieldLength);
\r
291 if (entry.header.Sig != 0x04034b50)
\r
292 return false; // local file headers end here.
\r
296 c8 *tmp = new c8 [ entry.header.FilenameLength + 2 ];
\r
297 File->read(tmp, entry.header.FilenameLength);
\r
298 tmp[entry.header.FilenameLength] = 0;
\r
303 if (entry.header.ExtraFieldLength)
\r
304 File->seek(entry.header.ExtraFieldLength, true);
\r
306 // if bit 3 was set, use CentralDirectory for setup
\r
307 if (!ignoreGPBits && entry.header.GeneralBitFlag & ZIP_INFO_IN_DATA_DESCRIPTOR)
\r
309 SZIPFileCentralDirEnd dirEnd;
\r
312 // First place where the end record could be stored
\r
313 File->seek(File->getSize()-22);
\r
314 const char endID[] = {0x50, 0x4b, 0x05, 0x06, 0x0};
\r
315 char tmp[5]={'\0'};
\r
317 // search for the end record ID
\r
318 while (!found && File->getPos()>0)
\r
321 File->read(tmp, 4);
\r
325 if (!strcmp(endID, tmp))
\r
341 File->seek(-seek, true);
\r
343 File->read(&dirEnd, sizeof(dirEnd));
\r
344 #ifdef __BIG_ENDIAN__
\r
345 dirEnd.NumberDisk = os::Byteswap::byteswap(dirEnd.NumberDisk);
\r
346 dirEnd.NumberStart = os::Byteswap::byteswap(dirEnd.NumberStart);
\r
347 dirEnd.TotalDisk = os::Byteswap::byteswap(dirEnd.TotalDisk);
\r
348 dirEnd.TotalEntries = os::Byteswap::byteswap(dirEnd.TotalEntries);
\r
349 dirEnd.Size = os::Byteswap::byteswap(dirEnd.Size);
\r
350 dirEnd.Offset = os::Byteswap::byteswap(dirEnd.Offset);
\r
351 dirEnd.CommentLength = os::Byteswap::byteswap(dirEnd.CommentLength);
\r
353 FileInfo.reallocate(dirEnd.TotalEntries);
\r
354 File->seek(dirEnd.Offset);
\r
355 while (scanCentralDirectoryHeader()) { }
\r
359 // store position in file
\r
360 entry.Offset = File->getPos();
\r
361 // move forward length of data
\r
362 File->seek(entry.header.DataDescriptor.CompressedSize, true);
\r
365 //os::Debuginfo::print("added file from archive", ZipFileName.c_str());
\r
368 addItem(ZipFileName, entry.Offset, entry.header.DataDescriptor.UncompressedSize, ZipFileName.lastChar()=='/', FileInfo.size());
\r
369 FileInfo.push_back(entry);
\r
375 //! scans for a local header, returns false if there is no more local file header.
\r
376 bool CZipReader::scanCentralDirectoryHeader()
\r
378 io::path ZipFileName = "";
\r
379 SZIPFileCentralDirFileHeader entry;
\r
380 File->read(&entry, sizeof(SZIPFileCentralDirFileHeader));
\r
382 #ifdef __BIG_ENDIAN__
\r
383 entry.Sig = os::Byteswap::byteswap(entry.Sig);
\r
384 entry.VersionMadeBy = os::Byteswap::byteswap(entry.VersionMadeBy);
\r
385 entry.VersionToExtract = os::Byteswap::byteswap(entry.VersionToExtract);
\r
386 entry.GeneralBitFlag = os::Byteswap::byteswap(entry.GeneralBitFlag);
\r
387 entry.CompressionMethod = os::Byteswap::byteswap(entry.CompressionMethod);
\r
388 entry.LastModFileTime = os::Byteswap::byteswap(entry.LastModFileTime);
\r
389 entry.LastModFileDate = os::Byteswap::byteswap(entry.LastModFileDate);
\r
390 entry.CRC32 = os::Byteswap::byteswap(entry.CRC32);
\r
391 entry.CompressedSize = os::Byteswap::byteswap(entry.CompressedSize);
\r
392 entry.UncompressedSize = os::Byteswap::byteswap(entry.UncompressedSize);
\r
393 entry.FilenameLength = os::Byteswap::byteswap(entry.FilenameLength);
\r
394 entry.ExtraFieldLength = os::Byteswap::byteswap(entry.ExtraFieldLength);
\r
395 entry.FileCommentLength = os::Byteswap::byteswap(entry.FileCommentLength);
\r
396 entry.DiskNumberStart = os::Byteswap::byteswap(entry.DiskNumberStart);
\r
397 entry.InternalFileAttributes = os::Byteswap::byteswap(entry.InternalFileAttributes);
\r
398 entry.ExternalFileAttributes = os::Byteswap::byteswap(entry.ExternalFileAttributes);
\r
399 entry.RelativeOffsetOfLocalHeader = os::Byteswap::byteswap(entry.RelativeOffsetOfLocalHeader);
\r
402 if (entry.Sig != 0x02014b50)
\r
403 return false; // central dir headers end here.
\r
405 const long pos = File->getPos();
\r
406 File->seek(entry.RelativeOffsetOfLocalHeader);
\r
407 scanZipHeader(true);
\r
408 File->seek(pos+entry.FilenameLength+entry.ExtraFieldLength+entry.FileCommentLength);
\r
409 FileInfo.getLast().header.DataDescriptor.CompressedSize=entry.CompressedSize;
\r
410 FileInfo.getLast().header.DataDescriptor.UncompressedSize=entry.UncompressedSize;
\r
411 FileInfo.getLast().header.DataDescriptor.CRC32=entry.CRC32;
\r
412 Files.getLast().Size=entry.UncompressedSize;
\r
417 //! opens a file by file name
\r
418 IReadFile* CZipReader::createAndOpenFile(const io::path& filename)
\r
420 s32 index = findFile(filename, false);
\r
423 return createAndOpenFile(index);
\r
428 //! opens a file by index
\r
429 IReadFile* CZipReader::createAndOpenFile(u32 index)
\r
431 // Irrlicht supports 0, 8, 12, 14, 99
\r
432 //0 - The file is stored (no compression)
\r
433 //1 - The file is Shrunk
\r
434 //2 - The file is Reduced with compression factor 1
\r
435 //3 - The file is Reduced with compression factor 2
\r
436 //4 - The file is Reduced with compression factor 3
\r
437 //5 - The file is Reduced with compression factor 4
\r
438 //6 - The file is Imploded
\r
439 //7 - Reserved for Tokenizing compression algorithm
\r
440 //8 - The file is Deflated
\r
441 //9 - Reserved for enhanced Deflating
\r
442 //10 - PKWARE Date Compression Library Imploding
\r
443 //12 - bzip2 - Compression Method from libbz2, WinZip 10
\r
444 //14 - LZMA - Compression Method, WinZip 12
\r
445 //96 - Jpeg compression - Compression Method, WinZip 12
\r
446 //97 - WavPack - Compression Method, WinZip 11
\r
447 //98 - PPMd - Compression Method, WinZip 10
\r
448 //99 - AES encryption, WinZip 9
\r
450 const SZipFileEntry &e = FileInfo[Files[index].ID];
\r
452 s16 actualCompressionMethod=e.header.CompressionMethod;
\r
453 IReadFile* decrypted=0;
\r
454 u8* decryptedBuf=0;
\r
455 u32 decryptedSize=e.header.DataDescriptor.CompressedSize;
\r
456 switch(actualCompressionMethod)
\r
458 case 0: // no compression
\r
463 return createLimitReadFile(Files[index].FullName, File, e.Offset, decryptedSize);
\r
467 #ifdef _IRR_COMPILE_WITH_ZLIB_
\r
469 const u32 uncompressedSize = e.header.DataDescriptor.UncompressedSize;
\r
470 c8* pBuf = new c8[ uncompressedSize ];
\r
473 swprintf_irr ( buf, 64, L"Not enough memory for decompressing %s", core::stringw(Files[index].FullName).c_str() );
\r
474 os::Printer::log( buf, ELL_ERROR);
\r
480 u8 *pcData = decryptedBuf;
\r
483 pcData = new u8[decryptedSize];
\r
486 swprintf_irr ( buf, 64, L"Not enough memory for decompressing %s", core::stringw(Files[index].FullName).c_str() );
\r
487 os::Printer::log( buf, ELL_ERROR);
\r
492 //memset(pcData, 0, decryptedSize);
\r
493 File->seek(e.Offset);
\r
494 File->read(pcData, decryptedSize);
\r
497 // Setup the inflate stream.
\r
501 stream.next_in = (Bytef*)pcData;
\r
502 stream.avail_in = (uInt)decryptedSize;
\r
503 stream.next_out = (Bytef*)pBuf;
\r
504 stream.avail_out = uncompressedSize;
\r
505 stream.zalloc = (alloc_func)0;
\r
506 stream.zfree = (free_func)0;
\r
508 // Perform inflation. wbits < 0 indicates no zlib header inside the data.
\r
509 err = inflateInit2(&stream, -MAX_WBITS);
\r
512 err = inflate(&stream, Z_FINISH);
\r
513 inflateEnd(&stream);
\r
514 if (err == Z_STREAM_END)
\r
517 inflateEnd(&stream);
\r
527 swprintf_irr ( buf, 64, L"Error decompressing %s", core::stringw(Files[index].FullName).c_str() );
\r
528 os::Printer::log( buf, ELL_ERROR);
\r
533 return FileSystem->createMemoryReadFile(pBuf, uncompressedSize, Files[index].FullName, true);
\r
536 return 0; // zlib not compiled, we cannot decompress the data.
\r
541 os::Printer::log("bzip2 decompression not supported. File cannot be read.", ELL_ERROR);
\r
546 os::Printer::log("lzma decompression not supported. File cannot be read.", ELL_ERROR);
\r
550 // If we come here with an encrypted file, decryption support is missing
\r
551 os::Printer::log("Decryption support not enabled. File cannot be read.", ELL_ERROR);
\r
554 swprintf_irr ( buf, 64, L"file has unsupported compression method. %s", core::stringw(Files[index].FullName).c_str() );
\r
555 os::Printer::log( buf, ELL_ERROR);
\r
561 } // end namespace io
\r
562 } // end namespace irr
\r
564 #endif // __IRR_COMPILE_WITH_ZIP_ARCHIVE_LOADER_
\r