123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512 |
- // Copyright (C) 2002-2012 Nikolaus Gebhardt
- // This file is part of the "Irrlicht Engine".
- // For conditions of distribution and use, see copyright notice in irrlicht.h
- #include "CZipReader.h"
- #include "os.h"
- #include "CFileList.h"
- #include "CReadFile.h"
- #include "coreutil.h"
- #include <zlib.h> // use system lib
- namespace irr
- {
- namespace io
- {
- // -----------------------------------------------------------------------------
- // zip loader
- // -----------------------------------------------------------------------------
- //! Constructor
- CArchiveLoaderZIP::CArchiveLoaderZIP(io::IFileSystem *fs) :
- FileSystem(fs)
- {}
- //! returns true if the file maybe is able to be loaded by this class
- bool CArchiveLoaderZIP::isALoadableFileFormat(const io::path &filename) const
- {
- return core::hasFileExtension(filename, "zip", "pk3") ||
- core::hasFileExtension(filename, "gz", "tgz");
- }
- //! Check to see if the loader can create archives of this type.
- bool CArchiveLoaderZIP::isALoadableFileFormat(E_FILE_ARCHIVE_TYPE fileType) const
- {
- return (fileType == EFAT_ZIP || fileType == EFAT_GZIP);
- }
- //! Creates an archive from the filename
- /** \param file File handle to check.
- \return Pointer to newly created archive, or 0 upon error. */
- IFileArchive *CArchiveLoaderZIP::createArchive(const io::path &filename, bool ignoreCase, bool ignorePaths) const
- {
- IFileArchive *archive = 0;
- io::IReadFile *file = FileSystem->createAndOpenFile(filename);
- if (file) {
- archive = createArchive(file, ignoreCase, ignorePaths);
- file->drop();
- }
- return archive;
- }
- //! creates/loads an archive from the file.
- //! \return Pointer to the created archive. Returns 0 if loading failed.
- IFileArchive *CArchiveLoaderZIP::createArchive(io::IReadFile *file, bool ignoreCase, bool ignorePaths) const
- {
- IFileArchive *archive = 0;
- if (file) {
- file->seek(0);
- u16 sig;
- file->read(&sig, 2);
- #ifdef __BIG_ENDIAN__
- sig = os::Byteswap::byteswap(sig);
- #endif
- file->seek(0);
- bool isGZip = (sig == 0x8b1f);
- archive = new CZipReader(FileSystem, file, ignoreCase, ignorePaths, isGZip);
- }
- return archive;
- }
- //! Check if the file might be loaded by this class
- /** Check might look into the file.
- \param file File handle to check.
- \return True if file seems to be loadable. */
- bool CArchiveLoaderZIP::isALoadableFileFormat(io::IReadFile *file) const
- {
- SZIPFileHeader header;
- file->read(&header.Sig, 4);
- #ifdef __BIG_ENDIAN__
- header.Sig = os::Byteswap::byteswap(header.Sig);
- #endif
- return header.Sig == 0x04034b50 || // ZIP
- (header.Sig & 0xffff) == 0x8b1f; // gzip
- }
- // -----------------------------------------------------------------------------
- // zip archive
- // -----------------------------------------------------------------------------
- CZipReader::CZipReader(IFileSystem *fs, IReadFile *file, bool ignoreCase, bool ignorePaths, bool isGZip) :
- CFileList((file ? file->getFileName() : io::path("")), ignoreCase, ignorePaths), FileSystem(fs), File(file), IsGZip(isGZip)
- {
- if (File) {
- File->grab();
- // load file entries
- if (IsGZip)
- while (scanGZipHeader()) {
- }
- else
- while (scanZipHeader()) {
- }
- sort();
- }
- }
- CZipReader::~CZipReader()
- {
- if (File)
- File->drop();
- }
- //! get the archive type
- E_FILE_ARCHIVE_TYPE CZipReader::getType() const
- {
- return IsGZip ? EFAT_GZIP : EFAT_ZIP;
- }
- const IFileList *CZipReader::getFileList() const
- {
- return this;
- }
- //! scans for a local header, returns false if there is no more local file header.
- //! The gzip file format seems to think that there can be multiple files in a gzip file
- //! but none
- bool CZipReader::scanGZipHeader()
- {
- SZipFileEntry entry;
- entry.Offset = 0;
- memset(&entry.header, 0, sizeof(SZIPFileHeader));
- // read header
- SGZIPMemberHeader header;
- if (File->read(&header, sizeof(SGZIPMemberHeader)) == sizeof(SGZIPMemberHeader)) {
- #ifdef __BIG_ENDIAN__
- header.sig = os::Byteswap::byteswap(header.sig);
- header.time = os::Byteswap::byteswap(header.time);
- #endif
- // check header value
- if (header.sig != 0x8b1f)
- return false;
- // now get the file info
- if (header.flags & EGZF_EXTRA_FIELDS) {
- // read lenth of extra data
- u16 dataLen;
- File->read(&dataLen, 2);
- #ifdef __BIG_ENDIAN__
- dataLen = os::Byteswap::byteswap(dataLen);
- #endif
- // skip it
- File->seek(dataLen, true);
- }
- io::path ZipFileName = "";
- if (header.flags & EGZF_FILE_NAME) {
- c8 c;
- File->read(&c, 1);
- while (c) {
- ZipFileName.append(c);
- File->read(&c, 1);
- }
- } else {
- // no file name?
- ZipFileName = core::deletePathFromFilename(Path);
- // rename tgz to tar or remove gz extension
- if (core::hasFileExtension(ZipFileName, "tgz")) {
- ZipFileName[ZipFileName.size() - 2] = 'a';
- ZipFileName[ZipFileName.size() - 1] = 'r';
- } else if (core::hasFileExtension(ZipFileName, "gz")) {
- ZipFileName[ZipFileName.size() - 3] = 0;
- ZipFileName.validate();
- }
- }
- if (header.flags & EGZF_COMMENT) {
- c8 c = 'a';
- while (c)
- File->read(&c, 1);
- }
- if (header.flags & EGZF_CRC16)
- File->seek(2, true);
- // we are now at the start of the data blocks
- entry.Offset = File->getPos();
- entry.header.FilenameLength = ZipFileName.size();
- entry.header.CompressionMethod = header.compressionMethod;
- entry.header.DataDescriptor.CompressedSize = (File->getSize() - 8) - File->getPos();
- // seek to file end
- File->seek(entry.header.DataDescriptor.CompressedSize, true);
- // read CRC
- File->read(&entry.header.DataDescriptor.CRC32, 4);
- // read uncompressed size
- File->read(&entry.header.DataDescriptor.UncompressedSize, 4);
- #ifdef __BIG_ENDIAN__
- entry.header.DataDescriptor.CRC32 = os::Byteswap::byteswap(entry.header.DataDescriptor.CRC32);
- entry.header.DataDescriptor.UncompressedSize = os::Byteswap::byteswap(entry.header.DataDescriptor.UncompressedSize);
- #endif
- // now we've filled all the fields, this is just a standard deflate block
- addItem(ZipFileName, entry.Offset, entry.header.DataDescriptor.UncompressedSize, false, 0);
- FileInfo.push_back(entry);
- }
- // there's only one block of data in a gzip file
- return false;
- }
- //! scans for a local header, returns false if there is no more local file header.
- bool CZipReader::scanZipHeader(bool ignoreGPBits)
- {
- io::path ZipFileName = "";
- SZipFileEntry entry;
- entry.Offset = 0;
- memset(&entry.header, 0, sizeof(SZIPFileHeader));
- File->read(&entry.header, sizeof(SZIPFileHeader));
- #ifdef __BIG_ENDIAN__
- entry.header.Sig = os::Byteswap::byteswap(entry.header.Sig);
- entry.header.VersionToExtract = os::Byteswap::byteswap(entry.header.VersionToExtract);
- entry.header.GeneralBitFlag = os::Byteswap::byteswap(entry.header.GeneralBitFlag);
- entry.header.CompressionMethod = os::Byteswap::byteswap(entry.header.CompressionMethod);
- entry.header.LastModFileTime = os::Byteswap::byteswap(entry.header.LastModFileTime);
- entry.header.LastModFileDate = os::Byteswap::byteswap(entry.header.LastModFileDate);
- entry.header.DataDescriptor.CRC32 = os::Byteswap::byteswap(entry.header.DataDescriptor.CRC32);
- entry.header.DataDescriptor.CompressedSize = os::Byteswap::byteswap(entry.header.DataDescriptor.CompressedSize);
- entry.header.DataDescriptor.UncompressedSize = os::Byteswap::byteswap(entry.header.DataDescriptor.UncompressedSize);
- entry.header.FilenameLength = os::Byteswap::byteswap(entry.header.FilenameLength);
- entry.header.ExtraFieldLength = os::Byteswap::byteswap(entry.header.ExtraFieldLength);
- #endif
- if (entry.header.Sig != 0x04034b50)
- return false; // local file headers end here.
- // read filename
- {
- c8 *tmp = new c8[entry.header.FilenameLength + 2];
- File->read(tmp, entry.header.FilenameLength);
- tmp[entry.header.FilenameLength] = 0;
- ZipFileName = tmp;
- delete[] tmp;
- }
- if (entry.header.ExtraFieldLength)
- File->seek(entry.header.ExtraFieldLength, true);
- // if bit 3 was set, use CentralDirectory for setup
- if (!ignoreGPBits && entry.header.GeneralBitFlag & ZIP_INFO_IN_DATA_DESCRIPTOR) {
- SZIPFileCentralDirEnd dirEnd;
- FileInfo.clear();
- Files.clear();
- // First place where the end record could be stored
- File->seek(File->getSize() - 22);
- const char endID[] = {0x50, 0x4b, 0x05, 0x06, 0x0};
- char tmp[5] = {'\0'};
- bool found = false;
- // search for the end record ID
- while (!found && File->getPos() > 0) {
- int seek = 8;
- File->read(tmp, 4);
- switch (tmp[0]) {
- case 0x50:
- if (!strcmp(endID, tmp)) {
- seek = 4;
- found = true;
- }
- break;
- case 0x4b:
- seek = 5;
- break;
- case 0x05:
- seek = 6;
- break;
- case 0x06:
- seek = 7;
- break;
- }
- File->seek(-seek, true);
- }
- File->read(&dirEnd, sizeof(dirEnd));
- #ifdef __BIG_ENDIAN__
- dirEnd.NumberDisk = os::Byteswap::byteswap(dirEnd.NumberDisk);
- dirEnd.NumberStart = os::Byteswap::byteswap(dirEnd.NumberStart);
- dirEnd.TotalDisk = os::Byteswap::byteswap(dirEnd.TotalDisk);
- dirEnd.TotalEntries = os::Byteswap::byteswap(dirEnd.TotalEntries);
- dirEnd.Size = os::Byteswap::byteswap(dirEnd.Size);
- dirEnd.Offset = os::Byteswap::byteswap(dirEnd.Offset);
- dirEnd.CommentLength = os::Byteswap::byteswap(dirEnd.CommentLength);
- #endif
- FileInfo.reserve(dirEnd.TotalEntries);
- File->seek(dirEnd.Offset);
- while (scanCentralDirectoryHeader()) {
- }
- return false;
- }
- // store position in file
- entry.Offset = File->getPos();
- // move forward length of data
- File->seek(entry.header.DataDescriptor.CompressedSize, true);
- #ifdef _DEBUG
- // os::Debuginfo::print("added file from archive", ZipFileName.c_str());
- #endif
- addItem(ZipFileName, entry.Offset, entry.header.DataDescriptor.UncompressedSize, ZipFileName.lastChar() == '/', FileInfo.size());
- FileInfo.push_back(entry);
- return true;
- }
- //! scans for a local header, returns false if there is no more local file header.
- bool CZipReader::scanCentralDirectoryHeader()
- {
- io::path ZipFileName = "";
- SZIPFileCentralDirFileHeader entry;
- File->read(&entry, sizeof(SZIPFileCentralDirFileHeader));
- #ifdef __BIG_ENDIAN__
- entry.Sig = os::Byteswap::byteswap(entry.Sig);
- entry.VersionMadeBy = os::Byteswap::byteswap(entry.VersionMadeBy);
- entry.VersionToExtract = os::Byteswap::byteswap(entry.VersionToExtract);
- entry.GeneralBitFlag = os::Byteswap::byteswap(entry.GeneralBitFlag);
- entry.CompressionMethod = os::Byteswap::byteswap(entry.CompressionMethod);
- entry.LastModFileTime = os::Byteswap::byteswap(entry.LastModFileTime);
- entry.LastModFileDate = os::Byteswap::byteswap(entry.LastModFileDate);
- entry.CRC32 = os::Byteswap::byteswap(entry.CRC32);
- entry.CompressedSize = os::Byteswap::byteswap(entry.CompressedSize);
- entry.UncompressedSize = os::Byteswap::byteswap(entry.UncompressedSize);
- entry.FilenameLength = os::Byteswap::byteswap(entry.FilenameLength);
- entry.ExtraFieldLength = os::Byteswap::byteswap(entry.ExtraFieldLength);
- entry.FileCommentLength = os::Byteswap::byteswap(entry.FileCommentLength);
- entry.DiskNumberStart = os::Byteswap::byteswap(entry.DiskNumberStart);
- entry.InternalFileAttributes = os::Byteswap::byteswap(entry.InternalFileAttributes);
- entry.ExternalFileAttributes = os::Byteswap::byteswap(entry.ExternalFileAttributes);
- entry.RelativeOffsetOfLocalHeader = os::Byteswap::byteswap(entry.RelativeOffsetOfLocalHeader);
- #endif
- if (entry.Sig != 0x02014b50)
- return false; // central dir headers end here.
- const long pos = File->getPos();
- File->seek(entry.RelativeOffsetOfLocalHeader);
- scanZipHeader(true);
- File->seek(pos + entry.FilenameLength + entry.ExtraFieldLength + entry.FileCommentLength);
- auto &lastInfo = FileInfo.back();
- lastInfo.header.DataDescriptor.CompressedSize = entry.CompressedSize;
- lastInfo.header.DataDescriptor.UncompressedSize = entry.UncompressedSize;
- lastInfo.header.DataDescriptor.CRC32 = entry.CRC32;
- Files.getLast().Size = entry.UncompressedSize;
- return true;
- }
- //! opens a file by file name
- IReadFile *CZipReader::createAndOpenFile(const io::path &filename)
- {
- s32 index = findFile(filename, false);
- if (index != -1)
- return createAndOpenFile(index);
- return 0;
- }
- //! opens a file by index
- IReadFile *CZipReader::createAndOpenFile(u32 index)
- {
- // Irrlicht supports 0, 8, 12, 14, 99
- // 0 - The file is stored (no compression)
- // 1 - The file is Shrunk
- // 2 - The file is Reduced with compression factor 1
- // 3 - The file is Reduced with compression factor 2
- // 4 - The file is Reduced with compression factor 3
- // 5 - The file is Reduced with compression factor 4
- // 6 - The file is Imploded
- // 7 - Reserved for Tokenizing compression algorithm
- // 8 - The file is Deflated
- // 9 - Reserved for enhanced Deflating
- // 10 - PKWARE Date Compression Library Imploding
- // 12 - bzip2 - Compression Method from libbz2, WinZip 10
- // 14 - LZMA - Compression Method, WinZip 12
- // 96 - Jpeg compression - Compression Method, WinZip 12
- // 97 - WavPack - Compression Method, WinZip 11
- // 98 - PPMd - Compression Method, WinZip 10
- // 99 - AES encryption, WinZip 9
- const SZipFileEntry &e = FileInfo[Files[index].ID];
- char buf[64];
- s16 actualCompressionMethod = e.header.CompressionMethod;
- IReadFile *decrypted = 0;
- u8 *decryptedBuf = 0;
- u32 decryptedSize = e.header.DataDescriptor.CompressedSize;
- switch (actualCompressionMethod) {
- case 0: // no compression
- {
- if (decrypted)
- return decrypted;
- else
- return createLimitReadFile(Files[index].FullName, File, e.Offset, decryptedSize);
- }
- case 8: {
- const u32 uncompressedSize = e.header.DataDescriptor.UncompressedSize;
- c8 *pBuf = new c8[uncompressedSize];
- if (!pBuf) {
- snprintf_irr(buf, 64, "Not enough memory for decompressing %s", Files[index].FullName.c_str());
- os::Printer::log(buf, ELL_ERROR);
- if (decrypted)
- decrypted->drop();
- return 0;
- }
- u8 *pcData = decryptedBuf;
- if (!pcData) {
- pcData = new u8[decryptedSize];
- if (!pcData) {
- snprintf_irr(buf, 64, "Not enough memory for decompressing %s", Files[index].FullName.c_str());
- os::Printer::log(buf, ELL_ERROR);
- delete[] pBuf;
- return 0;
- }
- // memset(pcData, 0, decryptedSize);
- File->seek(e.Offset);
- File->read(pcData, decryptedSize);
- }
- // Setup the inflate stream.
- z_stream stream;
- s32 err;
- stream.next_in = (Bytef *)pcData;
- stream.avail_in = (uInt)decryptedSize;
- stream.next_out = (Bytef *)pBuf;
- stream.avail_out = uncompressedSize;
- stream.zalloc = (alloc_func)0;
- stream.zfree = (free_func)0;
- // Perform inflation. wbits < 0 indicates no zlib header inside the data.
- err = inflateInit2(&stream, -MAX_WBITS);
- if (err == Z_OK) {
- err = inflate(&stream, Z_FINISH);
- inflateEnd(&stream);
- if (err == Z_STREAM_END)
- err = Z_OK;
- err = Z_OK;
- inflateEnd(&stream);
- }
- if (decrypted)
- decrypted->drop();
- else
- delete[] pcData;
- if (err != Z_OK) {
- snprintf_irr(buf, 64, "Error decompressing %s", Files[index].FullName.c_str());
- os::Printer::log(buf, ELL_ERROR);
- delete[] pBuf;
- return 0;
- } else
- return FileSystem->createMemoryReadFile(pBuf, uncompressedSize, Files[index].FullName, true);
- }
- case 12: {
- os::Printer::log("bzip2 decompression not supported. File cannot be read.", ELL_ERROR);
- return 0;
- }
- case 14: {
- os::Printer::log("lzma decompression not supported. File cannot be read.", ELL_ERROR);
- return 0;
- }
- case 99:
- // If we come here with an encrypted file, decryption support is missing
- os::Printer::log("Decryption support not enabled. File cannot be read.", ELL_ERROR);
- return 0;
- default:
- snprintf_irr(buf, 64, "file has unsupported compression method. %s", Files[index].FullName.c_str());
- os::Printer::log(buf, ELL_ERROR);
- return 0;
- };
- }
- } // end namespace io
- } // end namespace irr
|