#include "bzip2/bzlib.h"
#include "UnAlz.h"

#ifdef _WIN32
#	include "zlib/zlib.h"
#else
#	include <zlib.h>
#endif


#ifdef _WIN32
#	pragma warning( disable : 4996 )		// crt secure warning
#endif

// utime 함수 처리
#if defined(_WIN32) || defined(__CYGWIN__)
#	include <time.h>
#	include <sys/utime.h>
#endif
#ifdef __GNUC__
#	include <time.h>
#	include <utime.h>
#endif

// mkdir
#ifdef _WIN32				
#	include <direct.h>		
#else
#	include <sys/stat.h>
#endif

#ifdef _UNALZ_ICONV			// code page support
#	include <iconv.h>
#endif

#if defined(__linux__) || defined(__GLIBC__) || defined(__GNU__) || defined(__APPLE__)
#	include <errno.h>
#endif

#if defined(__NetBSD__)
#	include <sys/param.h>	// __NetBSD_Version__
#	include <errno.h>		// iconv.h 때문에 필요 
#endif

#ifdef _WIN32				// safe string 처리
#	include <strsafe.h>
#endif


// ENDIAN 처리
#ifdef _WIN32	// (L)
#	define swapint64(a)	(UINT64) ( (((a)&0x00000000000000FFL) << 56) | (((a)&0x000000000000FF00L) << 40) | (((a)&0x0000000000FF0000L) << 24) | (((a)&0x00000000FF000000L) << 8)  | (((a)&0x000000FF00000000L) >> 8)  | (((a)&0x0000FF0000000000L) >> 24) | (((a)&0x00FF000000000000L) >> 40) | (((a)&0xFF00000000000000L) >> 56) )
#else			// (LL)
#	define swapint64(a)	(UINT64) ( (((a)&0x00000000000000FFLL) << 56) | (((a)&0x000000000000FF00LL) << 40) | (((a)&0x0000000000FF0000LL) << 24) | (((a)&0x00000000FF000000LL) << 8)  | (((a)&0x000000FF00000000LL) >> 8)  | (((a)&0x0000FF0000000000LL) >> 24) | (((a)&0x00FF000000000000LL) >> 40) | (((a)&0xFF00000000000000LL) >> 56) )
#endif
#define swapint32(a)    ((((a)&0xff)<<24)+(((a>>8)&0xff)<<16)+(((a>>16)&0xff)<<8)+(((a>>24)&0xff)))
#define swapint16(a)    (((a)&0xff)<<8)+(((a>>8)&0xff))

typedef UINT16	(*_unalz_le16toh)(UINT16 a);
typedef UINT32	(*_unalz_le32toh)(UINT32 a);
typedef UINT64	(*_unalz_le64toh)(UINT64 a);

static _unalz_le16toh unalz_le16toh=NULL;
static _unalz_le32toh unalz_le32toh=NULL;
static _unalz_le64toh unalz_le64toh=NULL;

static UINT16	le16tole(UINT16 a){return a;}
static UINT32	le32tole(UINT32 a){return a;}
static UINT64	le64tole(UINT64 a){return a;}
static UINT16	le16tobe(UINT16 a){return swapint16(a);}
static UINT32	le32tobe(UINT32 a){return swapint32(a);}
static UINT64	le64tobe(UINT64 a){return swapint64(a);}


#ifndef MAX_PATH
#	define MAX_PATH 260*6			// 그냥 .. 충분히..
#endif

#ifdef _WIN32
#	define PATHSEP "\\"
#	define PATHSEPC '\\'
#else
#	define PATHSEP "/"
#	define PATHSEPC '/'
#endif

static time_t dosTime2TimeT(UINT32 dostime)   // from INFO-ZIP src
{
	struct tm t;         
	t.tm_isdst = -1;     
	t.tm_sec  = (((int)dostime) <<  1) & 0x3e;
	t.tm_min  = (((int)dostime) >>  5) & 0x3f;
	t.tm_hour = (((int)dostime) >> 11) & 0x1f;
	t.tm_mday = (int)(dostime >> 16) & 0x1f;
	t.tm_mon  = ((int)(dostime >> 21) & 0x0f) - 1;
	t.tm_year = ((int)(dostime >> 25) & 0x7f) + 80;
	return mktime(&t);
}

static BOOL IsBigEndian(void) 
{
   union {
        short a;
        char  b[2];
   } endian;
   
   endian.a = 0x0102;
   if(endian.b[0] == 0x02) return FALSE;
   return TRUE;
}


#ifdef _WIN32
#	define safe_sprintf	StringCbPrintfA
#else
#	define safe_sprintf	snprintf
#endif


// 64bit file handling support
#if (_FILE_OFFSET_BITS==64)
#	define unalz_fseek	fseeko
#	define unalz_ftell	ftello
#else
#	define unalz_fseek	fseek
#	define unalz_ftell	ftell
#endif


// error string table <- CUnAlz::ERR 의 번역
static const char* errorstrtable[]=
{
	"no error",										// ERR_NOERR
	"general error",								// ERR_GENERAL
	"can't open archive file",						// ERR_CANT_OPEN_FILE
	"can't open dest file or path",					// ERR_CANT_OPEN_DEST_FILE
//	"can't create dest path",						// ERR_CANT_CREATE_DEST_PATH
	"corrupted file",								// ERR_CORRUPTED_FILE
	"not alz file",									// ERR_NOT_ALZ_FILE
	"can't read signature",							// ERR_CANT_READ_SIG
	"can't read file",								// ERR_CANT_READ_FILE
	"error at read header",							// ERR_AT_READ_HEADER
	"invalid filename length",						// ERR_INVALID_FILENAME_LENGTH
	"invalid extrafield length",					// ERR_INVALID_EXTRAFIELD_LENGTH,
	"can't read central directory structure head",	// ERR_CANT_READ_CENTRAL_DIRECTORY_STRUCTURE_HEAD, 
	"invalid filename size",						// ERR_INVALID_FILENAME_SIZE,
	"invalid extrafield size",						// ERR_INVALID_EXTRAFIELD_SIZE,
	"invalid filecomment size",						// ERR_INVALID_FILECOMMENT_SIZE,
	"cant' read header",							// ERR_CANT_READ_HEADER,
	"memory allocation failed",						// ERR_MEM_ALLOC_FAILED,
	"file read error",								// ERR_FILE_READ_ERROR,
	"inflate failed",								// ERR_INFLATE_FAILED,
	"bzip2 decompress failed",						// ERR_BZIP2_FAILED,
	"invalid file CRC",								// ERR_INVALID_FILE_CRC
	"unknown compression method",					// ERR_UNKNOWN_COMPRESSION_METHOD
									
	"iconv-can't open iconv",						// ERR_ICONV_CANT_OPEN,
	"iconv-invalid multisequence of characters",	// ERR_ICONV_INVALID_MULTISEQUENCE_OF_CHARACTERS,
	"iconv-incomplete multibyte sequence",			// ERR_ICONV_INCOMPLETE_MULTIBYTE_SEQUENCE,
	"iconv-not enough space of buffer to convert",	// ERR_ICONV_NOT_ENOUGH_SPACE_OF_BUFFER_TO_CONVERT,
	"iconv-etc",									// ERR_ICONV_ETC,
	
	"password was not set",							// ERR_PASSWD_NOT_SET,
	"invalid password",								// ERR_INVALID_PASSWD,
	"user aborted",					
};


////////////////////////////////////////////////////////////////////////////////////////////////////
///          ctor
/// @date    2004-03-06 오후 11:19:49
////////////////////////////////////////////////////////////////////////////////////////////////////
CUnAlz::CUnAlz()
{
	memset(m_files, 0, sizeof(m_files));
	m_nErr = ERR_NOERR;
	m_posCur = m_fileList.end();//(FileList::iterator)NULL;
	m_pFuncCallBack = NULL;
	m_pCallbackParam = NULL;
	m_bHalt = FALSE;
	m_nFileCount = 0;
	m_nCurFile = -1;
	m_nVirtualFilePos = 0;
	m_nCurFilePos = 0;
	m_bIsEOF = FALSE;
	m_bIsEncrypted = FALSE;
	m_bIsDataDescr = FALSE;
	m_bPipeMode = FALSE;

#ifdef _UNALZ_ICONV

#ifdef _UNALZ_UTF8
	safe_strcpy(m_szToCodepage, "UTF-8",UNALZ_LEN_CODEPAGE) ;		// 기본적으로 utf-8
#else 
	safe_strcpy(m_szToCodepage, "CP949",UNALZ_LEN_CODEPAGE) ;		// 기본적으로 CP949
#endif // _UNALZ_UTF8

	safe_strcpy(m_szFromCodepage, "CP949",UNALZ_LEN_CODEPAGE);		// alz 는 949 만 지원
#endif // _UNALZ_ICONV

	// check endian
	if(unalz_le16toh==NULL)
	{
		if(IsBigEndian())
		{
			unalz_le16toh = le16tobe;
			unalz_le32toh = le32tobe;
			unalz_le64toh = le64tobe;
		}
		else
		{
			unalz_le16toh = le16tole;
			unalz_le32toh = le32tole;
			unalz_le64toh = le64tole;
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          dtor
/// @date    2004-03-06 오후 11:19:52
////////////////////////////////////////////////////////////////////////////////////////////////////
CUnAlz::~CUnAlz()
{
	Close();
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          progress callback func setting
/// @date    2004-03-01 오전 6:02:05
////////////////////////////////////////////////////////////////////////////////////////////////////
void CUnAlz::SetCallback(_UnAlzCallback* pFunc, void* param)
{
	m_pFuncCallBack = pFunc;
	m_pCallbackParam = param;
}

#ifdef _WIN32
#if !defined(__GNUWIN32__) && !defined(__GNUC__)
////////////////////////////////////////////////////////////////////////////////////////////////////
///          파일 열기
/// @param   szPathName  
/// @return  
/// @date    2004-03-06 오후 11:03:59
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::Open(LPCWSTR szPathName)
{
	char szPathNameA[MAX_PATH];
	::WideCharToMultiByte(CP_ACP, 0, szPathName, -1, szPathNameA, MAX_PATH, NULL, NULL);
	return Open(szPathNameA);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
///          대상 파일 세팅하기.
/// @param   szFileName  
/// @return  
/// @date    2004-03-06 오후 11:06:20
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::SetCurrentFile(LPCWSTR szFileName)
{
	char szFileNameA[MAX_PATH];
	::WideCharToMultiByte(CP_ACP, 0, szFileName, -1, szFileNameA, MAX_PATH, NULL, NULL);
	return SetCurrentFile(szFileNameA);
}
BOOL CUnAlz::IsFolder(LPCWSTR szPathName)
{
	UINT32 dwRet;
	dwRet = GetFileAttributesW(szPathName);
	if(dwRet==0xffffffff) return FALSE;
	if(dwRet & FILE_ATTRIBUTE_DIRECTORY) return TRUE;
	return FALSE;
}
#endif // __GNUWIN32__
#endif // _WIN32

BOOL CUnAlz::Open(const char* szPathName)
{
	if(FOpen(szPathName)==FALSE) 
	{
		m_nErr = ERR_CANT_OPEN_FILE;
		return FALSE;
	}

	BOOL	bValidAlzHeader = FALSE;

	// file 분석시작..
	for(;;)
	{
		SIGNATURE	sig;
		BOOL		ret;

		if(FEof()) break;
		//int pos = unalz_ftell(m_fp);
		sig = ReadSignature();
		if(sig==SIG_EOF)
		{
			break;
		}
		if(sig==SIG_ERROR)
		{
			if(bValidAlzHeader)
				m_nErr = ERR_CORRUPTED_FILE;	// 손상된 파일
			else
				m_nErr = ERR_NOT_ALZ_FILE;	// alz 파일이 아니다.
			return FALSE;						// 깨진 파일..
		}

		if(sig==SIG_ALZ_FILE_HEADER) 
		{
			ret = ReadAlzFileHeader();
			bValidAlzHeader = TRUE;					// alz 파일은 맞다.
		}
		else if(sig==SIG_LOCAL_FILE_HEADER) ret = ReadLocalFileheader();
		else if(sig==SIG_CENTRAL_DIRECTORY_STRUCTURE) ret = ReadCentralDirectoryStructure();
		else if(sig==SIG_ENDOF_CENTRAL_DIRECTORY_RECORD) ret = ReadEndofCentralDirectoryRecord();
		else
		{
			// 미구현된 signature ? 깨진 파일 ?
			ASSERT(0);
			m_nErr = ERR_CORRUPTED_FILE;
			return FALSE;
		}
		
		if(ret==FALSE) 
		{
			return FALSE;
		}

		if(FEof()) break;
	}

	return TRUE;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          파일 닫기..
/// @return  
/// @date    2004-03-06 오후 11:04:21
////////////////////////////////////////////////////////////////////////////////////////////////////
void CUnAlz::Close()
{
	FClose();

	// 목록 날리기..
	FileList::iterator	i;

	for(i=m_fileList.begin(); i<m_fileList.end(); i++)
	{
		i->Clear();
	}

	m_posCur = m_fileList.end();//(FileList::iterator)NULL;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          FILE 내의 SIGNATURE 읽기
/// @return  
/// @date    2004-03-06 오후 11:04:47
////////////////////////////////////////////////////////////////////////////////////////////////////
CUnAlz::SIGNATURE CUnAlz::ReadSignature()
{
	UINT32	dwSig;
	if(FRead(&dwSig, sizeof(dwSig))==FALSE)
	{
		if(FEof())
			return SIG_EOF;
		m_nErr = ERR_CANT_READ_SIG;
		return SIG_ERROR;
	}

	return (SIGNATURE)unalz_le32toh(dwSig);		// little to host;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          ALZ HEADER SIGNATURE 읽기
/// @return  
/// @date    2004-03-06 오후 11:05:11
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::ReadAlzFileHeader()
{
	SAlzHeader	header;
	if(FRead(&header, sizeof(header))==FALSE)
	{
		ASSERT(0);
		m_nErr = ERR_CANT_READ_FILE;
		return FALSE;
	}
	return TRUE;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          각각의 파일 헤더 읽기
/// @return  
/// @date    2004-03-06 오후 11:05:18
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::ReadLocalFileheader()
{
	SAlzLocalFileHeader zipHeader;
	int ret;

	ret = FRead(&(zipHeader.head), sizeof(zipHeader.head));
	if(ret==FALSE) 
	{
		m_nErr = ERR_AT_READ_HEADER;
		return FALSE;
	}

	// ALZ 확장..
	if( (zipHeader.head.fileDescriptor & (SHORT)1) != 0){
		m_bIsEncrypted = TRUE;									// 하나라도 암호 걸렸으면 세팅한다.
	}
	if( (zipHeader.head.fileDescriptor & (SHORT)8) != 0){
		m_bIsDataDescr = TRUE;
	}
	
	int byteLen = zipHeader.head.fileDescriptor/0x10;

	if(byteLen)
	{
		FRead(&(zipHeader.compressionMethod),	sizeof(zipHeader.compressionMethod));
		FRead(&(zipHeader.unknown),				sizeof(zipHeader.unknown));
		FRead(&(zipHeader.fileCRC),				sizeof(zipHeader.fileCRC));

		FRead(&(zipHeader.compressedSize), byteLen);
		FRead(&(zipHeader.uncompressedSize),  byteLen);			// 압축 사이즈가 없다.
	}

	// little to system 
    zipHeader.fileCRC				=   unalz_le32toh(zipHeader.fileCRC);
    zipHeader.head.fileNameLength   =   unalz_le16toh(zipHeader.head.fileNameLength);
    zipHeader.compressedSize        =   unalz_le64toh(zipHeader.compressedSize);
    zipHeader.uncompressedSize      =   unalz_le64toh(zipHeader.uncompressedSize); 

	// FILE NAME
	zipHeader.fileName = (char*)malloc(zipHeader.head.fileNameLength+sizeof(char));
	if(zipHeader.fileName==NULL)
	{
		m_nErr = ERR_INVALID_FILENAME_LENGTH;
		return FALSE;
	}
	FRead(zipHeader.fileName, zipHeader.head.fileNameLength);
	if(zipHeader.head.fileNameLength > MAX_PATH - 5)
		zipHeader.head.fileNameLength = MAX_PATH - 5;
	zipHeader.fileName[zipHeader.head.fileNameLength] = (CHAR)NULL;


#ifdef _UNALZ_ICONV		// codepage convert

	if(strlen(m_szToCodepage))
	{

	#define ICONV_BUF_SIZE	(260*6)			// utf8 은 최대 6byte 
	size_t ileft, oleft;
	iconv_t cd;
	size_t iconv_result;
	size_t size;
	char inbuf[ICONV_BUF_SIZE];
	char outbuf[ICONV_BUF_SIZE];
#if defined(__FreeBSD__) || defined(__CYGWIN__) ||  defined(__NetBSD__)
	const char *inptr = inbuf;
#else
	char *inptr = inbuf;
#endif
	char *outptr = outbuf;
	
	size = strlen(zipHeader.fileName)+1;
	strncpy(inbuf, zipHeader.fileName, size);
	ileft = size;
	oleft = sizeof(outbuf);
	
	cd = iconv_open(m_szToCodepage, m_szFromCodepage);		// 보통 "CP949" 에서 "UTF-8" 로 
	iconv(cd, NULL, NULL, NULL, NULL);
	if( cd == (iconv_t)(-1)) 
	{
		m_nErr = ERR_ICONV_CANT_OPEN;		// printf("Converting Error : Cannot open iconv");
		return FALSE;
	}
	else
	{
		iconv_result = iconv(cd, &inptr, &ileft, &outptr, &oleft);
		
		if(iconv_result== (size_t)(-1))		// iconv 실패..
		{
			if (errno == EILSEQ) 
				m_nErr = ERR_ICONV_INVALID_MULTISEQUENCE_OF_CHARACTERS; // printf("Invalid Multibyte Sequence of Characters");
			else if (errno == EINVAL) 
				m_nErr = ERR_ICONV_INCOMPLETE_MULTIBYTE_SEQUENCE; //printf("Incomplete  multibyte sequence");
			else if (errno != E2BIG) 
				m_nErr = ERR_ICONV_NOT_ENOUGH_SPACE_OF_BUFFER_TO_CONVERT;	// printf("Not enough space of buffer to convert");
			else 
				m_nErr = ERR_ICONV_ETC;
			iconv_close(cd);
			return FALSE;
		} 
		else 
		{
			outbuf[ICONV_BUF_SIZE-oleft] = 0;
			if(zipHeader.fileName) free(zipHeader.fileName);
			zipHeader.fileName = strdup(outbuf);
			if (zipHeader.fileName == NULL)
			{
				m_nErr = ERR_ICONV_ETC;
				iconv_close(cd);
				return FALSE;
			}
			// printf("\n  Converted File Name : %s", outbuf);
		}
		
		iconv_close(cd);
    }

	}
#endif

	/*
	// EXTRA FIELD LENGTH
	if(zipHeader.head.extraFieldLength)
	{
		zipHeader.extraField = (BYTE*)malloc(zipHeader.head.extraFieldLength);
		if(zipHeader.extraField==NULL)
		{
			m_nErr = ERR_INVALID_EXTRAFIELD_LENGTH;
			return FALSE;
		}
		FRead(zipHeader.extraField, 1, zipHeader.head.extraFieldLength);
	}
	*/

	if(IsEncryptedFile(zipHeader.head.fileDescriptor)) 
		FRead(zipHeader.encChk, ALZ_ENCR_HEADER_LEN);  // xf86

	// SKIP FILE DATA
	zipHeader.dwFileDataPos = FTell();						// data 의 위치 저장하기..
	FSeek(FTell()+zipHeader.compressedSize);

	// DATA DESCRIPTOR
	/*
	if(zipHeader.head.generalPurposeBitFlag.bit1)
	{
		FRead(zipHeader.extraField, 1, sizeof(zipHeader.extraField),);
	}
	*/

	/*
#ifdef _DEBUG
	printf("NAME:%s COMPRESSED SIZE:%d UNCOMPRESSED SIZE:%d COMP METHOD:%d\n", 
		zipHeader.fileName, 
		zipHeader.compressedSize, 
		zipHeader.uncompressedSize,
		zipHeader.compressionMethod
		);
#endif
	*/

	// 파일을 목록에 추가한다..
	m_fileList.push_back(zipHeader);

	return TRUE;
}


BOOL CUnAlz::ReadCentralDirectoryStructure()
{
	SCentralDirectoryStructure header;

	if(FRead(&header, sizeof(header.head))==FALSE)
	{
		m_nErr = ERR_CANT_READ_CENTRAL_DIRECTORY_STRUCTURE_HEAD;
		return FALSE;
	}

	/*
	// read file name
	if(header.head.fileNameLength)
	{
		header.fileName = (char*)malloc(header.head.fileNameLength+1);
		if(header.fileName==NULL)
		{
			m_nErr = ERR_INVALID_FILENAME_SIZE;
			return FALSE;
		}
		FRead(header.fileName, 1, header.head.fileNameLength, m_fp);
		header.fileName[header.head.fileNameLength] = NULL;
	}

	// extra field;
	if(header.head.extraFieldLength)
	{
		header.extraField = (BYTE*)malloc(header.head.extraFieldLength);
		if(header.extraField==NULL)
		{
			m_nErr = ERR_INVALID_EXTRAFIELD_SIZE;
			return FALSE;
		}
		FRead(header.extraField, 1, header.head.extraFieldLength, m_fp);
	}

	// file comment;
	if(header.head.fileCommentLength)
	{
		header.fileComment = (char*)malloc(header.head.fileCommentLength+1);
		if(header.fileComment==NULL)
		{
			m_nErr = ERR_INVALID_FILECOMMENT_SIZE;
			return FALSE;
		}
		FRead(header.fileComment, 1, header.head.fileCommentLength, m_fp);
		header.fileComment[header.head.fileCommentLength] = NULL;
	}
	*/

	return TRUE;
}


BOOL CUnAlz::ReadEndofCentralDirectoryRecord()
{
	/*
	SEndOfCentralDirectoryRecord	header;

	if(FRead(&header, sizeof(header.head), 1, m_fp)!=1)
	{
		m_nErr = ERR_CANT_READ_HEADER;
		return FALSE;
	}

	if(header.head.zipFileCommentLength)
	{
		header.fileComment = (char*)malloc(header.head.zipFileCommentLength+1);
		if(header.fileComment==NULL)
		{
			m_nErr = ERR_INVALID_FILECOMMENT_SIZE;
			return FALSE;
		}
		FRead(header.fileComment, 1, header.head.zipFileCommentLength, m_fp);
		header.fileComment[header.head.zipFileCommentLength] = NULL;
	}
	*/

	return TRUE;
}


BOOL CUnAlz::SetCurrentFile(const char* szFileName)
{
	FileList::iterator	i;

	// 순차적으로 찾는다.
	for(i=m_fileList.begin(); i<m_fileList.end(); i++)
	{
#ifdef _WIN32
		if(stricmp(i->fileName, szFileName)==0)
#else
		if(strcmp(i->fileName, szFileName)==0)
#endif
		{
			m_posCur = i;
			return TRUE;
		}
	}

	m_posCur = m_fileList.end();//(FileList::iterator)NULL;

	return FALSE;
}

void CUnAlz::SetCurrentFile(FileList::iterator newPos)
{
	m_posCur = newPos;
}

#ifndef MAX_WBITS
#  define MAX_WBITS   15 /* 32K LZ77 window */
#endif


////////////////////////////////////////////////////////////////////////////////////////////////////
///          버퍼에 압축 풀기. 버퍼는 당근 충분한 크기가 준비되어 있어야 한다.
/// @param   pDestBuf  
/// @return  
/// @date    2004-03-07 오전 12:26:13
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::ExtractCurrentFileToBuf(BYTE* pDestBuf, int nBufSize)
{
	SExtractDest	dest;
	dest.nType = ET_MEM;
	dest.buf = pDestBuf;
	dest.bufpos = 0;
	dest.bufsize = nBufSize;
	return ExtractTo(&dest);
}


////////////////////////////////////////////////////////////////////////////////////////////////////
///          현재 파일 (SetCurrentFile로 지)을 대상 경로에 대상 파일로 푼다.
/// @param   szDestPathName  - 대상 경로
/// @param   szDestFileName  - 대상 파일명, NULL 이면 원래 파일명 사용
/// @return  
/// @date    2004-03-06 오후 11:06:59
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::ExtractCurrentFile(const char* szDestPathName, const char* szDestFileName)
{
	if(m_posCur==m_fileList.end()/*(FileList::iterator)NULL*/) {ASSERT(0); return FALSE;}
	BOOL	ret=FALSE;

	SExtractDest	dest;
	char			szDestPathFileName[MAX_PATH];
	
	if(chkValidPassword() == FALSE) 
	{
		return FALSE;
	}

	if( szDestPathName==NULL||
		strlen(szDestPathName) + (szDestFileName?strlen(szDestFileName):strlen(m_posCur->fileName))+1 > MAX_PATH
	   )	// check buffer overflow
	{
		ASSERT(0);
		m_nErr = ERR_GENERAL;
		return FALSE;
	}
	
	// 경로명
	safe_strcpy(szDestPathFileName, szDestPathName, MAX_PATH);
	if(szDestPathFileName[strlen(szDestPathFileName)]!=PATHSEPC)
		safe_strcat(szDestPathFileName, PATHSEP, MAX_PATH);

	// 파일명
	if(szDestFileName) safe_strcat(szDestPathFileName, szDestFileName, MAX_PATH);
	else safe_strcat(szDestPathFileName, m_posCur->fileName, MAX_PATH);

	// ../../ 형식의 보안 버그 확인
	if( strstr(szDestPathFileName, "../")||
		strstr(szDestPathFileName, "..\\"))
	{
		ASSERT(0);
		m_nErr = ERR_GENERAL;
		return FALSE;
	}

#ifndef _WIN32 
	{
		char* p = szDestPathFileName;			// 경로 delimiter 바꾸기 
		while(*p)
		{
			if(*p=='\\') *p='/';
			p++;
		}
	}
#endif

	// 압축풀 대상 ( 파일 )
	dest.nType = ET_FILE;

	if(m_bPipeMode)
		dest.fp = stdout;				// pipe mode 일 경우 stdout 출력
	else
		dest.fp = fopen(szDestPathFileName, "wb");

	// 타입이 폴더일 경우..
	if(m_bPipeMode==FALSE && (m_posCur->head.fileAttribute) & ALZ_FILEATTR_DIRECTORY )
	{
//printf("digpath:%s\n", szDestPathFileName);
		// 경로파기
		DigPath(szDestPathFileName);
		return TRUE;
//		m_nErr = ERR_CANT_CREATE_DEST_PATH;
//		return FALSE;
	}

	// 파일 열기 실패시 - 경로를 파본다
	if(dest.fp==NULL) 
	{
		DigPath(szDestPathFileName);
		dest.fp = fopen(szDestPathFileName, "wb");
	}

	// 그래도 파일열기 실패시.
	if(dest.fp==NULL) 
	{
		// 대상 파일 열기 실패
		m_nErr = ERR_CANT_OPEN_DEST_FILE;
//printf("dest pathfilename:%s\n",szDestPathFileName);
		if(m_pFuncCallBack)
		{
//			CHAR buf[1024];
//			sprintf(buf, "파일 열기 실패 : %s", szDestPathFileName);
//			m_pFuncCallBack(buf, 0,0,m_pCallbackParam, NULL);
		}
		return FALSE;
	}
//#endif

	// CALLBACK 세팅
	if(m_pFuncCallBack) m_pFuncCallBack(m_posCur->fileName, 0,m_posCur->uncompressedSize,m_pCallbackParam, NULL);

	ret = ExtractTo(&dest);
	if(dest.fp!=NULL)
	{
		fclose(dest.fp);
		// file time setting - from unalz_wcx_01i.zip
		utimbuf tmp;
		tmp.actime = 0;													// 마지막 엑세스 타임
		tmp.modtime = dosTime2TimeT(m_posCur->head.fileTimeDate);		// 마지막 수정일자만 변경(만든 날자는 어떻게 바꾸지?)
		utime(m_posCur->fileName, &tmp);
	}
	return ret;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          대상에 압축 풀기..
/// @param   dest  
/// @return  
/// @date    2004-03-07 오전 12:44:36
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::ExtractTo(SExtractDest* dest)
{
	BOOL ret = FALSE;
	// 압축 방법에 따라서 압축 풀기
	if(m_posCur->compressionMethod==COMP_NOCOMP)
	{
		ret = ExtractRawfile(dest, *m_posCur);
	}
	else if(m_posCur->compressionMethod==COMP_BZIP2)
	{
		ret = ExtractBzip2(dest, *m_posCur);			// bzip2
	}
	else if(m_posCur->compressionMethod==COMP_DEFLATE)
	{
		ret = ExtractDeflate2(dest, *m_posCur);			// deflate
	}
	else	// COMP_UNKNOWN
	{
		// alzip 5.6 부터 추가된 포맷(5.5 에서는 풀지 못한다. 영문 5.51 은 푼다 ) 
		// 하지만 어떤 버전에서 이 포맷을 만들어 내는지 정확히 알 수 없다.
		// 공식으로 릴리즈된 알집은 이 포맷을 만들어내지 않는다. 비공식(베타?)으로 배포된 버전에서만 이 포맷을 만들어낸다.
		m_nErr = ERR_UNKNOWN_COMPRESSION_METHOD;
		ASSERT(0);										
		ret = FALSE;
	}
	return ret;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          DEFLATE 로 풀기 - 테스트용 함수. 모든 파일을 한꺼번에 읽어서 푼다. 실제 사용 안함.
/// @param   fp  - 대상 파일
/// @param   file  - 소스 파일 정보
/// @return  
/// @date    2004-03-06 오후 11:09:17
////////////////////////////////////////////////////////////////////////////////////////////////////
/*
BOOL CUnAlz::ExtractDeflate(FILE* fp, SAlzLocalFileHeader& file)
{
	z_stream	stream;
	BYTE*		pInBuf=NULL;
	BYTE*		pOutBuf=NULL;
	int			nInBufSize = file.compressedSize;
	int			nOutBufSize = file.uncompressedSize;
	int			err;
	int			flush=Z_SYNC_FLUSH;
	BOOL		ret = FALSE;

	memset(&stream, 0, sizeof(stream));

	pInBuf = (BYTE*)malloc(nInBufSize);
	if(pInBuf==NULL)
	{
		m_nErr = ERR_MEM_ALLOC_FAILED;
		goto END;
	}

	pOutBuf = (BYTE*)malloc(nOutBufSize);
	if(pOutBuf==NULL)
	{
		m_nErr = ERR_MEM_ALLOC_FAILED;
		goto END;
	}

	// 한번에 읽어서
	fseek(m_fp, file.dwFileDataPos, SEEK_SET);
	if(FRead(pInBuf, nInBufSize, 1, m_fp)!=1)
	{
		m_nErr = ERR_FILE_READ_ERROR;
		goto END;
	}

	// 초기화..
	inflateInit2(&stream, -MAX_WBITS);

	stream.next_out = pOutBuf;
	stream.avail_out = nOutBufSize;
	stream.next_in = pInBuf;
	stream.avail_in = nInBufSize;

	err = inflate(&stream, flush);

	if(err!=Z_OK && err!=Z_STREAM_END )
	{
		m_nErr = ERR_INFLATE_FAILED;
		goto END;
	}

	fwrite(pOutBuf, 1, nOutBufSize, fp);

	ret = TRUE;

END :
	inflateEnd(&stream);

	if(pInBuf) free(pInBuf);
	if(pOutBuf) free(pOutBuf);
	return ret;
}
*/

////////////////////////////////////////////////////////////////////////////////////////////////////
///          대상 폴더에 현재 압축파일을 전부 풀기
/// @param   szDestPathName  - 대상 경로
/// @return  
/// @date    2004-03-06 오후 11:09:49
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::ExtractAll(const char* szDestPathName)
{
	FileList::iterator	i;

	for(i=m_fileList.begin(); i<m_fileList.end(); i++)
	{
		m_posCur = i;
		if(ExtractCurrentFile(szDestPathName)==FALSE) return FALSE;
		if(m_bHalt) 
			break;							// 멈추기..
	}

	return TRUE;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          대상 경로 파기 - 압축 파일 내에 폴더 정보가 있을 경우, 다중 폴더를 판다(dig)
/// @param   szPathName  
/// @return  
/// @date    2004-03-06 오후 11:10:12
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::DigPath(const char* szPathName)
{
	char*	dup = strdup(szPathName);
	char	seps[]   = "/\\";
	char*	token;
	char	path[MAX_PATH] = {0};
	char*	last;

	// 경로만 뽑기.
	last = dup + strlen(dup);
	while(last!=dup)
	{
		if(*last=='/' || *last=='\\') 
		{
			*last = (char)NULL;
			break;
		}
		last --;
	}

	
	token = strtok( dup, seps );
	while( token != NULL )
	{
		if(strlen(path)==0)
		{
			if(szPathName[0]=='/')			// is absolute path ?
				safe_strcpy(path,"/", MAX_PATH);
			else if(szPathName[0]=='\\' && szPathName[1]=='\\')	// network drive ?
				safe_strcpy(path,"\\\\", MAX_PATH);
			safe_strcat(path, token, MAX_PATH);
		}
		else
		{
			safe_strcat(path, PATHSEP,MAX_PATH);
			safe_strcat(path, token,MAX_PATH);
		}

		if(IsFolder(path)==FALSE)
#ifdef _WIN32
			_mkdir(path);
#else
			mkdir(path,  S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
#endif
//printf("path:%s\n", path);
		token = strtok( NULL, seps );
	}

	free(dup);
	if(IsFolder(szPathName)) return TRUE;
	return FALSE;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          제대로된 폴더 인가?
/// @param   szPathName  
/// @return  
/// @date    2004-03-06 오후 11:03:26
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::IsFolder(const CHAR* szPathName)
{
#ifdef _WIN32
	UINT32 dwRet;
	dwRet = GetFileAttributesA(szPathName);
	if(dwRet==0xffffffff) return FALSE;
	if(dwRet & FILE_ATTRIBUTE_DIRECTORY) return TRUE;
	return FALSE;
#else

	struct stat buf;
	int result;

	result = stat(szPathName, &buf);
	if(result!=0) return FALSE;
//printf("isfolder:%s, %d,%d,%d\n", szPathName, buf.st_mode, S_IFDIR, buf.st_mode & S_IFDIR);
	if(buf.st_mode & S_IFDIR) return TRUE;
	return FALSE;
#endif
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          압축을 풀 대상에 압축을 푼다.
/// @param   dest  - 대상 OBJECT
/// @param   buf  - 풀린 데이타
/// @param   nSize  - 데이타의 크기
/// @return  쓴 바이트수. 에러시 -1 리턴
/// @date    2004-03-07 오전 12:37:41
////////////////////////////////////////////////////////////////////////////////////////////////////
int	CUnAlz::WriteToDest(SExtractDest* dest, BYTE* buf, int nSize)
{
	if(dest->nType==ET_FILE)
	{
		return fwrite(buf,  1, nSize, dest->fp);
	}
	else if(dest->nType==ET_MEM)
	{
		if(dest->buf==NULL) return nSize;			// 대상이 NULL 이다... 압축푸는 시늉만 한다..
		if(dest->bufpos+nSize >dest->bufsize)		// 에러.. 버퍼가 넘쳤다.
		{
			ASSERT(0);
			return -1;
		}
		// memcpy
		memcpy(dest->buf + dest->bufpos, buf, nSize);
		dest->bufpos += nSize;
		return nSize;
	}
	else
	{
		ASSERT(0);
	}
	return -1;
}

/* 실패한 방법.. 고생한게 아까워서 못지움.
#define ALZDLZ_HEADER_SIZE	4		// alz 파일의 bzip2 헤더 크기
#define BZIP2_HEADER_SIZE	10		// bzip 파일의 헤더 크기
#define BZIP2_CRC_SIZE		4		// bzip2 의 crc
#define BZIP2_TAIL_SIZE		10		// 대충 4+5 정도.?
BYTE bzip2Header[BZIP2_HEADER_SIZE] = {0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59};

BOOL CUnAlz::ExtractBzip2_bak(FILE* fp, SAlzLocalFileHeader& file)
{
	bz_stream	stream;
	BYTE*		pInBuf=NULL;
	BYTE*		pOutBuf=NULL;
	int			nInBufSize = file.compressedSize;
	int			nOutBufSize = file.uncompressedSize;
	//int			err;
	int			flush=Z_SYNC_FLUSH;
	BOOL		ret = FALSE;
	UINT32 crc = 0xffffffff;

	//BYTE		temp[100];
	
	memset(&stream, 0, sizeof(stream));

	pInBuf = (BYTE*)malloc(nInBufSize + BZIP2_HEADER_SIZE + BZIP2_CRC_SIZE - ALZDLZ_HEADER_SIZE + BZIP2_TAIL_SIZE);
	if(pInBuf==NULL)
	{
		m_nErr = ERR_MEM_ALLOC_FAILED;
		goto END;
	}

	pOutBuf = (BYTE*)malloc(nOutBufSize);
	if(pOutBuf==NULL)
	{
		m_nErr = ERR_MEM_ALLOC_FAILED;
		goto END;
	}

	// ALZ 의 BZIP 헤더 ("DLZ.") 스킵하기.
	fseek(m_fp, ALZDLZ_HEADER_SIZE, SEEK_CUR);
	// BZIP2 헤더 삽입
	memcpy(pInBuf, bzip2Header, BZIP2_HEADER_SIZE);
	// BZIP2 CRC
	memcpy(pInBuf+BZIP2_HEADER_SIZE, &(crc), BZIP2_CRC_SIZE);

	// 진짜 압축된 데이타를  한번에 읽어서
	fseek(m_fp, file.dwFileDataPos+ALZDLZ_HEADER_SIZE, SEEK_SET);
	if(FRead(pInBuf+BZIP2_HEADER_SIZE+BZIP2_CRC_SIZE, nInBufSize-ALZDLZ_HEADER_SIZE, 1, m_fp)!=1)
	{
		m_nErr = ERR_FILE_READ_ERROR;
		goto END;
	}


	// 초기화..
	stream.bzalloc = NULL;
	stream.bzfree = NULL;
	stream.opaque = NULL;
	ret = BZ2_bzDecompressInit ( &stream, 3,0 );
	if (ret != BZ_OK) goto END;


	//memcpy(temp, pInBuf, 100);

	stream.next_in = (char*)pInBuf;
	stream.next_out = (char*)pOutBuf;
	stream.avail_in = nInBufSize+BZIP2_HEADER_SIZE+BZIP2_CRC_SIZE+BZIP2_TAIL_SIZE;
	stream.avail_out = nOutBufSize;


	ret = BZ2_bzDecompress ( &stream );

	// BZ_DATA_ERROR 가 리턴될 수 있다..
	//if (ret == BZ_OK) goto END;
	//if (ret != BZ_STREAM_END) goto END;

	BZ2_bzDecompressEnd(&stream);

	fwrite(pOutBuf, 1, nOutBufSize, fp);

	ret = TRUE;

END :

	if(pInBuf) free(pInBuf);
	if(pOutBuf) free(pOutBuf);

	if(ret==FALSE) 	BZ2_bzDecompressEnd(&stream);

	return ret;
}
*/

////////////////////////////////////////////////////////////////////////////////////////////////////
///          RAW 로 압축된 파일 풀기
/// @param   fp  - 대상 파일
/// @param   file  - 소스 파일
/// @return  
/// @date    2004-03-06 오후 11:10:53
////////////////////////////////////////////////////////////////////////////////////////////////////
#define BUF_LEN		(4096*2)
BOOL CUnAlz::ExtractRawfile(SExtractDest* dest, SAlzLocalFileHeader& file)
{
	BOOL		ret = FALSE;
	BYTE		buf[BUF_LEN];
	INT64		read;
	INT64		sizeToRead;
	INT64		bufLen = BUF_LEN;
	INT64		nWritten = 0;
	BOOL		bHalt = FALSE;
	BOOL		bIsEncrypted = 	IsEncryptedFile();		// 암호걸린 파일인가?
	UINT32		dwCRC32= 0;



	// 위치 잡고.
	FSeek(file.dwFileDataPos);

	sizeToRead = file.compressedSize;			// 읽을 크기.

	m_nErr = ERR_NOERR;
	while(sizeToRead)
	{
		read = min(sizeToRead, bufLen);
		if(FRead(buf, (int)read)==FALSE)
		{
			break;
		}

		if(bIsEncrypted)
			DecryptingData((int)read, buf); // xf86

		dwCRC32 = crc32(dwCRC32, buf, (UINT)(read));

		WriteToDest(dest, buf, (int)read);
		//fwrite(buf, read, 1, fp);
		sizeToRead -= read;

		nWritten+=read;

		// progress callback
		if(m_pFuncCallBack)
		{
			m_pFuncCallBack(NULL, nWritten, file.uncompressedSize, m_pCallbackParam, &bHalt);
			if(bHalt) 
			{
				break;
			}
		}
	}

	m_bHalt = bHalt;

	if(m_nErr==ERR_NOERR)			// 성공적으로 압축을 풀었다.. CRC 검사하기..
	{
		if(file.fileCRC==dwCRC32)
		{
			ret = TRUE;
		}
		else
		{
			m_nErr = ERR_INVALID_FILE_CRC;
		}
	}


	return ret;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          BZIP2 압축 풀기..
/// @param   fp_w  - 대상 파일
/// @param   file  - 소스 파일 정보
/// @return  
/// @date    2004-03-01 오전 5:47:36
////////////////////////////////////////////////////////////////////////////////////////////////////
#define BZIP2_EXTRACT_BUF_SIZE	0x2000
BOOL CUnAlz::ExtractBzip2(SExtractDest* dest, SAlzLocalFileHeader& file)
{
	BZFILE		*bzfp = NULL;
	int			smallMode = 0;
	int			verbosity = 1;
	int			bzerr;
	INT64		len;
	BYTE		buff[BZIP2_EXTRACT_BUF_SIZE];
	INT64		nWritten = 0;
	BOOL		bHalt = FALSE;
	UINT32		dwCRC32= 0;
	BOOL		ret = FALSE;

	FSeek(file.dwFileDataPos);

	bzfp = BZ2_bzReadOpen(&bzerr,this,verbosity,smallMode,0,0);

	if(bzfp==NULL){ASSERT(0); return FALSE;}

	m_nErr = ERR_NOERR;
	while((len=BZ2_bzread(bzfp,buff,BZIP2_EXTRACT_BUF_SIZE))>0)
	{
		WriteToDest(dest, (BYTE*)buff, (int)len);
		//fwrite(buff,1,len,fp_w);

		dwCRC32 = crc32(dwCRC32,buff, (UINT)(len));


		nWritten+=len;

		// progress callback
		if(m_pFuncCallBack)
		{
			m_pFuncCallBack(NULL, nWritten, file.uncompressedSize, m_pCallbackParam, &bHalt);
			if(bHalt) 
			{
				break;
			}
		}
	}

	if(len<0)			// 에러 상황..
	{
		m_nErr = ERR_INFLATE_FAILED;
	}

	BZ2_bzReadClose( &bzerr, bzfp);

	m_bHalt = bHalt;

	if(m_nErr==ERR_NOERR)			// 성공적으로 압축을 풀었다.. CRC 검사하기..
	{
		if(file.fileCRC==dwCRC32)
		{
			ret = TRUE;
		}
		else
		{
			m_nErr = ERR_INVALID_FILE_CRC;
		}
	}


	/*		
	// FILE* 를 사용할경우 사용하던 코드. - 멀티 볼륨 지원 안함..
	BZFILE	*bzfp = NULL;
	int		smallMode = 0;
	int		verbosity = 1;
	int		bzerr;
	int		len;
	char	buff[BZIP2_EXTRACT_BUF_SIZE];
	INT64	nWritten = 0;
	BOOL	bHalt = FALSE;

	FSeek(file.dwFileDataPos, SEEK_SET);

	bzfp = BZ2_bzReadOpen(&bzerr,m_fp,verbosity,smallMode,0,0);

	while((len=BZ2_bzread(bzfp,buff,BZIP2_EXTRACT_BUF_SIZE))>0)
	{
		WriteToDest(dest, (BYTE*)buff, len);
		//fwrite(buff,1,len,fp_w);

		nWritten+=len;

		// progress callback
		if(m_pFuncCallBack)
		{
			m_pFuncCallBack(NULL, nWritten, file.uncompressedSize, m_pCallbackParam, &bHalt);
			if(bHalt) 
			{
				break;
			}
		}
	}

	BZ2_bzReadClose( &bzerr, bzfp);

	m_bHalt = bHalt;
	*/

	return ret;
}


#ifndef UNZ_BUFSIZE
#define UNZ_BUFSIZE		0x1000 // (16384)
#endif

#define IN_BUF_SIZE		UNZ_BUFSIZE
#define OUT_BUF_SIZE	0x1000 //IN_BUF_SIZE

////////////////////////////////////////////////////////////////////////////////////////////////////
///          deflate 로 압축 풀기. ExtractDeflate() 와 달리 조금씩 읽어서 푼다.
/// @param   fp  
/// @param   file  
/// @return  
/// @date    2004-03-06 오후 11:11:36
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::ExtractDeflate2(SExtractDest* dest, SAlzLocalFileHeader& file)
{
	z_stream	stream;
	BYTE		pInBuf[IN_BUF_SIZE];
	BYTE		pOutBuf[OUT_BUF_SIZE];
	int			nInBufSize = IN_BUF_SIZE;
	int			nOutBufSize = OUT_BUF_SIZE;
	int			err;
	int			flush=Z_SYNC_FLUSH;
	BOOL		ret = FALSE;
	INT64		nRestReadCompressed;
	UINT32		dwCRC32= 0;
	INT64		rest_read_uncompressed;
	UINT		iRead = 0;
	INT64	nWritten = 0;
	BOOL	bHalt = FALSE;
	BOOL	bIsEncrypted = 	IsEncryptedFile();		// 암호걸린 파일인가?

	memset(&stream, 0, sizeof(stream));

	FSeek(file.dwFileDataPos);

	inflateInit2(&stream, -MAX_WBITS);
	nRestReadCompressed = file.compressedSize;
	rest_read_uncompressed = file.uncompressedSize;

	// 출력 부분.
	stream.next_out = pOutBuf;
	stream.avail_out = OUT_BUF_SIZE;

	m_nErr = ERR_NOERR;
	while(stream.avail_out>0)
	{
		if(stream.avail_in==0 && nRestReadCompressed>0)
		{
			UINT uReadThis = UNZ_BUFSIZE;
			if (nRestReadCompressed<(int)uReadThis)
				uReadThis = (UINT)nRestReadCompressed;		// 읽을 크기.

			if (uReadThis == 0)
				break;					// 중지

			if(FRead(pInBuf, uReadThis)==FALSE)
			{
				m_nErr = ERR_CANT_READ_FILE;
				goto END;
			}
			
			if(bIsEncrypted)
				DecryptingData(uReadThis, pInBuf); // xf86

//			dwCRC32 = crc32(dwCRC32,pInBuf, (UINT)(uReadThis));
			
			nRestReadCompressed -= uReadThis;
			stream.next_in = pInBuf;
			stream.avail_in = uReadThis;
		}

        UINT uTotalOutBefore,uTotalOutAfter;
        const BYTE *bufBefore;
        UINT uOutThis;
        int flush=Z_SYNC_FLUSH;

        uTotalOutBefore = stream.total_out;
        bufBefore = stream.next_out;

        err=inflate(&stream,flush);

        uTotalOutAfter = stream.total_out;
        uOutThis = uTotalOutAfter-uTotalOutBefore;
        
        dwCRC32 = crc32(dwCRC32,bufBefore, (UINT)(uOutThis));

        rest_read_uncompressed -= uOutThis;

        iRead += (UINT)(uTotalOutAfter - uTotalOutBefore);
		
		WriteToDest(dest, pOutBuf, uOutThis);
		//fwrite(pOutBuf, uOutThis, 1, fp);		// file 에 쓰기.
		stream.next_out = pOutBuf;
		stream.avail_out = OUT_BUF_SIZE;

		nWritten+=uOutThis;

		// progress callback
		if(m_pFuncCallBack)
		{
			m_pFuncCallBack(NULL, nWritten, file.uncompressedSize, m_pCallbackParam, &bHalt);
			if(bHalt) 
			{
				m_nErr = ERR_USER_ABORTED;
				break;
			}
		}

        if (err==Z_STREAM_END)
			break;
			//if(iRead==0) break; // UNZ_EOF;
        if (err!=Z_OK) 
		{
			m_nErr = ERR_INFLATE_FAILED;
            goto END;
		}
	}
	m_bHalt = bHalt;


	if(m_nErr==ERR_NOERR)			// 성공적으로 압축을 풀었다.. CRC 검사하기..
	{
		if(file.fileCRC==dwCRC32)
		{
			ret = TRUE;
		}
		else
		{
			m_nErr = ERR_INVALID_FILE_CRC;
		}
	}


END :
	inflateEnd(&stream);

	return ret;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          파일 열기
/// @param   szPathName  
/// @return  
/// @date    2004-10-02 오후 11:47:14
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::FOpen(const char* szPathName)
{
	char* temp = strdup(szPathName);			// 파일명 복사..
	int	  i;
	int	  nLen = strlen(szPathName);
	UINT64 nFileSizeLow;
	UINT32 dwFileSizeHigh;
	m_nFileCount = 0;
	m_nCurFile = 0;
	m_nVirtualFilePos = 0;
	m_nCurFilePos = 0;
	m_bIsEOF = FALSE;
	for(i=0;i<MAX_FILES;i++)						// aa.alz 파일명을 가지고 aa.a00 aa.a01 aa.a02 .. 만들기
	{
		if(i>0) 
			safe_sprintf(temp+nLen-3, 4, "%c%02d", (i-1)/100+'a', (i-1)%100);

#ifdef _WIN32
		m_files[i].fp = CreateFileA(temp, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
		if(m_files[i].fp==INVALID_HANDLE_VALUE) break;
		nFileSizeLow = GetFileSize(m_files[i].fp, (DWORD*)&dwFileSizeHigh);
#else
		m_files[i].fp = fopen(temp, "rb");
		if(m_files[i].fp==NULL) break;
		dwFileSizeHigh=0;
		unalz_fseek(m_files[i].fp,0,SEEK_END);		
		nFileSizeLow=unalz_ftell(m_files[i].fp);
		unalz_fseek(m_files[i].fp,0,SEEK_SET);
#endif
		m_nFileCount++;
		m_files[i].nFileSize = ((INT64)nFileSizeLow) + (((INT64)dwFileSizeHigh)<<32);
		if(i==0) m_files[i].nMultivolHeaderSize = 0;
		else m_files[i].nMultivolHeaderSize = MULTIVOL_HEAD_SIZE;
		m_files[i].nMultivolTailSize = MULTIVOL_TAIL_SIZE;
	}
	free(temp);
	if(m_nFileCount==0) return FALSE;
	m_files[m_nFileCount-1].nMultivolTailSize = 0;			// 마지막 파일은 꼴랑지가 없다..
	return TRUE;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          파일 닫기
/// @return  
/// @date    2004-10-02 오후 11:48:53
////////////////////////////////////////////////////////////////////////////////////////////////////
void CUnAlz::FClose()
{
	int i;
#ifdef _WIN32
	for(i=0;i<m_nFileCount;i++) CloseHandle(m_files[i].fp);
#else
	for(i=0;i<m_nFileCount;i++) fclose(m_files[i].fp);
#endif
	memset(m_files, 0, sizeof(m_files));
	m_nFileCount = 0;
	m_nCurFile = -1;
	m_nVirtualFilePos = 0;
	m_nCurFilePos = 0;
	m_bIsEOF = FALSE;

}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          파일의 끝인가?
/// @return  
/// @date    2004-10-02 오후 11:48:21
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::FEof()
{
	return m_bIsEOF;
	/*
	if(m_fp==NULL){ASSERT(0); return TRUE;}
	if(feof(m_fp)) return TRUE;
	return FALSE;
	*/
}


////////////////////////////////////////////////////////////////////////////////////////////////////
///          현재 파일 위치
/// @return  
/// @date    2004-10-02 오후 11:50:50
////////////////////////////////////////////////////////////////////////////////////////////////////
INT64 CUnAlz::FTell()
{
	return m_nVirtualFilePos;	//	return ftell(m_fp);
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          파일 위치 세팅
/// @param   offset  
/// @param   origin  
/// @return  
/// @date    2004-10-02 오후 11:51:53
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::FSeek(INT64 offset)
{
	m_nVirtualFilePos = offset;
	int		i;
	INT64	remain=offset;
	LONG	remainHigh;

	m_bIsEOF = FALSE;

	for(i=0;i<m_nFileCount;i++)			// 앞에서 루프를 돌면서 위치 선정하기..
	{
		if(remain<=m_files[i].nFileSize-m_files[i].nMultivolHeaderSize-m_files[i].nMultivolTailSize)
		{
			m_nCurFile = i;
			m_nCurFilePos = remain+m_files[i].nMultivolHeaderSize;			// 물리적 위치.
			remainHigh = (LONG)((m_nCurFilePos>>32)&0xffffffff);
#ifdef _WIN32
			SetFilePointer(m_files[i].fp, LONG(m_nCurFilePos), &remainHigh, FILE_BEGIN);
#else
			unalz_fseek(m_files[i].fp, m_nCurFilePos, SEEK_SET);
#endif
			return TRUE;
		}
		remain -= (m_files[i].nFileSize-m_files[i].nMultivolHeaderSize-m_files[i].nMultivolTailSize);
	}

	// 실패..?
	ASSERT(0);
	return FALSE;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          파일 읽기
/// @param   buffer  
/// @param   size  
/// @param   count  
/// @return  
/// @date    2004-10-02 오후 11:44:05
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::FRead(void* buffer, UINT32 nBytesToRead, int* pTotRead )
{
	BOOL ret;
	UINT32 nNumOfBytesRead;
	INT64 dwRemain;
	UINT32 dwRead;
	UINT32 dwTotRead;

	dwRemain = nBytesToRead;
	dwTotRead = 0;
	if(pTotRead) *pTotRead=0;

	while(dwRemain)
	{
		dwRead = (UINT32)min(dwRemain, (m_files[m_nCurFile].nFileSize-m_nCurFilePos-m_files[m_nCurFile].nMultivolTailSize));
		if(dwRead==0) {
			m_bIsEOF = TRUE;return FALSE;
		}
#ifdef _WIN32
		ret = ReadFile(m_files[m_nCurFile].fp, ((BYTE*)buffer)+dwTotRead, dwRead, (DWORD*)&nNumOfBytesRead, NULL);
		if(ret==FALSE && GetLastError()==ERROR_HANDLE_EOF) 
		{
			m_bIsEOF = TRUE;return FALSE;
		}

#else
		nNumOfBytesRead = fread(((BYTE*)buffer)+dwTotRead, 1,dwRead ,m_files[m_nCurFile].fp);
		if(nNumOfBytesRead<=0) 
		{
			m_bIsEOF = TRUE;return FALSE;
		}
		ret=TRUE;
#endif
		if(dwRead!=nNumOfBytesRead)					// 발생하면 안된다..
		{
			ASSERT(0); return FALSE;
		}

		m_nVirtualFilePos += nNumOfBytesRead;	// virtual 파일 위치..

		m_nCurFilePos+=nNumOfBytesRead;					// 물리적 파일 위치.
		dwRemain-=nNumOfBytesRead;
		dwTotRead+=nNumOfBytesRead;
		if(pTotRead) *pTotRead=dwTotRead;

		if(m_nCurFilePos==m_files[m_nCurFile].nFileSize-m_files[m_nCurFile].nMultivolTailSize)	// overflow
		{
			m_nCurFile++;

#ifdef _WIN32
			if(m_files[m_nCurFile].fp==INVALID_HANDLE_VALUE)
#else
			if(m_files[m_nCurFile].fp==NULL)
#endif
			{
				m_bIsEOF = TRUE;
				if(dwRemain==0) return TRUE;						// 완전히 끝까지 읽었다..
				return FALSE;
			}

			m_nCurFilePos = m_files[m_nCurFile].nMultivolHeaderSize;					// header skip
#ifdef _WIN32
			SetFilePointer(m_files[m_nCurFile].fp, (int)m_nCurFilePos, NULL, FILE_BEGIN);
#else
			unalz_fseek(m_files[m_nCurFile].fp, m_nCurFilePos, SEEK_SET);
#endif
		}
		else
			if(m_nCurFilePos>m_files[m_nCurFile].nFileSize-m_files[m_nCurFile].nMultivolTailSize) ASSERT(0);
	}
	
	return ret;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          error code 를 스트링으로 바꿔 준다.
/// @param   nERR  
/// @return  
/// @date    2004-10-24 오후 3:28:39
////////////////////////////////////////////////////////////////////////////////////////////////////
const char* CUnAlz::LastErrToStr(ERR nERR)
{
	if(nERR>= sizeof(errorstrtable)/sizeof(errorstrtable[0])) {ASSERT(0); return NULL; }
	return errorstrtable[nERR];
}


// by xf86
BOOL	CUnAlz::chkValidPassword()
{
	if(IsEncryptedFile()==FALSE) {return TRUE;}

	if (getPasswordLen() == 0){
		m_nErr = ERR_PASSWD_NOT_SET;
		return FALSE;
	}
	InitCryptKeys(m_szPasswd);
	if(CryptCheck(m_posCur->encChk) == FALSE){
		m_nErr = ERR_INVALID_PASSWD;
		return FALSE;
	}
	return TRUE;
}


/*
////////////////////////////////////////////////////////////////////////////////////////////////////
//	from CZipArchive
//	Copyright (C) 2000 - 2004 Tadeusz Dracz
//
//		http://www.artpol-software.com
//
//	it's under GPL.
////////////////////////////////////////////////////////////////////////////////////////////////////
void CUnAlz::CryptDecodeBuffer(UINT32 uCount, CHAR *buf)
{
	if (IsEncrypted())
		for (UINT32 i = 0; i < uCount; i++)
			CryptDecode(buf[i]);
}

void CUnAlz::CryptInitKeys()
{
	m_keys[0] = 305419896L;
	m_keys[1] = 591751049L;
	m_keys[2] = 878082192L;
	for (int i = 0; i < strlen(m_szPasswd); i++)
		CryptUpdateKeys(m_szPasswd[i]);
}

void CUnAlz::CryptUpdateKeys(CHAR c)
{
	
	m_keys[0] = CryptCRC32(m_keys[0], c);
	m_keys[1] += m_keys[0] & 0xff;
	m_keys[1] = m_keys[1] * 134775813L + 1;
	c = CHAR(m_keys[1] >> 24);
	m_keys[2] = CryptCRC32(m_keys[2], c);
}

BOOL CUnAlz::CryptCheck(CHAR *buf)
{
	CHAR b = 0;
	for (int i = 0; i < ALZ_ENCR_HEADER_LEN; i++)
	{
		b = buf[i]; 
		CryptDecode((CHAR&)b);
	}

	if (IsDataDescr()) // Data descriptor present
		return CHAR(m_posCur->head.fileTimeDate >> 8) == b;
	else
		return CHAR(m_posCur->maybeCRC >> 24) == b;
}

CHAR CUnAlz::CryptDecryptCHAR()
{
	int temp = (m_keys[2] & 0xffff) | 2;
	return (CHAR)(((temp * (temp ^ 1)) >> 8) & 0xff);
}

void CUnAlz::CryptDecode(CHAR &c)
{
	c ^= CryptDecryptCHAR();
	CryptUpdateKeys(c);
}

UINT32 CUnAlz::CryptCRC32(UINT32 l, CHAR c)
{
	const ULONG *CRC_TABLE = get_crc_table();
	return CRC_TABLE[(l ^ c) & 0xff] ^ (l >> 8);
}
*/

////////////////////////////////////////////////////////////////////////////////////////////////////
///          암호걸린 파일인지 여부
/// @param   fileDescriptor  
/// @return  
/// @date    2004-11-27 오후 11:25:32
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::IsEncryptedFile(BYTE fileDescriptor)
{
	return fileDescriptor&0x01;
}
BOOL CUnAlz::IsEncryptedFile()
{
	return m_posCur->head.fileDescriptor&0x01;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          암호로 키 초기화
/// @param   szPassword  
/// @return  
/// @date    2004-11-27 오후 11:04:01
////////////////////////////////////////////////////////////////////////////////////////////////////
void CUnAlz::InitCryptKeys(const CHAR* szPassword)
{
	m_key[0] = 305419896;
	m_key[1] = 591751049;
	m_key[2] = 878082192;

	int i;
	for(i=0;i<(int)strlen(szPassword);i++)
	{
		UpdateKeys(szPassword[i]);
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          데이타로 키 업데이트하기
/// @param   c  
/// @return  
/// @date    2004-11-27 오후 11:04:09
////////////////////////////////////////////////////////////////////////////////////////////////////
void CUnAlz::UpdateKeys(BYTE c)
{
	m_key[0] = CRC32(m_key[0], c);
	m_key[1] = m_key[1]+(m_key[0]&0x000000ff);
	m_key[1] = m_key[1]*134775813+1;
	m_key[2] = CRC32(m_key[2],m_key[1]>>24);
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          암호가 맞는지 헤더 체크하기 
/// @param   buf  
/// @return  
/// @date    2004-11-27 오후 11:04:24
////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CUnAlz::CryptCheck(const BYTE* buf)
{
	int i;
	BYTE c;
	BYTE temp[ALZ_ENCR_HEADER_LEN];

	memcpy(temp, buf, ALZ_ENCR_HEADER_LEN);		// 임시 복사.

	for(i=0;i<ALZ_ENCR_HEADER_LEN;i++)
	{
		c = temp[i] ^ DecryptByte();
		UpdateKeys(c);
		temp[i] = c;
	}

	if (IsDataDescr()) // Data descriptor present
		return (m_posCur->head.fileTimeDate >> 8) == c;
	else
		return ( ((m_posCur->fileCRC)>>24) ) == c;		// 파일 crc 의 최상위 byte 
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          키에서 데이타 추출
/// @return  
/// @date    2004-11-27 오후 11:05:36
////////////////////////////////////////////////////////////////////////////////////////////////////
BYTE CUnAlz::DecryptByte()
{
	UINT16 temp;
	temp = m_key[2] | 2;
	return (temp * (temp^1))>>8;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          데이타 압축 풀기
/// @param   nSize  
/// @param   data  
/// @return  
/// @date    2004-11-27 오후 11:03:30
////////////////////////////////////////////////////////////////////////////////////////////////////
void CUnAlz::DecryptingData(int nSize, BYTE* data)
{
	BYTE* p = data;
	BYTE temp;

	while(nSize)
	{
		temp = *p ^ DecryptByte();
		UpdateKeys(temp);
		*p = temp;
		nSize--;
		p++;
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////
///          CRC 테이블 참조
/// @param   l  
/// @param   c  
/// @return  
/// @date    2004-11-27 오후 11:14:16
////////////////////////////////////////////////////////////////////////////////////////////////////
UINT32 CUnAlz::CRC32(UINT32 l, BYTE c)
{
	const unsigned long *CRC_TABLE = get_crc_table();
	return CRC_TABLE[(l ^ c) & 0xff] ^ (l >> 8);
}

void CUnAlz::SetPassword(char *passwd) 
{ 
	if(strlen(passwd) == 0) return; 
	safe_strcpy(m_szPasswd, passwd, UNALZ_LEN_PASSWORD); 
}

#ifdef _UNALZ_ICONV
void CUnAlz::SetDestCodepage(const char* szToCodepage)
{
	safe_strcpy(m_szToCodepage, szToCodepage, UNALZ_LEN_CODEPAGE); 
}
#endif



////////////////////////////////////////////////////////////////////////////////////////////////////
///          문자열 처리 함수들
/// @param   l  
/// @param   c  
/// @return  
/// @date    2007-02
////////////////////////////////////////////////////////////////////////////////////////////////////
unsigned int CUnAlz::_strlcpy (char *dest, const char *src, unsigned int size)
{
	register unsigned int i = 0;
	if (size > 0) {
	size--;
	for (i=0; size > 0 && src[i] != '\0'; ++i, size--)
		dest[i] = src[i];
	dest[i] = '\0';
	}
	while (src[i++]);
	return i;
}
unsigned int CUnAlz::_strlcat (char *dest, const char *src, unsigned int size)
{
	register char *d = dest;
	for (; size > 0 && *d != '\0'; size--, d++);
	return (d - dest) + _strlcpy(d, src, size);
}

// 안전한 strcpy
void CUnAlz::safe_strcpy(char* dst, const char* src, size_t dst_size)
{
#ifdef _WIN32
	lstrcpynA(dst, src, dst_size);
#else
	_strlcpy(dst, src, dst_size);
#endif
}

void CUnAlz::safe_strcat(char* dst, const char* src, size_t dst_size)
{
#ifdef _WIN32
	StringCchCatExA(dst, dst_size, src, NULL, NULL, STRSAFE_FILL_BEHIND_NULL);
	//lstrcatA(dst, src);			// not safe!!
#else
	_strlcat(dst, src, dst_size);
#endif
}

