]> git.lizzy.rs Git - irrlicht.git/blob - source/Irrlicht/CZipReader.cpp
Reduce IrrCompileConfig usage to files that actually need it
[irrlicht.git] / source / Irrlicht / CZipReader.cpp
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
4 \r
5 #include "CZipReader.h"\r
6 \r
7 #include "os.h"\r
8 \r
9 \r
10 #include "CFileList.h"\r
11 #include "CReadFile.h"\r
12 #include "coreutil.h"\r
13 \r
14 #include <zlib.h> // use system lib\r
15 \r
16 namespace irr\r
17 {\r
18 namespace io\r
19 {\r
20 \r
21 \r
22 // -----------------------------------------------------------------------------\r
23 // zip loader\r
24 // -----------------------------------------------------------------------------\r
25 \r
26 //! Constructor\r
27 CArchiveLoaderZIP::CArchiveLoaderZIP(io::IFileSystem* fs)\r
28 : FileSystem(fs)\r
29 {\r
30         #ifdef _DEBUG\r
31         setDebugName("CArchiveLoaderZIP");\r
32         #endif\r
33 }\r
34 \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
37 {\r
38         return core::hasFileExtension(filename, "zip", "pk3") ||\r
39                core::hasFileExtension(filename, "gz", "tgz");\r
40 }\r
41 \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
44 {\r
45         return (fileType == EFAT_ZIP || fileType == EFAT_GZIP);\r
46 }\r
47 \r
48 \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
53 {\r
54         IFileArchive *archive = 0;\r
55         io::IReadFile* file = FileSystem->createAndOpenFile(filename);\r
56 \r
57         if (file)\r
58         {\r
59                 archive = createArchive(file, ignoreCase, ignorePaths);\r
60                 file->drop();\r
61         }\r
62 \r
63         return archive;\r
64 }\r
65 \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
69 {\r
70         IFileArchive *archive = 0;\r
71         if (file)\r
72         {\r
73                 file->seek(0);\r
74 \r
75                 u16 sig;\r
76                 file->read(&sig, 2);\r
77 \r
78 #ifdef __BIG_ENDIAN__\r
79                 sig = os::Byteswap::byteswap(sig);\r
80 #endif\r
81 \r
82                 file->seek(0);\r
83 \r
84                 bool isGZip = (sig == 0x8b1f);\r
85 \r
86                 archive = new CZipReader(FileSystem, file, ignoreCase, ignorePaths, isGZip);\r
87         }\r
88         return archive;\r
89 }\r
90 \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
96 {\r
97         SZIPFileHeader header;\r
98 \r
99         file->read( &header.Sig, 4 );\r
100 #ifdef __BIG_ENDIAN__\r
101         header.Sig = os::Byteswap::byteswap(header.Sig);\r
102 #endif\r
103 \r
104         return header.Sig == 0x04034b50 || // ZIP\r
105                    (header.Sig&0xffff) == 0x8b1f; // gzip\r
106 }\r
107 \r
108 // -----------------------------------------------------------------------------\r
109 // zip archive\r
110 // -----------------------------------------------------------------------------\r
111 \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
114 {\r
115         #ifdef _DEBUG\r
116         setDebugName("CZipReader");\r
117         #endif\r
118 \r
119         if (File)\r
120         {\r
121                 File->grab();\r
122 \r
123                 // load file entries\r
124                 if (IsGZip)\r
125                         while (scanGZipHeader()) { }\r
126                 else\r
127                         while (scanZipHeader()) { }\r
128 \r
129                 sort();\r
130         }\r
131 }\r
132 \r
133 CZipReader::~CZipReader()\r
134 {\r
135         if (File)\r
136                 File->drop();\r
137 }\r
138 \r
139 \r
140 //! get the archive type\r
141 E_FILE_ARCHIVE_TYPE CZipReader::getType() const\r
142 {\r
143         return IsGZip ? EFAT_GZIP : EFAT_ZIP;\r
144 }\r
145 \r
146 const IFileList* CZipReader::getFileList() const\r
147 {\r
148         return this;\r
149 }\r
150 \r
151 \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
154 //! but none\r
155 bool CZipReader::scanGZipHeader()\r
156 {\r
157         SZipFileEntry entry;\r
158         entry.Offset = 0;\r
159         memset(&entry.header, 0, sizeof(SZIPFileHeader));\r
160 \r
161         // read header\r
162         SGZIPMemberHeader header;\r
163         if (File->read(&header, sizeof(SGZIPMemberHeader)) == sizeof(SGZIPMemberHeader))\r
164         {\r
165 \r
166 #ifdef __BIG_ENDIAN__\r
167                 header.sig = os::Byteswap::byteswap(header.sig);\r
168                 header.time = os::Byteswap::byteswap(header.time);\r
169 #endif\r
170 \r
171                 // check header value\r
172                 if (header.sig != 0x8b1f)\r
173                         return false;\r
174 \r
175                 // now get the file info\r
176                 if (header.flags & EGZF_EXTRA_FIELDS)\r
177                 {\r
178                         // read lenth of extra data\r
179                         u16 dataLen;\r
180 \r
181                         File->read(&dataLen, 2);\r
182 \r
183 #ifdef __BIG_ENDIAN__\r
184                         dataLen = os::Byteswap::byteswap(dataLen);\r
185 #endif\r
186 \r
187                         // skip it\r
188                         File->seek(dataLen, true);\r
189                 }\r
190 \r
191                 io::path ZipFileName = "";\r
192 \r
193                 if (header.flags & EGZF_FILE_NAME)\r
194                 {\r
195                         c8 c;\r
196                         File->read(&c, 1);\r
197                         while (c)\r
198                         {\r
199                                 ZipFileName.append(c);\r
200                                 File->read(&c, 1);\r
201                         }\r
202                 }\r
203                 else\r
204                 {\r
205                         // no file name?\r
206                         ZipFileName = Path;\r
207                         core::deletePathFromFilename(ZipFileName);\r
208 \r
209                         // rename tgz to tar or remove gz extension\r
210                         if (core::hasFileExtension(ZipFileName, "tgz"))\r
211                         {\r
212                                 ZipFileName[ ZipFileName.size() - 2] = 'a';\r
213                                 ZipFileName[ ZipFileName.size() - 1] = 'r';\r
214                         }\r
215                         else if (core::hasFileExtension(ZipFileName, "gz"))\r
216                         {\r
217                                 ZipFileName[ ZipFileName.size() - 3] = 0;\r
218                                 ZipFileName.validate();\r
219                         }\r
220                 }\r
221 \r
222                 if (header.flags & EGZF_COMMENT)\r
223                 {\r
224                         c8 c='a';\r
225                         while (c)\r
226                                 File->read(&c, 1);\r
227                 }\r
228 \r
229                 if (header.flags & EGZF_CRC16)\r
230                         File->seek(2, true);\r
231 \r
232                 // we are now at the start of the data blocks\r
233                 entry.Offset = File->getPos();\r
234 \r
235                 entry.header.FilenameLength = ZipFileName.size();\r
236 \r
237                 entry.header.CompressionMethod = header.compressionMethod;\r
238                 entry.header.DataDescriptor.CompressedSize = (File->getSize() - 8) - File->getPos();\r
239 \r
240                 // seek to file end\r
241                 File->seek(entry.header.DataDescriptor.CompressedSize, true);\r
242 \r
243                 // read CRC\r
244                 File->read(&entry.header.DataDescriptor.CRC32, 4);\r
245                 // read uncompressed size\r
246                 File->read(&entry.header.DataDescriptor.UncompressedSize, 4);\r
247 \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
251 #endif\r
252 \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
256         }\r
257 \r
258         // there's only one block of data in a gzip file\r
259         return false;\r
260 }\r
261 \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
264 {\r
265         io::path ZipFileName = "";\r
266         SZipFileEntry entry;\r
267         entry.Offset = 0;\r
268         memset(&entry.header, 0, sizeof(SZIPFileHeader));\r
269 \r
270         File->read(&entry.header, sizeof(SZIPFileHeader));\r
271 \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
284 #endif\r
285 \r
286         if (entry.header.Sig != 0x04034b50)\r
287                 return false; // local file headers end here.\r
288 \r
289         // read filename\r
290         {\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
294                 ZipFileName = tmp;\r
295                 delete [] tmp;\r
296         }\r
297 \r
298         if (entry.header.ExtraFieldLength)\r
299                 File->seek(entry.header.ExtraFieldLength, true);\r
300 \r
301         // if bit 3 was set, use CentralDirectory for setup\r
302         if (!ignoreGPBits && entry.header.GeneralBitFlag & ZIP_INFO_IN_DATA_DESCRIPTOR)\r
303         {\r
304                 SZIPFileCentralDirEnd dirEnd;\r
305                 FileInfo.clear();\r
306                 Files.clear();\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
311                 bool found=false;\r
312                 // search for the end record ID\r
313                 while (!found && File->getPos()>0)\r
314                 {\r
315                         int seek=8;\r
316                         File->read(tmp, 4);\r
317                         switch (tmp[0])\r
318                         {\r
319                         case 0x50:\r
320                                 if (!strcmp(endID, tmp))\r
321                                 {\r
322                                         seek=4;\r
323                                         found=true;\r
324                                 }\r
325                                 break;\r
326                         case 0x4b:\r
327                                 seek=5;\r
328                                 break;\r
329                         case 0x05:\r
330                                 seek=6;\r
331                                 break;\r
332                         case 0x06:\r
333                                 seek=7;\r
334                                 break;\r
335                         }\r
336                         File->seek(-seek, true);\r
337                 }\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
347 #endif\r
348                 FileInfo.reallocate(dirEnd.TotalEntries);\r
349                 File->seek(dirEnd.Offset);\r
350                 while (scanCentralDirectoryHeader()) { }\r
351                 return false;\r
352         }\r
353 \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
358 \r
359         #ifdef _DEBUG\r
360         //os::Debuginfo::print("added file from archive", ZipFileName.c_str());\r
361         #endif\r
362 \r
363         addItem(ZipFileName, entry.Offset, entry.header.DataDescriptor.UncompressedSize, ZipFileName.lastChar()=='/', FileInfo.size());\r
364         FileInfo.push_back(entry);\r
365 \r
366         return true;\r
367 }\r
368 \r
369 \r
370 //! scans for a local header, returns false if there is no more local file header.\r
371 bool CZipReader::scanCentralDirectoryHeader()\r
372 {\r
373         io::path ZipFileName = "";\r
374         SZIPFileCentralDirFileHeader entry;\r
375         File->read(&entry, sizeof(SZIPFileCentralDirFileHeader));\r
376 \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
395 #endif\r
396 \r
397         if (entry.Sig != 0x02014b50)\r
398                 return false; // central dir headers end here.\r
399 \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
408         return true;\r
409 }\r
410 \r
411 \r
412 //! opens a file by file name\r
413 IReadFile* CZipReader::createAndOpenFile(const io::path& filename)\r
414 {\r
415         s32 index = findFile(filename, false);\r
416 \r
417         if (index != -1)\r
418                 return createAndOpenFile(index);\r
419 \r
420         return 0;\r
421 }\r
422 \r
423 //! opens a file by index\r
424 IReadFile* CZipReader::createAndOpenFile(u32 index)\r
425 {\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
444 \r
445         const SZipFileEntry &e = FileInfo[Files[index].ID];\r
446         char buf[64];\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
452         {\r
453         case 0: // no compression\r
454                 {\r
455                         if (decrypted)\r
456                                 return decrypted;\r
457                         else\r
458                                 return createLimitReadFile(Files[index].FullName, File, e.Offset, decryptedSize);\r
459                 }\r
460         case 8:\r
461                 {\r
462                         const u32 uncompressedSize = e.header.DataDescriptor.UncompressedSize;\r
463                         c8* pBuf = new c8[ uncompressedSize ];\r
464                         if (!pBuf)\r
465                         {\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
468                                 if (decrypted)\r
469                                         decrypted->drop();\r
470                                 return 0;\r
471                         }\r
472 \r
473                         u8 *pcData = decryptedBuf;\r
474                         if (!pcData)\r
475                         {\r
476                                 pcData = new u8[decryptedSize];\r
477                                 if (!pcData)\r
478                                 {\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
481                                         delete [] pBuf;\r
482                                         return 0;\r
483                                 }\r
484 \r
485                                 //memset(pcData, 0, decryptedSize);\r
486                                 File->seek(e.Offset);\r
487                                 File->read(pcData, decryptedSize);\r
488                         }\r
489 \r
490                         // Setup the inflate stream.\r
491                         z_stream stream;\r
492                         s32 err;\r
493 \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
500 \r
501                         // Perform inflation. wbits < 0 indicates no zlib header inside the data.\r
502                         err = inflateInit2(&stream, -MAX_WBITS);\r
503                         if (err == Z_OK)\r
504                         {\r
505                                 err = inflate(&stream, Z_FINISH);\r
506                                 inflateEnd(&stream);\r
507                                 if (err == Z_STREAM_END)\r
508                                         err = Z_OK;\r
509                                 err = Z_OK;\r
510                                 inflateEnd(&stream);\r
511                         }\r
512 \r
513                         if (decrypted)\r
514                                 decrypted->drop();\r
515                         else\r
516                                 delete[] pcData;\r
517 \r
518                         if (err != Z_OK)\r
519                         {\r
520                                 snprintf_irr ( buf, 64, "Error decompressing %s", Files[index].FullName.c_str() );\r
521                                 os::Printer::log( buf, ELL_ERROR);\r
522                                 delete [] pBuf;\r
523                                 return 0;\r
524                         }\r
525                         else\r
526                                 return FileSystem->createMemoryReadFile(pBuf, uncompressedSize, Files[index].FullName, true);\r
527                 }\r
528         case 12:\r
529                 {\r
530                         os::Printer::log("bzip2 decompression not supported. File cannot be read.", ELL_ERROR);\r
531                         return 0;\r
532                 }\r
533         case 14:\r
534                 {\r
535                         os::Printer::log("lzma decompression not supported. File cannot be read.", ELL_ERROR);\r
536                         return 0;\r
537                 }\r
538         case 99:\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
541                 return 0;\r
542         default:\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
545                 return 0;\r
546         };\r
547 \r
548 }\r
549 \r
550 } // end namespace io\r
551 } // end namespace irr\r