1 // Copyright (C) 2002-2012 Nikolaus Gebhardt
2 // This file is part of the "Irrlicht Engine".
3 // For conditions of distribution and use, see copyright notice in irrlicht.h
5 #include "CZipReader.h"
10 #include "CReadFile.h"
13 #include <zlib.h> // use system lib
20 // -----------------------------------------------------------------------------
22 // -----------------------------------------------------------------------------
25 CArchiveLoaderZIP::CArchiveLoaderZIP(io::IFileSystem
*fs
) :
29 setDebugName("CArchiveLoaderZIP");
33 //! returns true if the file maybe is able to be loaded by this class
34 bool CArchiveLoaderZIP::isALoadableFileFormat(const io::path
&filename
) const
36 return core::hasFileExtension(filename
, "zip", "pk3") ||
37 core::hasFileExtension(filename
, "gz", "tgz");
40 //! Check to see if the loader can create archives of this type.
41 bool CArchiveLoaderZIP::isALoadableFileFormat(E_FILE_ARCHIVE_TYPE fileType
) const
43 return (fileType
== EFAT_ZIP
|| fileType
== EFAT_GZIP
);
46 //! Creates an archive from the filename
47 /** \param file File handle to check.
48 \return Pointer to newly created archive, or 0 upon error. */
49 IFileArchive
*CArchiveLoaderZIP::createArchive(const io::path
&filename
, bool ignoreCase
, bool ignorePaths
) const
51 IFileArchive
*archive
= 0;
52 io::IReadFile
*file
= FileSystem
->createAndOpenFile(filename
);
55 archive
= createArchive(file
, ignoreCase
, ignorePaths
);
62 //! creates/loads an archive from the file.
63 //! \return Pointer to the created archive. Returns 0 if loading failed.
64 IFileArchive
*CArchiveLoaderZIP::createArchive(io::IReadFile
*file
, bool ignoreCase
, bool ignorePaths
) const
66 IFileArchive
*archive
= 0;
74 sig
= os::Byteswap::byteswap(sig
);
79 bool isGZip
= (sig
== 0x8b1f);
81 archive
= new CZipReader(FileSystem
, file
, ignoreCase
, ignorePaths
, isGZip
);
86 //! Check if the file might be loaded by this class
87 /** Check might look into the file.
88 \param file File handle to check.
89 \return True if file seems to be loadable. */
90 bool CArchiveLoaderZIP::isALoadableFileFormat(io::IReadFile
*file
) const
92 SZIPFileHeader header
;
94 file
->read(&header
.Sig
, 4);
96 header
.Sig
= os::Byteswap::byteswap(header
.Sig
);
99 return header
.Sig
== 0x04034b50 || // ZIP
100 (header
.Sig
& 0xffff) == 0x8b1f; // gzip
103 // -----------------------------------------------------------------------------
105 // -----------------------------------------------------------------------------
107 CZipReader::CZipReader(IFileSystem
*fs
, IReadFile
*file
, bool ignoreCase
, bool ignorePaths
, bool isGZip
) :
108 CFileList((file
? file
->getFileName() : io::path("")), ignoreCase
, ignorePaths
), FileSystem(fs
), File(file
), IsGZip(isGZip
)
111 setDebugName("CZipReader");
119 while (scanGZipHeader()) {
122 while (scanZipHeader()) {
129 CZipReader::~CZipReader()
135 //! get the archive type
136 E_FILE_ARCHIVE_TYPE
CZipReader::getType() const
138 return IsGZip
? EFAT_GZIP
: EFAT_ZIP
;
141 const IFileList
*CZipReader::getFileList() const
146 //! scans for a local header, returns false if there is no more local file header.
147 //! The gzip file format seems to think that there can be multiple files in a gzip file
149 bool CZipReader::scanGZipHeader()
153 memset(&entry
.header
, 0, sizeof(SZIPFileHeader
));
156 SGZIPMemberHeader header
;
157 if (File
->read(&header
, sizeof(SGZIPMemberHeader
)) == sizeof(SGZIPMemberHeader
)) {
159 #ifdef __BIG_ENDIAN__
160 header
.sig
= os::Byteswap::byteswap(header
.sig
);
161 header
.time
= os::Byteswap::byteswap(header
.time
);
164 // check header value
165 if (header
.sig
!= 0x8b1f)
168 // now get the file info
169 if (header
.flags
& EGZF_EXTRA_FIELDS
) {
170 // read lenth of extra data
173 File
->read(&dataLen
, 2);
175 #ifdef __BIG_ENDIAN__
176 dataLen
= os::Byteswap::byteswap(dataLen
);
180 File
->seek(dataLen
, true);
183 io::path ZipFileName
= "";
185 if (header
.flags
& EGZF_FILE_NAME
) {
189 ZipFileName
.append(c
);
194 ZipFileName
= core::deletePathFromFilename(Path
);
196 // rename tgz to tar or remove gz extension
197 if (core::hasFileExtension(ZipFileName
, "tgz")) {
198 ZipFileName
[ZipFileName
.size() - 2] = 'a';
199 ZipFileName
[ZipFileName
.size() - 1] = 'r';
200 } else if (core::hasFileExtension(ZipFileName
, "gz")) {
201 ZipFileName
[ZipFileName
.size() - 3] = 0;
202 ZipFileName
.validate();
206 if (header
.flags
& EGZF_COMMENT
) {
212 if (header
.flags
& EGZF_CRC16
)
215 // we are now at the start of the data blocks
216 entry
.Offset
= File
->getPos();
218 entry
.header
.FilenameLength
= ZipFileName
.size();
220 entry
.header
.CompressionMethod
= header
.compressionMethod
;
221 entry
.header
.DataDescriptor
.CompressedSize
= (File
->getSize() - 8) - File
->getPos();
224 File
->seek(entry
.header
.DataDescriptor
.CompressedSize
, true);
227 File
->read(&entry
.header
.DataDescriptor
.CRC32
, 4);
228 // read uncompressed size
229 File
->read(&entry
.header
.DataDescriptor
.UncompressedSize
, 4);
231 #ifdef __BIG_ENDIAN__
232 entry
.header
.DataDescriptor
.CRC32
= os::Byteswap::byteswap(entry
.header
.DataDescriptor
.CRC32
);
233 entry
.header
.DataDescriptor
.UncompressedSize
= os::Byteswap::byteswap(entry
.header
.DataDescriptor
.UncompressedSize
);
236 // now we've filled all the fields, this is just a standard deflate block
237 addItem(ZipFileName
, entry
.Offset
, entry
.header
.DataDescriptor
.UncompressedSize
, false, 0);
238 FileInfo
.push_back(entry
);
241 // there's only one block of data in a gzip file
245 //! scans for a local header, returns false if there is no more local file header.
246 bool CZipReader::scanZipHeader(bool ignoreGPBits
)
248 io::path ZipFileName
= "";
251 memset(&entry
.header
, 0, sizeof(SZIPFileHeader
));
253 File
->read(&entry
.header
, sizeof(SZIPFileHeader
));
255 #ifdef __BIG_ENDIAN__
256 entry
.header
.Sig
= os::Byteswap::byteswap(entry
.header
.Sig
);
257 entry
.header
.VersionToExtract
= os::Byteswap::byteswap(entry
.header
.VersionToExtract
);
258 entry
.header
.GeneralBitFlag
= os::Byteswap::byteswap(entry
.header
.GeneralBitFlag
);
259 entry
.header
.CompressionMethod
= os::Byteswap::byteswap(entry
.header
.CompressionMethod
);
260 entry
.header
.LastModFileTime
= os::Byteswap::byteswap(entry
.header
.LastModFileTime
);
261 entry
.header
.LastModFileDate
= os::Byteswap::byteswap(entry
.header
.LastModFileDate
);
262 entry
.header
.DataDescriptor
.CRC32
= os::Byteswap::byteswap(entry
.header
.DataDescriptor
.CRC32
);
263 entry
.header
.DataDescriptor
.CompressedSize
= os::Byteswap::byteswap(entry
.header
.DataDescriptor
.CompressedSize
);
264 entry
.header
.DataDescriptor
.UncompressedSize
= os::Byteswap::byteswap(entry
.header
.DataDescriptor
.UncompressedSize
);
265 entry
.header
.FilenameLength
= os::Byteswap::byteswap(entry
.header
.FilenameLength
);
266 entry
.header
.ExtraFieldLength
= os::Byteswap::byteswap(entry
.header
.ExtraFieldLength
);
269 if (entry
.header
.Sig
!= 0x04034b50)
270 return false; // local file headers end here.
274 c8
*tmp
= new c8
[entry
.header
.FilenameLength
+ 2];
275 File
->read(tmp
, entry
.header
.FilenameLength
);
276 tmp
[entry
.header
.FilenameLength
] = 0;
281 if (entry
.header
.ExtraFieldLength
)
282 File
->seek(entry
.header
.ExtraFieldLength
, true);
284 // if bit 3 was set, use CentralDirectory for setup
285 if (!ignoreGPBits
&& entry
.header
.GeneralBitFlag
& ZIP_INFO_IN_DATA_DESCRIPTOR
) {
286 SZIPFileCentralDirEnd dirEnd
;
289 // First place where the end record could be stored
290 File
->seek(File
->getSize() - 22);
291 const char endID
[] = {0x50, 0x4b, 0x05, 0x06, 0x0};
292 char tmp
[5] = {'\0'};
294 // search for the end record ID
295 while (!found
&& File
->getPos() > 0) {
300 if (!strcmp(endID
, tmp
)) {
315 File
->seek(-seek
, true);
317 File
->read(&dirEnd
, sizeof(dirEnd
));
318 #ifdef __BIG_ENDIAN__
319 dirEnd
.NumberDisk
= os::Byteswap::byteswap(dirEnd
.NumberDisk
);
320 dirEnd
.NumberStart
= os::Byteswap::byteswap(dirEnd
.NumberStart
);
321 dirEnd
.TotalDisk
= os::Byteswap::byteswap(dirEnd
.TotalDisk
);
322 dirEnd
.TotalEntries
= os::Byteswap::byteswap(dirEnd
.TotalEntries
);
323 dirEnd
.Size
= os::Byteswap::byteswap(dirEnd
.Size
);
324 dirEnd
.Offset
= os::Byteswap::byteswap(dirEnd
.Offset
);
325 dirEnd
.CommentLength
= os::Byteswap::byteswap(dirEnd
.CommentLength
);
327 FileInfo
.reserve(dirEnd
.TotalEntries
);
328 File
->seek(dirEnd
.Offset
);
329 while (scanCentralDirectoryHeader()) {
334 // store position in file
335 entry
.Offset
= File
->getPos();
336 // move forward length of data
337 File
->seek(entry
.header
.DataDescriptor
.CompressedSize
, true);
340 // os::Debuginfo::print("added file from archive", ZipFileName.c_str());
343 addItem(ZipFileName
, entry
.Offset
, entry
.header
.DataDescriptor
.UncompressedSize
, ZipFileName
.lastChar() == '/', FileInfo
.size());
344 FileInfo
.push_back(entry
);
349 //! scans for a local header, returns false if there is no more local file header.
350 bool CZipReader::scanCentralDirectoryHeader()
352 io::path ZipFileName
= "";
353 SZIPFileCentralDirFileHeader entry
;
354 File
->read(&entry
, sizeof(SZIPFileCentralDirFileHeader
));
356 #ifdef __BIG_ENDIAN__
357 entry
.Sig
= os::Byteswap::byteswap(entry
.Sig
);
358 entry
.VersionMadeBy
= os::Byteswap::byteswap(entry
.VersionMadeBy
);
359 entry
.VersionToExtract
= os::Byteswap::byteswap(entry
.VersionToExtract
);
360 entry
.GeneralBitFlag
= os::Byteswap::byteswap(entry
.GeneralBitFlag
);
361 entry
.CompressionMethod
= os::Byteswap::byteswap(entry
.CompressionMethod
);
362 entry
.LastModFileTime
= os::Byteswap::byteswap(entry
.LastModFileTime
);
363 entry
.LastModFileDate
= os::Byteswap::byteswap(entry
.LastModFileDate
);
364 entry
.CRC32
= os::Byteswap::byteswap(entry
.CRC32
);
365 entry
.CompressedSize
= os::Byteswap::byteswap(entry
.CompressedSize
);
366 entry
.UncompressedSize
= os::Byteswap::byteswap(entry
.UncompressedSize
);
367 entry
.FilenameLength
= os::Byteswap::byteswap(entry
.FilenameLength
);
368 entry
.ExtraFieldLength
= os::Byteswap::byteswap(entry
.ExtraFieldLength
);
369 entry
.FileCommentLength
= os::Byteswap::byteswap(entry
.FileCommentLength
);
370 entry
.DiskNumberStart
= os::Byteswap::byteswap(entry
.DiskNumberStart
);
371 entry
.InternalFileAttributes
= os::Byteswap::byteswap(entry
.InternalFileAttributes
);
372 entry
.ExternalFileAttributes
= os::Byteswap::byteswap(entry
.ExternalFileAttributes
);
373 entry
.RelativeOffsetOfLocalHeader
= os::Byteswap::byteswap(entry
.RelativeOffsetOfLocalHeader
);
376 if (entry
.Sig
!= 0x02014b50)
377 return false; // central dir headers end here.
379 const long pos
= File
->getPos();
380 File
->seek(entry
.RelativeOffsetOfLocalHeader
);
382 File
->seek(pos
+ entry
.FilenameLength
+ entry
.ExtraFieldLength
+ entry
.FileCommentLength
);
383 auto &lastInfo
= FileInfo
.back();
384 lastInfo
.header
.DataDescriptor
.CompressedSize
= entry
.CompressedSize
;
385 lastInfo
.header
.DataDescriptor
.UncompressedSize
= entry
.UncompressedSize
;
386 lastInfo
.header
.DataDescriptor
.CRC32
= entry
.CRC32
;
387 Files
.getLast().Size
= entry
.UncompressedSize
;
391 //! opens a file by file name
392 IReadFile
*CZipReader::createAndOpenFile(const io::path
&filename
)
394 s32 index
= findFile(filename
, false);
397 return createAndOpenFile(index
);
402 //! opens a file by index
403 IReadFile
*CZipReader::createAndOpenFile(u32 index
)
405 // Irrlicht supports 0, 8, 12, 14, 99
406 // 0 - The file is stored (no compression)
407 // 1 - The file is Shrunk
408 // 2 - The file is Reduced with compression factor 1
409 // 3 - The file is Reduced with compression factor 2
410 // 4 - The file is Reduced with compression factor 3
411 // 5 - The file is Reduced with compression factor 4
412 // 6 - The file is Imploded
413 // 7 - Reserved for Tokenizing compression algorithm
414 // 8 - The file is Deflated
415 // 9 - Reserved for enhanced Deflating
416 // 10 - PKWARE Date Compression Library Imploding
417 // 12 - bzip2 - Compression Method from libbz2, WinZip 10
418 // 14 - LZMA - Compression Method, WinZip 12
419 // 96 - Jpeg compression - Compression Method, WinZip 12
420 // 97 - WavPack - Compression Method, WinZip 11
421 // 98 - PPMd - Compression Method, WinZip 10
422 // 99 - AES encryption, WinZip 9
424 const SZipFileEntry
&e
= FileInfo
[Files
[index
].ID
];
426 s16 actualCompressionMethod
= e
.header
.CompressionMethod
;
427 IReadFile
*decrypted
= 0;
428 u8
*decryptedBuf
= 0;
429 u32 decryptedSize
= e
.header
.DataDescriptor
.CompressedSize
;
430 switch (actualCompressionMethod
) {
431 case 0: // no compression
436 return createLimitReadFile(Files
[index
].FullName
, File
, e
.Offset
, decryptedSize
);
439 const u32 uncompressedSize
= e
.header
.DataDescriptor
.UncompressedSize
;
440 c8
*pBuf
= new c8
[uncompressedSize
];
442 snprintf_irr(buf
, 64, "Not enough memory for decompressing %s", Files
[index
].FullName
.c_str());
443 os::Printer::log(buf
, ELL_ERROR
);
449 u8
*pcData
= decryptedBuf
;
451 pcData
= new u8
[decryptedSize
];
453 snprintf_irr(buf
, 64, "Not enough memory for decompressing %s", Files
[index
].FullName
.c_str());
454 os::Printer::log(buf
, ELL_ERROR
);
459 // memset(pcData, 0, decryptedSize);
460 File
->seek(e
.Offset
);
461 File
->read(pcData
, decryptedSize
);
464 // Setup the inflate stream.
468 stream
.next_in
= (Bytef
*)pcData
;
469 stream
.avail_in
= (uInt
)decryptedSize
;
470 stream
.next_out
= (Bytef
*)pBuf
;
471 stream
.avail_out
= uncompressedSize
;
472 stream
.zalloc
= (alloc_func
)0;
473 stream
.zfree
= (free_func
)0;
475 // Perform inflation. wbits < 0 indicates no zlib header inside the data.
476 err
= inflateInit2(&stream
, -MAX_WBITS
);
478 err
= inflate(&stream
, Z_FINISH
);
480 if (err
== Z_STREAM_END
)
492 snprintf_irr(buf
, 64, "Error decompressing %s", Files
[index
].FullName
.c_str());
493 os::Printer::log(buf
, ELL_ERROR
);
497 return FileSystem
->createMemoryReadFile(pBuf
, uncompressedSize
, Files
[index
].FullName
, true);
500 os::Printer::log("bzip2 decompression not supported. File cannot be read.", ELL_ERROR
);
504 os::Printer::log("lzma decompression not supported. File cannot be read.", ELL_ERROR
);
508 // If we come here with an encrypted file, decryption support is missing
509 os::Printer::log("Decryption support not enabled. File cannot be read.", ELL_ERROR
);
512 snprintf_irr(buf
, 64, "file has unsupported compression method. %s", Files
[index
].FullName
.c_str());
513 os::Printer::log(buf
, ELL_ERROR
);
518 } // end namespace io
519 } // end namespace irr