// 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"

// This method is used for error output from bzip2.
extern "C" void bz_internal_error(int errorCode)
{
	irr::os::Printer::log("Error in bzip2 handling", irr::core::stringc(errorCode), irr::ELL_ERROR);
}

#ifdef __IRR_COMPILE_WITH_ZIP_ARCHIVE_LOADER_

#include "CFileList.h"
#include "CReadFile.h"
#include "coreutil.h"

#include "IrrCompileConfig.h"
#ifdef _IRR_COMPILE_WITH_ZLIB_
	#ifndef _IRR_USE_NON_SYSTEM_ZLIB_
	#include <zlib.h> // use system lib
	#else
	#include "zlib/zlib.h"
	#endif

	#ifdef _IRR_COMPILE_WITH_ZIP_ENCRYPTION_
	#include "aesGladman/fileenc.h"
	#endif
	#ifdef _IRR_COMPILE_WITH_BZIP2_
	#ifndef _IRR_USE_NON_SYSTEM_BZLIB_
	#include <bzlib.h>
	#else
	#include "bzip2/bzlib.h"
	#endif
	#endif
	#ifdef _IRR_COMPILE_WITH_LZMA_
	#include "lzma/LzmaDec.h"
	#endif
#endif

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;
	}

#ifdef _IRR_COMPILE_WITH_ZIP_ENCRYPTION_
	// AES encryption
	if ((entry.header.GeneralBitFlag & ZIP_FILE_ENCRYPTED) && (entry.header.CompressionMethod == 99))
	{
		s16 restSize = entry.header.ExtraFieldLength;
		SZipFileExtraHeader extraHeader;
		while (restSize)
		{
			File->read(&extraHeader, sizeof(extraHeader));
#ifdef __BIG_ENDIAN__
			extraHeader.ID = os::Byteswap::byteswap(extraHeader.ID);
			extraHeader.Size = os::Byteswap::byteswap(extraHeader.Size);
#endif
			restSize -= sizeof(extraHeader);
			if (extraHeader.ID==(s16)0x9901)
			{
				SZipFileAESExtraData data;
				File->read(&data, sizeof(data));
#ifdef __BIG_ENDIAN__
				data.Version = os::Byteswap::byteswap(data.Version);
				data.CompressionMode = os::Byteswap::byteswap(data.CompressionMode);
#endif
				restSize -= sizeof(data);
				if (data.Vendor[0]=='A' && data.Vendor[1]=='E')
				{
					// encode values into Sig
					// AE-Version | Strength | ActualMode
					entry.header.Sig =
						((data.Version & 0xff) << 24) |
						(data.EncryptionStrength << 16) |
						(data.CompressionMode);
					File->seek(restSize, true);
					break;
				}
			}
		}
	}
	// move forward length of extra field.
	else
#endif
	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;
}

#ifdef _IRR_COMPILE_WITH_LZMA_
//! Used for LZMA decompression. The lib has no default memory management
namespace
{
	void *SzAlloc(void *p, size_t size)
	{
		(void)p; // disable unused variable warnings
		return malloc(size);
	}
	void SzFree(void *p, void *address)
	{
		(void)p; // disable unused variable warnings
		free(address);
	}
	ISzAlloc lzmaAlloc = { SzAlloc, SzFree };
}
#endif

//! 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];
	wchar_t buf[64];
	s16 actualCompressionMethod=e.header.CompressionMethod;
	IReadFile* decrypted=0;
	u8* decryptedBuf=0;
	u32 decryptedSize=e.header.DataDescriptor.CompressedSize;
#ifdef _IRR_COMPILE_WITH_ZIP_ENCRYPTION_
	if ((e.header.GeneralBitFlag & ZIP_FILE_ENCRYPTED) && (e.header.CompressionMethod == 99))
	{
		os::Printer::log("Reading encrypted file.");
		u8 salt[16]={0};
		const u16 saltSize = (((e.header.Sig & 0x00ff0000) >>16)+1)*4;
		File->seek(e.Offset);
		File->read(salt, saltSize);
		char pwVerification[2];
		char pwVerificationFile[2];
		File->read(pwVerification, 2);
		fcrypt_ctx zctx; // the encryption context
		int rc = fcrypt_init(
			(e.header.Sig & 0x00ff0000) >>16,
			(const unsigned char*)Password.c_str(), // the password
			Password.size(), // number of bytes in password
			salt, // the salt
			(unsigned char*)pwVerificationFile, // on return contains password verifier
			&zctx); // encryption context
		if (strncmp(pwVerificationFile, pwVerification, 2))
		{
			os::Printer::log("Wrong password");
			return 0;
		}
		decryptedSize= e.header.DataDescriptor.CompressedSize-saltSize-12;
		decryptedBuf= new u8[decryptedSize];
		u32 c = 0;
		while ((c+32768)<=decryptedSize)
		{
			File->read(decryptedBuf+c, 32768);
			fcrypt_decrypt(
				decryptedBuf+c, // pointer to the data to decrypt
				32768,   // how many bytes to decrypt
				&zctx); // decryption context
			c+=32768;
		}
		File->read(decryptedBuf+c, decryptedSize-c);
		fcrypt_decrypt(
			decryptedBuf+c, // pointer to the data to decrypt
			decryptedSize-c,   // how many bytes to decrypt
			&zctx); // decryption context

		char fileMAC[10];
		char resMAC[10];
		rc = fcrypt_end(
			(unsigned char*)resMAC, // on return contains the authentication code
			&zctx); // encryption context
		if (rc != 10)
		{
			os::Printer::log("Error on encryption closing");
			delete [] decryptedBuf;
			return 0;
		}
		File->read(fileMAC, 10);
		if (strncmp(fileMAC, resMAC, 10))
		{
			os::Printer::log("Error on encryption check");
			delete [] decryptedBuf;
			return 0;
		}
		decrypted = FileSystem->createMemoryReadFile(decryptedBuf, decryptedSize, Files[index].FullName, true);
		actualCompressionMethod = (e.header.Sig & 0xffff);
#if 0
		if ((e.header.Sig & 0xff000000)==0x01000000)
		{
		}
		else if ((e.header.Sig & 0xff000000)==0x02000000)
		{
		}
		else
		{
			os::Printer::log("Unknown encryption method");
			return 0;
		}
#endif
	}
#endif
	switch(actualCompressionMethod)
	{
	case 0: // no compression
		{
			if (decrypted)
				return decrypted;
			else
				return createLimitReadFile(Files[index].FullName, File, e.Offset, decryptedSize);
		}
	case 8:
		{
  			#ifdef _IRR_COMPILE_WITH_ZLIB_

			const u32 uncompressedSize = e.header.DataDescriptor.UncompressedSize;
			c8* pBuf = new c8[ uncompressedSize ];
			if (!pBuf)
			{
				swprintf_irr ( buf, 64, L"Not enough memory for decompressing %s", core::stringw(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)
				{
					swprintf_irr ( buf, 64, L"Not enough memory for decompressing %s", core::stringw(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)
			{
				swprintf_irr ( buf, 64, L"Error decompressing %s", core::stringw(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);

			#else
			return 0; // zlib not compiled, we cannot decompress the data.
			#endif
		}
	case 12:
		{
  			#ifdef _IRR_COMPILE_WITH_BZIP2_

			const u32 uncompressedSize = e.header.DataDescriptor.UncompressedSize;
			c8* pBuf = new c8[ uncompressedSize ];
			if (!pBuf)
			{
				swprintf_irr ( buf, 64, L"Not enough memory for decompressing %s", core::stringw(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)
				{
					swprintf_irr ( buf, 64, L"Not enough memory for decompressing %s", core::stringw(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);
			}

			bz_stream bz_ctx;
			memset(&bz_ctx, 0, sizeof(bz_ctx));
			/* use BZIP2's default memory allocation
			bz_ctx->bzalloc = NULL;
			bz_ctx->bzfree  = NULL;
			bz_ctx->opaque  = NULL;
			*/
			int err = BZ2_bzDecompressInit(&bz_ctx, 0, 0); /* decompression */
			if(err != BZ_OK)
			{
				os::Printer::log("bzip2 decompression failed. File cannot be read.", ELL_ERROR);
				return 0;
			}
			bz_ctx.next_in = (char*)pcData;
			bz_ctx.avail_in = decryptedSize;
			/* pass all input to decompressor */
			bz_ctx.next_out = pBuf;
			bz_ctx.avail_out = uncompressedSize;
			err = BZ2_bzDecompress(&bz_ctx);
			err = BZ2_bzDecompressEnd(&bz_ctx);

			if (decrypted)
				decrypted->drop();
			else
				delete[] pcData;

			if (err != BZ_OK)
			{
				swprintf_irr ( buf, 64, L"Error decompressing %s", core::stringw(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);

			#else
			os::Printer::log("bzip2 decompression not supported. File cannot be read.", ELL_ERROR);
			return 0;
			#endif
		}
	case 14:
		{
  			#ifdef _IRR_COMPILE_WITH_LZMA_

			u32 uncompressedSize = e.header.DataDescriptor.UncompressedSize;
			c8* pBuf = new c8[ uncompressedSize ];
			if (!pBuf)
			{
				swprintf_irr ( buf, 64, L"Not enough memory for decompressing %s", core::stringw(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)
				{
					swprintf_irr ( buf, 64, L"Not enough memory for decompressing %s", core::stringw(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);
			}

			ELzmaStatus status;
			SizeT tmpDstSize = uncompressedSize;
			SizeT tmpSrcSize = decryptedSize;

			unsigned int propSize = (pcData[3]<<8)+pcData[2];
			int err = LzmaDecode((Byte*)pBuf, &tmpDstSize,
					pcData+4+propSize, &tmpSrcSize,
					pcData+4, propSize,
					e.header.GeneralBitFlag&0x1?LZMA_FINISH_END:LZMA_FINISH_ANY, &status,
					&lzmaAlloc);
			uncompressedSize = tmpDstSize; // may be different to expected value

			if (decrypted)
				decrypted->drop();
			else
				delete[] pcData;

			if (err != SZ_OK)
			{
				os::Printer::log( "Error decompressing", Files[index].FullName, ELL_ERROR);
				delete [] pBuf;
				return 0;
			}
			else
				return FileSystem->createMemoryReadFile(pBuf, uncompressedSize, Files[index].FullName, true);

			#else
			os::Printer::log("lzma decompression not supported. File cannot be read.", ELL_ERROR);
			return 0;
			#endif
		}
	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:
		swprintf_irr ( buf, 64, L"file has unsupported compression method. %s", core::stringw(Files[index].FullName).c_str() );
		os::Printer::log( buf, ELL_ERROR);
		return 0;
	};

}

} // end namespace io
} // end namespace irr

#endif // __IRR_COMPILE_WITH_ZIP_ARCHIVE_LOADER_