/*-- Include
*/
#include "patchmaker.h"
#include "zlib.h"
#include "ZipArchive.h"
#include <stdio.h>
#include <time.h>
#include <io.h>
/*-- Local definitions
*/
#define SafeDelete(P)	{ if (P!=NULL) { delete(P); (P)=NULL; } }       // 메모리를 안전하게 해제.

#define WM_PATCH_MSG	WM_USER+0x10
#define PM_COMPLETE		0
#define PM_ERROR_XCOPY  1
#define PM_ERROR        2



/*-- Global data
*/
HINSTANCE   g_instance   = NULL;
Patchmaker* g_patchmaker = NULL;

/*-- WinMain Prototype
*/
int WINAPI WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdLine, int cmdShow)
{
	// Initialize COM Library - INTERNET API(wininet.h/wininet.lib)를 사용하기 위해 선언.
	if ( CoInitialize( NULL ) != S_OK )
	{
		MessageBox( NULL, "CoInitialize Failed!\r\n", "Patchmaker", MB_OK );
		return 0L;
	}

	Patchmaker* patchMaker = new Patchmaker( );
	HANDLE      handle     = CreateMutex( NULL, FALSE, patchMaker->GetWindowName( ) );
	int         result     = GetLastError( );

	g_instance = instance;
	if ( handle != NULL )
	{
		if ( result != ERROR_ALREADY_EXISTS )
			result = patchMaker->Run( instance, prevInstance, cmdLine, cmdShow );
		CloseHandle( handle );
	}

	SafeDelete( patchMaker );

	// Finished with COM Library
	CoUninitialize( );
	return result;
}

/*-- DlgProc Prototype
*/
LRESULT CALLBACK DlgProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	return g_patchmaker->MsgProc( wnd, msg, wParam, lParam );
}

/*-- WorkerThreadStartingPoint Prototype
*/
DWORD WINAPI WorkerThreadStartingPoint(void* ptr)
{
	return g_patchmaker->WorkerThread( );
}

/*-- Patchmaker Constructor
*/
Patchmaker::Patchmaker(void)
{
	g_patchmaker   = this;
	m_wnd          = NULL;
	m_icon         = NULL;
	m_windowName   = TEXT( "Patchmaker - IRIS ON LINE" );
	m_endApp       = false;

	m_packlist.pack    = NULL;
	m_packlist.offset  = 0;
	m_packlist.length  = 0;

	m_patchlist.file   = NULL;
	m_patchlist.offset = 0;
	m_patchlist.length = 0;

	m_workerThread = NULL;
}

/*-- ~Patchmaker Destructor
*/
Patchmaker::~Patchmaker(void)
{
	g_patchmaker = NULL;
}

/*-- EventWrite Method
*/
void Patchmaker::EventWrite(LPCTSTR format, ...)
{
	LPVOID  msgBuf = NULL;
	DWORD   bufferLength;
	va_list args;

	va_start( args, format );

	bufferLength = _vscprintf( format, args ) +1;
	msgBuf       = malloc( bufferLength );
	vsprintf( (char*)msgBuf, format, args );

	va_end( args );

	time_t t;
	tm *pt;
	char time_array[ 1024 ];

	time(&t);
	pt=localtime(&t);

	sprintf( time_array, "%d/%d/%d %d:%d:%d \0",	pt->tm_year+1900, pt->tm_mon+1, pt->tm_mday, pt->tm_hour,pt->tm_min,pt->tm_sec );

	if ( msgBuf != NULL )
	{
		char filename[1024];
		sprintf( filename, "%s\\%s.log", m_currentDirectory, GetWindowName( ) );

		FILE* stream = fopen( filename, "at" );
		if ( stream != NULL )
		{
			fputs( time_array   , stream );
			fputs( (char*)msgBuf, stream );
			fclose( stream );
			stream = NULL;
		}

		free( msgBuf );
	}
}

/*-- GetFileTimeUTC Method
*/
bool Patchmaker::GetFileTimeUTC(char* filename, SYSTEMTIME* utc)
{
	HANDLE file = CreateFile( filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if ( file != INVALID_HANDLE_VALUE ) 
	{
		FILETIME write;
		GetFileTime( file, NULL, NULL, &write );
		FileTimeToSystemTime( &write, utc );
		CloseHandle( file );
		return true;
	}
	return false;
}

/*-- SetFileTimeUTC Method
*/
bool Patchmaker::SetFileTimeUTC(char* filename, SYSTEMTIME* utc)
{
	HANDLE file = CreateFile( filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if ( file != INVALID_HANDLE_VALUE ) 
	{
		FILETIME write;
		SystemTimeToFileTime( utc, &write );
		SetFileTime( file, NULL, NULL, &write );
		CloseHandle( file );
		return true;
	}
	return false;
}

/*-- FileTimeToUTC Method
*/
void Patchmaker::FileTimeToUTC(FILETIME* filetime)
{
	SYSTEMTIME utc;
	FileTimeToSystemTime( filetime, &utc );
	SystemTimeToFileTime( &utc, filetime );
}

/*-- FileTimeToDOS Method
*/
void Patchmaker::FileTimeToDOS(FILETIME* filetime)
{
	WORD fatDate, fatTime;
	FileTimeToDosDateTime( filetime, &fatDate, &fatTime );
	DosDateTimeToFileTime( fatDate, fatTime, filetime );
}

/*-- FileTimeCompare Method
*/
long Patchmaker::FileTimeCompare(char* arg1, char* arg2, int* error)
{
	HANDLE   file    = NULL;
	long     retcode = 0;

	FILETIME filetime1;
	FILETIME filetime2;

	file = CreateFile( arg1, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if ( file != INVALID_HANDLE_VALUE ) 
	{
		GetFileTime( file, NULL, NULL, &filetime1 );
		CloseHandle( file );
	}
	else
		return -1;

	file = CreateFile( arg2, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if ( file != INVALID_HANDLE_VALUE ) 
	{
		GetFileTime( file, NULL, NULL, &filetime2 );
		CloseHandle( file );
	}
	else
		return -2;

	retcode  = CompareFileTime( &filetime1, &filetime2 );
	(*error) = 0;
	if ( retcode != 0 )
	{
		FileTimeToUTC( &filetime1 );
		FileTimeToUTC( &filetime2 );
		retcode = CompareFileTime( &filetime1, &filetime2 );
		(*error) = 1;
	}
	if ( retcode != 0 )
	{
		FileTimeToDOS( &filetime1 );
		FileTimeToDOS( &filetime2 );
		retcode = CompareFileTime( &filetime1, &filetime2 );
		(*error) = 2;
	}
	return retcode;
}

/*-- Pack Method
*/
bool Patchmaker::Pack(char* source, char* target, SYSTEMTIME* utc, bool bReverseWrite )
{
	if( m_endApp == true )
		return false;

	HANDLE file    = CreateFile( source, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	bool   retcode = false;

	if ( file != INVALID_HANDLE_VALUE )
	{
		FILETIME   create;
		FILETIME   access;
		FILETIME   write;

		DWORD      sizeHigh;
		DWORD      sizeLow;

		GetFileTime( file, &create, &access, &write );
		if ( utc != NULL )
			FileTimeToSystemTime( &write, utc );

		sizeLow = GetFileSize( file, &sizeHigh );

		gzFile gz = gzopen( target, "wb" );
		if ( gz != NULL )
		{
			char  buf[16384];			// CHUNK 16KByte
			char  reverse_buf[16384];
			DWORD len = sizeof(buf);
			DWORD bytesRead;

			while ( true && m_endApp == false )
			{
				ReadFile( file, buf, len, &bytesRead, NULL );
				if ( bytesRead > 0 )
				{
					if( bReverseWrite == true )
					{
						char* pbuf = buf + bytesRead - 1;
						char* prev = reverse_buf;
						for( DWORD cnt = 0; cnt < bytesRead; ++cnt )
						{
							*prev = *pbuf;
							pbuf--;
							prev++;
						}
						gzwrite( gz, reverse_buf, bytesRead );
					}
					else 
						gzwrite( gz, buf, bytesRead );
				}
				else
					break;
			}
			gzclose( gz );
			gz = NULL;

			retcode = true;
		}

		CloseHandle( file );
		file = NULL;
	}

	return retcode;
}

/*-- Unpack Method
*/
bool Patchmaker::Unpack(char* source, char* target, SYSTEMTIME* utc)
{

	if( m_endApp == true )
		return false;

	gzFile gz = gzopen( source, "rb" );
	bool   retcode = false;

	if ( gz != NULL )
	{
		HANDLE file = CreateFile( target, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		if ( file != INVALID_HANDLE_VALUE )
		{
			char  buf[16384];			// CHUNK 16KByte
			DWORD len = sizeof(buf);

			DWORD bytesRead;
			DWORD bytesWritten;
			while ( true && m_endApp == false )
			{
				bytesRead = gzread( gz, buf, len );
				if ( bytesRead > 0 )
					WriteFile( file, buf, bytesRead, &bytesWritten, NULL );
				else
					break;
			}

			if ( utc != NULL )
			{
				FILETIME write;
				SystemTimeToFileTime( utc, &write );
				SetFileTime( file, NULL, NULL, &write );
			}

			CloseHandle( file );
			file = NULL;

			retcode = true;
		}
		gzclose( gz );
		gz = NULL;
	}

	return retcode;
}

/*-- Run Method
*/
int Patchmaker::Run(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdLine, int cmdShow)
{
	// 현재 폴더 저장.
	GetCurrentDirectory( sizeof(m_currentDirectory), m_currentDirectory );

	// 응용 프로그램, 다이얼로그 생성
	m_wnd = CreateDialog( instance, MAKEINTRESOURCE(IDD_PATCHMAKER), NULL, (DLGPROC)DlgProc );
	if ( m_wnd == NULL )
		return GetLastError( );

	// 윈도우 이름 입력
	SetWindowText( m_wnd, GetWindowName( ) );

	/*-- Command Line 처리.
	*/
	if ( CommandLine( cmdLine ) == true )
		StartPatch( );

	// 응용 프로그램, 다이얼로그 표시
	ShowWindow( m_wnd, cmdShow );

	// 메시지 루프 시작
	MSG  msg;
	BOOL result;

	while ( (result = GetMessage( &msg, NULL, 0, 0 )) )
	{
		if ( result == -1 )
		{
			// handle the error and possibly exit
			return GetLastError( );
		}
		else if ( !IsWindow( m_wnd ) || !IsDialogMessage( m_wnd, &msg ) )
		{
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}
	}
	return (int)msg.wParam;
}

/*-- MsgProc Method
*/
LRESULT Patchmaker::MsgProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch ( msg )
	{
	case WM_DESTROY:    return Destroy    ( wnd, wParam, lParam );
	case WM_INITDIALOG: return InitDialog ( wnd, wParam, lParam );
	case WM_COMMAND:    return Command    ( wnd, wParam, lParam );
	case WM_DROPFILES:  return DropFiles  ( wnd, wParam, lParam );
	case WM_PATCH_MSG:  return PatchMsg   ( wnd, wParam, lParam );
	}
	return 0L;
}

/*-- CommandLine Method
*/
bool Patchmaker::CommandLine(LPSTR cmdLine)
{
	if ( strlen( cmdLine ) > 0 )
	{
		char  path[ 1024 ] = "\0";
		char  seps[] = " ";
		bool  patch  = true;

		char* token;
		DWORD retcode;

		token = strtok( cmdLine, seps );
		if ( token != NULL )
			strcpy( m_sourceDirectory, token );
		else
			memset( m_sourceDirectory, 0, sizeof(m_sourceDirectory) );

		retcode = GetFileAttributes( m_sourceDirectory );
		if ( (retcode != INVALID_FILE_ATTRIBUTES) && (retcode & FILE_ATTRIBUTE_DIRECTORY) )
			SetWindowText( GetDlgItem( m_wnd, IDC_SOURCE_DIR ), m_sourceDirectory );
		else
			patch = false;

		token = strtok( NULL, seps );
		if ( token != NULL )
			strcpy( m_targetDirectory, token );
		else
			memset( m_targetDirectory, 0, sizeof(m_targetDirectory) );

		retcode = GetFileAttributes( m_targetDirectory );
		if ( (retcode != INVALID_FILE_ATTRIBUTES) && (retcode & FILE_ATTRIBUTE_DIRECTORY) )
			SetWindowText( GetDlgItem( m_wnd, IDC_TARGET_DIR ), m_targetDirectory );
		else
			patch = false;

		token = strtok( NULL, seps );
		if ( token != NULL )
			strcpy( m_workerDirectory, token );
		else
			memset( m_workerDirectory, 0, sizeof(m_workerDirectory) );

		retcode = GetFileAttributes( m_workerDirectory );
		if ( (retcode != INVALID_FILE_ATTRIBUTES) && (retcode & FILE_ATTRIBUTE_DIRECTORY) )
			SetWindowText( GetDlgItem( m_wnd, IDC_WORKER_DIR ), m_workerDirectory );
		else
			patch = false;

		/// 백업 폴더 지정 [2/10/2009 Jo_2]
		sprintf(m_backupDirectory, "%s\\%s\0", m_targetDirectory, BACKUP_DIRECTORY_NAME );

		return patch;
	}
	return false;
}

/*-- SetStatus Method
*/
BOOL Patchmaker::SetStatus(HWND wnd, char* msg)
{
	HWND dlgItem = GetDlgItem( wnd, IDC_STATUS );
	BOOL retcode = SetWindowText( dlgItem, msg );
	UpdateWindow( dlgItem );
	return retcode;
}

/*-- Progress Method
*/
void Patchmaker::Progress(DWORD length, DWORD offset)
{
	if ( m_wnd != NULL )
	{
		int percent = (int)((float)offset / (float)length * 100.f);
		SendMessage( GetDlgItem( m_wnd, IDC_PROGRESS ), PBM_SETPOS, percent, 0 );
	}
}

/*-- Destroy Method
*/
LRESULT Patchmaker::Destroy(HWND wnd, WPARAM wParam, LPARAM lParam)
{
	// 안전종료.
	m_endApp = true;

	// m_workerThread 핸들를 해제.
	if ( m_workerThread != NULL )
	{
		// WorkerThread가 종료될때까지 대기.
		if ( WaitForSingleObject( m_workerThread, 5000 ) != WAIT_OBJECT_0 )
		{
			DWORD exitCode;
			GetExitCodeThread( m_workerThread, &exitCode );
			if ( exitCode == STILL_ACTIVE )
			{
				TerminateThread( m_workerThread, 0 );
			}
		}
		CloseHandle( m_workerThread );
		m_workerThread = NULL;
	}

	// ICON 해제
	if ( m_icon != NULL )
	{
		DestroyIcon( m_icon );
		m_icon = NULL;
	}

	// 윈도우 탐색기로 부터 Drag & Drop 종료.
	DragAcceptFiles( wnd, FALSE );

	// 응용 프로그램을 종료 한다.
	PostQuitMessage( 0 );
	return 0L;
}

/*-- InitDialog Method
*/
LRESULT Patchmaker::InitDialog(HWND wnd, WPARAM wParam, LPARAM lParam)
{
	// 윈도우 탐색기로 부터 Drag & Drop 시작.
	DragAcceptFiles( wnd, TRUE );

	// ICON 로드
	m_icon = LoadIcon( g_instance, MAKEINTRESOURCE( IDI_ICON ) );
	SendMessage( wnd, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)m_icon );

	// 상태.
	SetStatus( wnd, "Ready" );
	return 1L;
}

/*-- Command Method
*/
LRESULT Patchmaker::Command(HWND wnd, WPARAM wParam, LPARAM lParam)
{
	switch ( LOWORD(wParam) )
	{
	case IDOK:
		//DestroyWindow( wnd );
		break;
	case IDCANCEL:
		DestroyWindow( wnd );
		break;
	}
	return 0L;
}

/*-- DropFiles Method
*/
LRESULT Patchmaker::DropFiles(HWND wnd, WPARAM wParam, LPARAM lParam)
{
	UINT  count = DragQueryFile( (HDROP)wParam, 0xFFFFFFFF, NULL ,0 );
	POINT pt;

	char  buffer[1024];
	DWORD fileAttributes;

	int   files;
	int   directories;

	// 어느 위치에 드롭되었는지 그 항목을 알아낸다.
	if ( DragQueryPoint( (HDROP)wParam, &pt ) ) 
	{
		SetStatus( wnd, "Start Verify..." );

		files       = 0;
		directories = 0;
		for ( UINT index = 0; index < count; index++ )
		{
			// 드롭된 파일의 이름을 알아온다.
			DragQueryFile( (HDROP)wParam, index, buffer, sizeof(buffer) );

			fileAttributes = GetFileAttributes( buffer );
			if ( fileAttributes & FILE_ATTRIBUTE_DIRECTORY )
			{
				Verify( buffer, NULL, files, directories );
			}
			else if ( fileAttributes & FILE_ATTRIBUTE_ARCHIVE )
			{
				Verify( buffer );
			}
		}
		SetStatus( wnd, "End Verify" );
	}

	// drag and drop 작업을 끝낸다.
	DragFinish( (HDROP)wParam );
	return 0L;
}

/*-- PatchMsg Method
*/
LRESULT Patchmaker::PatchMsg(HWND wnd, WPARAM wParam, LPARAM lParam)
{
	switch ( wParam )
	{
	case PM_COMPLETE:
		MessageBox( wnd, "Success", GetWindowName( ), MB_OK );
		break;
	case PM_ERROR_XCOPY:
		MessageBox( wnd, "Error Xcopy", GetWindowName( ), MB_OK );
		break;
	case PM_ERROR:
		MessageBox( wnd, "Error", GetWindowName( ), MB_OK );
		break;
	default:
		MessageBox( wnd, "Unkonwn", GetWindowName( ), MB_OK );
		break;
	}
	PostMessage( m_wnd, WM_CLOSE, 0, 0 );
	return 0L;
}

/*-- Verify Method
*/
int Patchmaker::Verify(char* root, char* sub, int& files, int& directories)
{
	HANDLE          search = NULL;
	char            where[ 1024 ];

	char            filename[ 1024 ];
	WIN32_FIND_DATA fileData;

	// 하위 디렉토리 적용여부
	if ( sub )
		sprintf( where, "%s\\%s", root, sub );
	else
		sprintf( where, "%s", root );

	sprintf( filename, "%s\\*.*", where );

	search = FindFirstFile( filename, &fileData );
	if ( search != INVALID_HANDLE_VALUE )
	{
		do {
			if ( fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
			{
				if ( (strcmp( fileData.cFileName, "."  ) != 0) && (strcmp( fileData.cFileName, ".." ) != 0) )
				{
					char directory[ 1024 ];

					if ( sub )
						sprintf( directory, "%s\\%s", sub, fileData.cFileName );
					else
						sprintf( directory, "%s", fileData.cFileName );

					Verify( root, directory, files, directories );	// 재귀호출.
					directories += 1;								// 폴더 수 누적.
				}
			}
			else if ( fileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE )
			{
				char fullpath[ 1024 ];

				if ( sub )
					sprintf( fullpath, "%s\\%s\\%s", root, sub, fileData.cFileName );
				else
					sprintf( fullpath, "%s\\%s", root, fileData.cFileName );

				Verify( fullpath );									// 파일 검사.
				files += 1;											// 파일 수 누적.
			}
		} while ( FindNextFile( search, &fileData ) );

		FindClose( search );
		return 0;
	}
	return -1; // 검사 실패.
}

/*-- Verify Method
*/
int Patchmaker::Verify(char* filename)
{
	SYSTEMTIME utc;

	char packname[ 1024 ];
	sprintf( packname, "%s.z", filename );

	Pack( filename, packname, &utc );

	char unpackname[ 1024 ];
	sprintf( unpackname, "%s.u", filename );

	Unpack( packname, unpackname, &utc );

	long retvalue;
	int  error;
	retvalue = FileTimeCompare( filename, unpackname, &error );

	EventWrite( "CompareFileTime(%d,%d)\t%s\n", retvalue, error, filename );

	DeleteFile( packname   );
	DeleteFile( unpackname );

	return retvalue;
}

/*-- Xcopy Method
*/
int Patchmaker::Xcopy(char* sub)
{
	int             retcode = 0;	 // 복사 성공.
	HANDLE          search  = NULL;
	char            where[ 1024 ];

	char            filename[ 1024 ];
	WIN32_FIND_DATA fileData;

	/// script폴더 내의 .pack을 다시 만들어야 하는지 체크
	bool bNeedPackRemake[eSUB_DIRECTORY_MAX_COUNT] = { false, false, false, false };

	// 하위 디렉토리 적용여부
	if ( sub )
		sprintf( where, "%s\\%s", m_workerDirectory, sub );
	else
		sprintf( where, "%s", m_workerDirectory );

	sprintf( filename, "%s\\*.*", where );

	search = FindFirstFile( filename, &fileData );
	if ( search != INVALID_HANDLE_VALUE )
	{
		do {
			if ( fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
			{
				if ( (strcmp( fileData.cFileName, "."  ) != 0) && (strcmp( fileData.cFileName, ".." ) != 0) )
				{
					char directory[ 1024 ];

					char source[ 1024 ];
					char target[ 1024 ];
					bool skipMakeSubDirectory = false;

					if ( sub )
					{
						sprintf( directory, "%s\\%s", sub, fileData.cFileName );
						sprintf( source, "%s\\%s\\%s", m_sourceDirectory, sub, fileData.cFileName );
						sprintf( target, "%s\\%s\\%s", m_targetDirectory, sub, fileData.cFileName );
 						if( stricmp( sub, SCRIPT_DIRECTORY ) == 0 )
 							skipMakeSubDirectory = true;
					}
					else
					{
						sprintf( directory, "%s", fileData.cFileName );
						sprintf( source, "%s\\%s", m_sourceDirectory, fileData.cFileName );
						sprintf( target, "%s\\%s", m_targetDirectory, fileData.cFileName );
					}
					if ( GetFileAttributes( source ) == INVALID_FILE_ATTRIBUTES ) 
					{
						if ( CreateDirectory( source, NULL ) == FALSE )
						{
							if ( GetLastError( ) != ERROR_ALREADY_EXISTS )
							{
								FindClose( search );
								return -2; // 폴더 생성 실패.
							}
						}
					}

					strlwr(target);
					if ( GetFileAttributes( target ) == INVALID_FILE_ATTRIBUTES && skipMakeSubDirectory == false )
					{
						if ( CreateDirectory( target, NULL ) == FALSE )
						{
							if ( GetLastError( ) != ERROR_ALREADY_EXISTS )
							{
								FindClose( search );
								return -2; // 폴더 생성 실패.
							}
						}
					}
					// 재귀호출.
					retcode = Xcopy( directory );
				}
			}
			else if ( fileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE || fileData.dwFileAttributes & FILE_ATTRIBUTE_READONLY )
			{
				char worker[ 1024 ];
				char source[ 1024 ];
				char target[ 1024 ];
                
				if ( sub )
				{
					sprintf( worker, "%s\\%s\\%s", m_workerDirectory, sub, fileData.cFileName );
					sprintf( source, "%s\\%s\\%s", m_sourceDirectory, sub, fileData.cFileName );
					sprintf( target, "%s\\%s\\%s.z", m_targetDirectory, sub, fileData.cFileName );
				}
				else
				{
					sprintf( worker, "%s\\%s", m_workerDirectory, fileData.cFileName );
					sprintf( source, "%s\\%s", m_sourceDirectory, fileData.cFileName );
					sprintf( target, "%s\\%s.z", m_targetDirectory, fileData.cFileName );
				}

				int  error    = 0;
				long retvalue = FileTimeCompare( worker, source, &error );
				bool skipPack = false;

				if ( retvalue != 0 )
				{
					EventWrite( "CompareFileTime(%d,%d) - Xcopy from(%s) to(%s)\n", retvalue, error, worker, source );

#ifdef _CLIENT_MODE_
					/// script 폴더 내의 파일이 바뀐 경우 pack 파일을 갱신시켜야 함.
					if( sub != NULL && strnicmp( sub, SCRIPT_DIRECTORY, strlen(SCRIPT_DIRECTORY) ) == 0)
					{
						for(int index=0; index < eSUB_DIRECTORY_MAX_COUNT; ++index)
						{
							if( strnicmp( sub, SCRIPT_SUB_DIRECTORY[index], strlen( SCRIPT_SUB_DIRECTORY[index] ) ) == 0 )
							{
								bNeedPackRemake[index] = true;
								skipPack = true;
							}
						}
					}
#endif
					bool retPack = true;
					SYSTEMTIME utc;

					/// script 파일안에 pack 파일을 생성 및 .z로 압축 할때에는 체크가 모두 끝난뒤에 한번에 한다.
					if( skipPack == false )
						retPack = Pack( worker, strlwr(target), &utc ); //continue;
					
					if( retPack == true )
					{
						bool isReadOnlyFile = false;
						DWORD fileAttibute  = 0;
						DWORD error_code =0;
						if( _access( source ,0 ) == 0 )
						{
							fileAttibute = GetFileAttributes( source );
							
							if( fileAttibute & FILE_ATTRIBUTE_READONLY )
							{
								if( SetFileAttributes( source, FILE_ATTRIBUTE_ARCHIVE ) == 0)
									ErrorPrint( "XCopy:Fail to change file attribute" )	;
								isReadOnlyFile = true;
							}

							/// target 폴더 안에 새로운 폴더를 만들어서 기존 파일들 백업시키기. 패치를 롤벡할때 쓰임 [2/9/2009 Jo_2]
							{
								char backup[ 1024 ] = { 0, };
								
								if( sub )
									sprintf( backup, "%s\\%s", m_backupDirectory, sub );
								else
									sprintf( backup, "%s", m_backupDirectory );
																
								int ret = _access( backup ,0 );
								if( ret == 0 )
								{

									if( sub )
										sprintf( backup, "%s\\%s\\%s.z", m_backupDirectory, sub, fileData.cFileName );
									else
										sprintf( backup, "%s\\%s.z", m_backupDirectory, fileData.cFileName );
									
									// 소스에서 파일을 읽어와서 .z파일로 압축하기. 압축할 파일인지 체크.
                                    if ( Pack( source, backup, &utc, false ) == false )
									{
										EventWrite("Fail to backup Original [%s]file", fileData.cFileName );
										continue;
									}
								}
								else if( ret == -1 ) //폴더가 없으면
								{
									char drive    [ _MAX_DRIVE ] = {0,};
									char directory[ _MAX_DIR   ] = {0,};
									char makeDir  [ 1024       ] = {0,};
									char backupFilename [ 1024       ] = {0,};

									char  seps[] = "/\\";
									char* token;

 									if(sub)
 										sprintf( backupFilename, "%s\\%s\\%s", m_backupDirectory, sub, fileData.cFileName );
 									else
 										sprintf( backupFilename, "%s\\%s", m_backupDirectory, fileData.cFileName );

									strlwr( backupFilename );

									_splitpath( backupFilename, drive, directory, NULL, NULL );

									strcat( makeDir, drive );
									strcat( makeDir, "/" );

									token = strtok( directory, seps );
									while ( token != NULL )
									{
										strcat( makeDir, token );
										strcat( makeDir, "/" );

										if ( GetFileAttributes( makeDir ) == 0xFFFFFFFF )
										{
											if ( CreateDirectory( makeDir, NULL ) == FALSE )
											{
												ErrorPrint(" Fail to create backup directory" );
												continue;
											}
										}
										token = strtok( NULL, seps );
									}

									// 소스에서 파일을 읽어와서 .z파일로 압축하기.
									strcat( backupFilename, ".z" );
									SYSTEMTIME utc;
									if ( Pack( source, backupFilename, &utc, false ) == false )
									{
										EventWrite("Fail to backup Original [%s]file", backup );
										continue;
									}
								}
								else
								{
									EventWrite( "Fail to _access backup directory" );
									continue;
								}
								
							}
						}
						
						

						// CopyFile에서 확장. [12/11/2008 Jo]
						if ( CopyFileEx( worker, source, NULL, NULL, (LPBOOL)m_endApp, 0 ) == TRUE ) 
						{
							GetFileTimeUTC( worker, &utc );
							SetFileTimeUTC( source, &utc );
							
 							if( isReadOnlyFile == true )
 							{
 								SetFileAttributes( worker, FILE_ATTRIBUTE_READONLY );
  							}

						}
						else
						{
							retcode = -4; // 파일 복사 실패.
							ErrorPrint( "XCopy:Fail to change file attribute" )	;
						}
					}
					else
						retcode = -3; // 압축 복사 실패.
				}
			}
		} while ( FindNextFile( search, &fileData ) && retcode == 0 );

		FindClose( search );
		search = NULL;


#ifdef _CLIENT_MODE_
		/// script 폴더안의 pack을 다시 생성해야 할 필요가 있는지 체크 한뒤에 압축하여 target/script 폴더에 저장.
		for(int index=0; index < eSUB_DIRECTORY_MAX_COUNT; ++index)
		{
			if( bNeedPackRemake[index] == true )
			{
				CZipArchive zip;
				char fileNameDummy[1024];
				char packfilename[1024];
				char *packFolderName;

				strcpy( fileNameDummy, SCRIPT_SUB_DIRECTORY[index] );
				
				packFolderName = strtok( fileNameDummy, "/\\" );
				packFolderName = strtok( NULL, "/\\" );

				sprintf( packfilename, "%s\\%s.pack", m_sourceDirectory, SCRIPT_SUB_DIRECTORY[index] );
				zip.Open( packfilename, CZipArchive::zipCreate );  
				zip.SetPassword(ZIP_PASSWORD);

				sprintf( filename, "%s\\%s\\*.*", m_sourceDirectory, SCRIPT_SUB_DIRECTORY[index] );
				
				search = FindFirstFile( filename, &fileData );
				if ( search != INVALID_HANDLE_VALUE )
				{
					do {
						if ( fileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE )
						{
							char     fullpath[ 1024 ];
							char     filepath[ 1024 ];
							sprintf( fullpath, "%s\\%s\\%s", m_sourceDirectory, SCRIPT_SUB_DIRECTORY[index], fileData.cFileName );
							sprintf( filepath, "%s\\%s", SCRIPT_SUB_DIRECTORY[index], fileData.cFileName );
						
							zip.AddNewFile( fullpath, filepath , Z_NO_COMPRESSION );
						}
					} while ( FindNextFile( search, &fileData ) );

					FindClose( search );
				}
				
				zip.Close();

				SYSTEMTIME utc;
				char	   target[1024];
				
				sprintf( target, "%s\\%s.pack.z", m_targetDirectory, SCRIPT_SUB_DIRECTORY[index] );
				
				if( Pack( packfilename, strlwr(target), &utc ) == false )
					retcode = -3; // 압축 복사 실패.

				//DeleteFile(packfilename);
			}
		}
#endif

		return retcode;
	}
	return -1; // 복사실패
}

/*-- Search Method
*/
int Patchmaker::Search(char* root, char* sub, int& files, int& directories, bool opcode)
{
	HANDLE          search = NULL;
	char            where[ 1024 ];

	char            filename[ 1024 ];
	WIN32_FIND_DATA fileData;

	// 하위 디렉토리 적용여부
	if ( sub )
		sprintf( where, "%s\\%s", root, sub );
	else
		sprintf( where, "%s", root );

	sprintf( filename, "%s\\*.*", where );

	search = FindFirstFile( filename, &fileData );
	if ( search != INVALID_HANDLE_VALUE )
	{
		do {
			if ( fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
			{
				if ( (strcmp( fileData.cFileName, "."  ) != 0) && (strcmp( fileData.cFileName, ".." ) != 0) )
				{
					char directory[ 1024 ];

					if ( sub )
						sprintf( directory, "%s\\%s", sub, fileData.cFileName );
					else
						sprintf( directory, "%s", fileData.cFileName );
#ifdef _CLIENT_MODE_
					/// script 폴더 안에 데이터들 skip.
					bool skipWriteList = false;
					for(int index=0; index < eSUB_DIRECTORY_MAX_COUNT; ++index)
					{
						if( strnicmp( directory, SCRIPT_SUB_DIRECTORY[index], strlen( SCRIPT_SUB_DIRECTORY[index] ) ) == 0 )
							skipWriteList = true;
					}

					if( skipWriteList == true )
						continue;
#endif

					if ( opcode )
					{
						Packfile* pack = m_packlist.pack + m_packlist.offset;

						if ( sub )
							sprintf( pack->directory, "%s\\%s\\", sub, fileData.cFileName );
						else
							sprintf( pack->directory, "%s\\", fileData.cFileName );

						m_packlist.offset++;
					}

					Search( root, directory, files, directories, opcode );	// 재귀호출.
					
					directories += 1; // 폴더 수 누적.
				}
			}
			else if ( fileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE )
			{
				char fullpath[ 1024 ];

				if ( sub )
					sprintf( fullpath, "%s\\%s\\%s", root, sub, fileData.cFileName );
				else
					sprintf( fullpath, "%s\\%s", root, fileData.cFileName );

				if ( opcode )
				{
					Patchfile* file = m_patchlist.file + m_patchlist.offset;

					if ( sub )
						sprintf( file->fileName, "%s\\%s", sub, fileData.cFileName );
					else
						sprintf( file->fileName, "%s", fileData.cFileName );

					file->sizeHigh  = fileData.nFileSizeHigh;
					file->sizeLow   = fileData.nFileSizeLow;
					file->writeTime = fileData.ftLastWriteTime;

					FileTimeToDOS( &file->writeTime );

					m_patchlist.offset++;
				}

				files += 1; // 파일 수 누적.
			}
		} while ( FindNextFile( search, &fileData ) );

		FindClose( search );
		return 0;
	}
	return -1; // 검색 실패.
}

/*-- StartPatch Method
*/
bool Patchmaker::StartPatch( )
{
	DWORD threadId;
	m_workerThread = CreateThread( NULL, 0, WorkerThreadStartingPoint, (LPVOID)this, 0, &threadId );
	return (m_workerThread != NULL) ? true : false;
	//return WorkerThread( ) == 0;
}

/*-- WorkerThread Method
*/
DWORD Patchmaker::WorkerThread( )
{
	int  files;
	int  directories;
	bool success = false;

	SetStatus( m_wnd, "Start Xcopy..." );

	if ( Xcopy( NULL ) != 0 )
	{
		PostMessage( m_wnd, WM_PATCH_MSG, (WPARAM)PM_ERROR_XCOPY, (LPARAM)0 );
		SetStatus( m_wnd, "Error Xcopy..." );
		return 0;
	}

	SetStatus( m_wnd, "End Xcopy..." );

	SetStatus( m_wnd, "Start Make..." );
	
	if ( Search( m_sourceDirectory, NULL, files=0, directories=0, false ) == 0 )
	{
		m_packlist.pack    = (Packfile*)GlobalAlloc( GPTR, sizeof(Patchfile)*directories );
		m_packlist.offset  = 0;
		m_packlist.length  = directories;

		m_patchlist.file   = (Patchfile*)GlobalAlloc( GPTR, sizeof(Patchfile)*files );
		m_patchlist.offset = 0;
		m_patchlist.length = files;

		if ( Search( m_sourceDirectory, NULL, files=0, directories=0, true ) == 0 )
		{
			char filename[1024];
			char packfilename[1024];

			sprintf( filename, "%s\\patchlist.txt", m_targetDirectory );
			sprintf( packfilename, "%s.z", filename );

			FILE* stream = fopen( filename, "wt" );
			if ( stream != NULL )
			{
				char buffer[ 2048 ];

				// header
				sprintf( buffer, "patchlist/ver.110/%d/%d\n", directories, files );
				fputs( buffer, stream );

				// body
				Packfile* packfile = m_packlist.pack;
				for ( ULONG i = 0; i < m_packlist.length && m_endApp == false; i++ , packfile++ )
				{

#ifdef _CLIENT_MODE_
					bool skipWriteList = false;

					for(int index=0; index < eSUB_DIRECTORY_MAX_COUNT; ++index)
					{
						char sub_directory[MAX_PATH];
						sprintf(sub_directory, "%s\\", SCRIPT_SUB_DIRECTORY[index] );

						if( strnicmp( packfile->directory, sub_directory, strlen( sub_directory ) ) == 0 )
							skipWriteList = true;
					}

					if( skipWriteList == true )
						continue;
#endif

					sprintf( buffer ,"%s\n" ,strlwr(packfile->directory) );
					fputs( buffer, stream );

					Progress( m_patchlist.length, i+1 );
				}

				// body
				Patchfile* patchfile = m_patchlist.file;
				for ( ULONG i = 0; i < m_patchlist.length && m_endApp == false; i++ , patchfile++ )
				{

#ifdef _CLIENT_MODE_
					bool skipWriteList = false;

					for(int index=0; index < eSUB_DIRECTORY_MAX_COUNT; ++index)
					{
						char sub_directory[MAX_PATH];
						sprintf(sub_directory, "%s\\", SCRIPT_SUB_DIRECTORY[index] );

						if( strnicmp( patchfile->fileName, sub_directory, strlen( sub_directory ) ) == 0 )
							skipWriteList = true;
					}

					if( skipWriteList == true )
						continue;
#endif

					time_t time;
					time_t getTime;
					FILETIME write;
					FILETIME newWrite;

					write = patchfile->writeTime;

					const __int64 SecsTo100ns = 10000000;
					const __int64 SecsBetweenEpochs = 11644473600;
					__int64 converted_time;
					converted_time = ((__int64)write.dwHighDateTime << 32) + write.dwLowDateTime;
					converted_time -= (SecsBetweenEpochs * SecsTo100ns);
					converted_time /= SecsTo100ns;
					time = (time_t)converted_time;
					getTime = time;

					LONGLONG ll;
					ll = Int32x32To64(getTime, 10000000) + 116444736000000000;
					newWrite.dwLowDateTime = (DWORD)ll;
					newWrite.dwHighDateTime = ll >> 32;

					sprintf( buffer
							,"%s;%u;%u;%u;%u\n"
							,strlwr(patchfile->fileName)
							,patchfile->sizeLow
							,patchfile->sizeHigh
							,newWrite.dwLowDateTime
							,newWrite.dwHighDateTime );
					fputs( buffer, stream );

					Progress( m_patchlist.length, i+1 );
				}
				success = true;

				fclose( stream );
				stream = NULL;
			}

			/// patchlist.txt 파일을 압축한 뒤 삭제 [12/11/2008 Jo]
			SYSTEMTIME utc;
			if ( Pack( filename, packfilename, &utc, false ) == false )
				SetStatus( m_wnd, "Fail To Pack Patchlist.txt.z" );
			else if( DeleteFile( filename ) == false )
				SetStatus( m_wnd, "Fail To Delete Patchlist.txt" );
		}

		GlobalFree( m_patchlist.file );
		m_patchlist.file   = NULL;
		m_patchlist.offset = 0;
		m_patchlist.length = 0;
	}
	SetStatus( m_wnd, "End Make..." );

	if ( success )
		PostMessage( m_wnd, WM_PATCH_MSG, (WPARAM)PM_COMPLETE, (LPARAM)0 );
	else
		PostMessage( m_wnd, WM_PATCH_MSG, (WPARAM)PM_ERROR, (LPARAM)0 );

	return 0;
}

void Patchmaker::ErrorPrint(char *msg)
{
	LPVOID lpMsgBuf;
	FormatMessage( 
		FORMAT_MESSAGE_ALLOCATE_BUFFER|
		FORMAT_MESSAGE_FROM_SYSTEM,
		NULL, GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPSTR)&lpMsgBuf, 0, NULL);
	MessageBox(m_wnd, (LPCSTR)lpMsgBuf, msg, MB_ICONERROR);
	LocalFree(lpMsgBuf);
}
