mirror of
https://github.com/minetest/irrlicht.git
synced 2024-11-14 06:20:26 +01:00
2bf1d12353
find -type f | # list all regular files grep -E '\.(h|cpp|mm)$' | # filter for source files grep -v '/mt_' | # filter out generated files grep -v '/vendor/' | # and vendored GL grep -v '/test/image_loader_test.cpp' | # and this file (has giant literals arrays) xargs -n 1 -P $(nproc) clang-format -i # reformat everything Co-authored-by: numzero <numzer0@yandex.ru>
520 lines
15 KiB
C++
520 lines
15 KiB
C++
// 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)
|
|
{
|
|
#ifdef _DEBUG
|
|
setDebugName("CArchiveLoaderZIP");
|
|
#endif
|
|
}
|
|
|
|
//! 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)
|
|
{
|
|
#ifdef _DEBUG
|
|
setDebugName("CZipReader");
|
|
#endif
|
|
|
|
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 = Path;
|
|
core::deletePathFromFilename(ZipFileName);
|
|
|
|
// 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.reallocate(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);
|
|
FileInfo.getLast().header.DataDescriptor.CompressedSize = entry.CompressedSize;
|
|
FileInfo.getLast().header.DataDescriptor.UncompressedSize = entry.UncompressedSize;
|
|
FileInfo.getLast().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
|