Move SMaterial std::hash impl to its header
[minetest.git] / irr / src / CZipReader.cpp
blob036f6302acc439b9257bbea3466784aae2f73499
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"
7 #include "os.h"
9 #include "CFileList.h"
10 #include "CReadFile.h"
11 #include "coreutil.h"
13 #include <zlib.h> // use system lib
15 namespace irr
17 namespace io
20 // -----------------------------------------------------------------------------
21 // zip loader
22 // -----------------------------------------------------------------------------
24 //! Constructor
25 CArchiveLoaderZIP::CArchiveLoaderZIP(io::IFileSystem *fs) :
26 FileSystem(fs)
28 #ifdef _DEBUG
29 setDebugName("CArchiveLoaderZIP");
30 #endif
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);
54 if (file) {
55 archive = createArchive(file, ignoreCase, ignorePaths);
56 file->drop();
59 return archive;
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;
67 if (file) {
68 file->seek(0);
70 u16 sig;
71 file->read(&sig, 2);
73 #ifdef __BIG_ENDIAN__
74 sig = os::Byteswap::byteswap(sig);
75 #endif
77 file->seek(0);
79 bool isGZip = (sig == 0x8b1f);
81 archive = new CZipReader(FileSystem, file, ignoreCase, ignorePaths, isGZip);
83 return archive;
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);
95 #ifdef __BIG_ENDIAN__
96 header.Sig = os::Byteswap::byteswap(header.Sig);
97 #endif
99 return header.Sig == 0x04034b50 || // ZIP
100 (header.Sig & 0xffff) == 0x8b1f; // gzip
103 // -----------------------------------------------------------------------------
104 // zip archive
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)
110 #ifdef _DEBUG
111 setDebugName("CZipReader");
112 #endif
114 if (File) {
115 File->grab();
117 // load file entries
118 if (IsGZip)
119 while (scanGZipHeader()) {
121 else
122 while (scanZipHeader()) {
125 sort();
129 CZipReader::~CZipReader()
131 if (File)
132 File->drop();
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
143 return this;
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
148 //! but none
149 bool CZipReader::scanGZipHeader()
151 SZipFileEntry entry;
152 entry.Offset = 0;
153 memset(&entry.header, 0, sizeof(SZIPFileHeader));
155 // read header
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);
162 #endif
164 // check header value
165 if (header.sig != 0x8b1f)
166 return false;
168 // now get the file info
169 if (header.flags & EGZF_EXTRA_FIELDS) {
170 // read lenth of extra data
171 u16 dataLen;
173 File->read(&dataLen, 2);
175 #ifdef __BIG_ENDIAN__
176 dataLen = os::Byteswap::byteswap(dataLen);
177 #endif
179 // skip it
180 File->seek(dataLen, true);
183 io::path ZipFileName = "";
185 if (header.flags & EGZF_FILE_NAME) {
186 c8 c;
187 File->read(&c, 1);
188 while (c) {
189 ZipFileName.append(c);
190 File->read(&c, 1);
192 } else {
193 // no file name?
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) {
207 c8 c = 'a';
208 while (c)
209 File->read(&c, 1);
212 if (header.flags & EGZF_CRC16)
213 File->seek(2, true);
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();
223 // seek to file end
224 File->seek(entry.header.DataDescriptor.CompressedSize, true);
226 // read CRC
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);
234 #endif
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
242 return false;
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 = "";
249 SZipFileEntry entry;
250 entry.Offset = 0;
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);
267 #endif
269 if (entry.header.Sig != 0x04034b50)
270 return false; // local file headers end here.
272 // read filename
274 c8 *tmp = new c8[entry.header.FilenameLength + 2];
275 File->read(tmp, entry.header.FilenameLength);
276 tmp[entry.header.FilenameLength] = 0;
277 ZipFileName = tmp;
278 delete[] tmp;
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;
287 FileInfo.clear();
288 Files.clear();
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'};
293 bool found = false;
294 // search for the end record ID
295 while (!found && File->getPos() > 0) {
296 int seek = 8;
297 File->read(tmp, 4);
298 switch (tmp[0]) {
299 case 0x50:
300 if (!strcmp(endID, tmp)) {
301 seek = 4;
302 found = true;
304 break;
305 case 0x4b:
306 seek = 5;
307 break;
308 case 0x05:
309 seek = 6;
310 break;
311 case 0x06:
312 seek = 7;
313 break;
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);
326 #endif
327 FileInfo.reserve(dirEnd.TotalEntries);
328 File->seek(dirEnd.Offset);
329 while (scanCentralDirectoryHeader()) {
331 return false;
334 // store position in file
335 entry.Offset = File->getPos();
336 // move forward length of data
337 File->seek(entry.header.DataDescriptor.CompressedSize, true);
339 #ifdef _DEBUG
340 // os::Debuginfo::print("added file from archive", ZipFileName.c_str());
341 #endif
343 addItem(ZipFileName, entry.Offset, entry.header.DataDescriptor.UncompressedSize, ZipFileName.lastChar() == '/', FileInfo.size());
344 FileInfo.push_back(entry);
346 return true;
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);
374 #endif
376 if (entry.Sig != 0x02014b50)
377 return false; // central dir headers end here.
379 const long pos = File->getPos();
380 File->seek(entry.RelativeOffsetOfLocalHeader);
381 scanZipHeader(true);
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;
388 return true;
391 //! opens a file by file name
392 IReadFile *CZipReader::createAndOpenFile(const io::path &filename)
394 s32 index = findFile(filename, false);
396 if (index != -1)
397 return createAndOpenFile(index);
399 return 0;
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];
425 char buf[64];
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
433 if (decrypted)
434 return decrypted;
435 else
436 return createLimitReadFile(Files[index].FullName, File, e.Offset, decryptedSize);
438 case 8: {
439 const u32 uncompressedSize = e.header.DataDescriptor.UncompressedSize;
440 c8 *pBuf = new c8[uncompressedSize];
441 if (!pBuf) {
442 snprintf_irr(buf, 64, "Not enough memory for decompressing %s", Files[index].FullName.c_str());
443 os::Printer::log(buf, ELL_ERROR);
444 if (decrypted)
445 decrypted->drop();
446 return 0;
449 u8 *pcData = decryptedBuf;
450 if (!pcData) {
451 pcData = new u8[decryptedSize];
452 if (!pcData) {
453 snprintf_irr(buf, 64, "Not enough memory for decompressing %s", Files[index].FullName.c_str());
454 os::Printer::log(buf, ELL_ERROR);
455 delete[] pBuf;
456 return 0;
459 // memset(pcData, 0, decryptedSize);
460 File->seek(e.Offset);
461 File->read(pcData, decryptedSize);
464 // Setup the inflate stream.
465 z_stream stream;
466 s32 err;
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);
477 if (err == Z_OK) {
478 err = inflate(&stream, Z_FINISH);
479 inflateEnd(&stream);
480 if (err == Z_STREAM_END)
481 err = Z_OK;
482 err = Z_OK;
483 inflateEnd(&stream);
486 if (decrypted)
487 decrypted->drop();
488 else
489 delete[] pcData;
491 if (err != Z_OK) {
492 snprintf_irr(buf, 64, "Error decompressing %s", Files[index].FullName.c_str());
493 os::Printer::log(buf, ELL_ERROR);
494 delete[] pBuf;
495 return 0;
496 } else
497 return FileSystem->createMemoryReadFile(pBuf, uncompressedSize, Files[index].FullName, true);
499 case 12: {
500 os::Printer::log("bzip2 decompression not supported. File cannot be read.", ELL_ERROR);
501 return 0;
503 case 14: {
504 os::Printer::log("lzma decompression not supported. File cannot be read.", ELL_ERROR);
505 return 0;
507 case 99:
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);
510 return 0;
511 default:
512 snprintf_irr(buf, 64, "file has unsupported compression method. %s", Files[index].FullName.c_str());
513 os::Printer::log(buf, ELL_ERROR);
514 return 0;
518 } // end namespace io
519 } // end namespace irr